// license:BSD-3-Clause
// copyright-holders:Couriersud
/*
 * nlid_proxy.cpp
 *
 */

#include "nlid_proxy.h"
#include "core/setup.h"
#include "nl_errstr.h"
#include "solver/nld_solver.h"

#include <array>

namespace netlist::devices {

	// -----------------------------------------------------------------------------
	// nld_base_proxy
	// -----------------------------------------------------------------------------

	static constexpr std::array<std::pair<const char *, const char *>, 3> power_syms = {{ {"VCC", "VEE"}, {"VCC", "GND"}, {"VDD", "VSS"}}};

	nld_base_proxy::nld_base_proxy(netlist_state_t &anetlist, const pstring &name,
		const logic_t *inout_proxied)
		: device_t(anetlist, name, inout_proxied->logic_family())
		, m_tp(nullptr)
		, m_tn(nullptr)
	{
		if (logic_family() == nullptr)
		{
			throw nl_exception(MF_NULLPTR_FAMILY_NP("nld_base_proxy"));
		}


		bool f = false;
		for (const auto & pwr_sym : power_syms)
		{
			pstring devname = inout_proxied->device().name();

			auto *tp_ct(anetlist.setup().find_terminal(devname + "." + pstring(pwr_sym.first),
					/*detail::terminal_type::INPUT,*/ false));
			auto *tp_cn(anetlist.setup().find_terminal(devname + "." + pstring(pwr_sym.second),
				/*detail::terminal_type::INPUT,*/ false));
			if ((tp_ct != nullptr) && (tp_cn != nullptr))
			{
				if (!tp_ct->is_analog())
					throw nl_exception(plib::pfmt("Not an analog terminal: {1}")(tp_ct->name()));
				if (!tp_cn->is_analog())
					throw nl_exception(plib::pfmt("Not an analog terminal: {1}")(tp_cn->name()));

				auto *tp_t = dynamic_cast<analog_t* >(tp_ct);
				auto *tn_t = dynamic_cast<analog_t *>(tp_cn);
				if (f && (tp_t != nullptr && tn_t != nullptr))
					log().warning(MI_MULTIPLE_POWER_TERMINALS_ON_DEVICE(inout_proxied->device().name(),
						m_tp->name(), m_tn->name(),
						tp_t != nullptr ? tp_t->name() : "",
						tn_t != nullptr ? tn_t->name() : ""));
				else if (tp_t != nullptr && tn_t != nullptr)
				{
					m_tp = tp_t;
					m_tn = tn_t;
					f = true;
				}
			}
		}
		if (!f)
			throw nl_exception(MF_NO_POWER_TERMINALS_ON_DEVICE_2(name, anetlist.setup().de_alias(inout_proxied->device().name())));

		log().verbose("D/A Proxy: Found power terminals on device {1}", inout_proxied->device().name());
	}

	// ----------------------------------------------------------------------------------------
	// nld_a_to_d_proxy
	// ----------------------------------------------------------------------------------------

	nld_base_a_to_d_proxy::nld_base_a_to_d_proxy(netlist_state_t &anetlist, const pstring &name,
			const logic_input_t *in_proxied)
	: nld_base_proxy(anetlist, name, in_proxied)
	{
	}

	nld_a_to_d_proxy::nld_a_to_d_proxy(netlist_state_t &anetlist, const pstring &name, const logic_input_t *in_proxied)
	: nld_base_a_to_d_proxy(anetlist, name, in_proxied)
	, m_Q(*this, "Q")
	, m_I(*this, "I", nldelegate(&nld_a_to_d_proxy::input, this))
	{
	}

	NETLIB_HANDLER(a_to_d_proxy, input)
	{
		const auto v(m_I.Q_Analog());
		const auto vn(m_tn->net().Q_Analog());
		const auto vp(m_tp->net().Q_Analog());

		if (logic_family()->is_above_high_thresh_V(v, vn, vp))
			out().push(1, netlist_time::quantum());
		else if (logic_family()->is_below_low_thresh_V(v, vn, vp))
			out().push(0, netlist_time::quantum());
		else
		{
			// do nothing
		}
	}

	// ----------------------------------------------------------------------------------------
	// nld_d_to_a_proxy
	// ----------------------------------------------------------------------------------------

	nld_base_d_to_a_proxy::nld_base_d_to_a_proxy(netlist_state_t &anetlist, const pstring &name,
			const logic_output_t *out_proxied)
	: nld_base_proxy(anetlist, name, out_proxied)
	{
	}

	nld_d_to_a_proxy::nld_d_to_a_proxy(netlist_state_t &anetlist, const pstring &name, const logic_output_t *out_proxied)
	: nld_base_d_to_a_proxy(anetlist, name, out_proxied)
	, m_I(*this, "I", nldelegate(&nld_d_to_a_proxy :: input, this))
	, m_RP(*this, "RP")
	, m_RN(*this, "RN")
	, m_last_state(*this, "m_last_var", terminal_t::OUT_TRISTATE())
	{
		register_subalias("Q", "RN.1");

		connect(m_RN.N(), *m_tn);
		connect(m_RP.P(), *m_tp);

		connect(m_RN.P(), m_RP.N());
	}


	void nld_d_to_a_proxy::reset()
	{
		//m_Q.initial(0.0);
		m_last_state = terminal_t::OUT_TRISTATE();
		m_RN.reset();
		m_RP.reset();
		m_RN.set_G_V_I(plib::reciprocal(logic_family()->R_low()),
				logic_family()->low_offset_V(), nlconst::zero());
		m_RP.set_G_V_I(G_OFF,
			nlconst::zero(),
			nlconst::zero());
	}

	NETLIB_HANDLER(d_to_a_proxy ,input)
	{
		const auto state = m_I();
		if (state != m_last_state)
		{
			// RN, RP are connected ...
			m_RN.change_state([this, &state]()
			{
				switch (state)
				{
					case 0:
						m_RN.set_G_V_I(plib::reciprocal(logic_family()->R_low()),
								logic_family()->low_offset_V(), nlconst::zero());
						m_RP.set_G_V_I(G_OFF,
							nlconst::zero(),
							nlconst::zero());
						break;
					case 1:
						m_RN.set_G_V_I(G_OFF,
							nlconst::zero(),
							nlconst::zero());
						m_RP.set_G_V_I(plib::reciprocal(logic_family()->R_high()),
								logic_family()->high_offset_V(), nlconst::zero());
						break;
					case terminal_t::OUT_TRISTATE():
						m_RN.set_G_V_I(G_OFF,
							nlconst::zero(),
							nlconst::zero());
						m_RP.set_G_V_I(G_OFF,
							nlconst::zero(),
							nlconst::zero());
						break;
					default:
						plib::terminate("unknown state for proxy: this should never happen!");
				}
			});
			m_last_state = state;
		}
	}

} // namespace netlist::devices
