#include "kernel/yosys.h"
#include "kernel/celltypes.h"
#include "kernel/newcelltypes.h"
#include "kernel/ff.h"

USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN

struct LibertyStubber {
	CellTypes ct;
	LibertyStubber() {
		ct.setup();
		ct.setup_internals_ff();
	}
	void liberty_prefix(std::ostream& f)
	{
		f << "/*\n";
		f << stringf("\tModels interfaces of select Yosys internal cell.\n");
		f << stringf("\tLikely contains INCORRECT POLARITIES.\n");
		f << stringf("\tImpractical for any simulation, synthesis, or timing.\n");
		f << stringf("\tIntended purely for SDC expansion.\n");
		f << stringf("\tDo not microwave or tumble dry.\n");
		f << stringf("\tGenerated by %s\n", yosys_maybe_version());
		f << "*/\n";
		f << "library (yosys) {\n";
		f << "\tinput_threshold_pct_fall : 50;\n";
		f << "\tinput_threshold_pct_rise : 50;\n";
		f << "\toutput_threshold_pct_fall : 50;\n";
		f << "\toutput_threshold_pct_rise : 50;\n";
		f << "\tslew_lower_threshold_pct_fall : 1;\n";
		f << "\tslew_lower_threshold_pct_rise : 1;\n";
		f << "\tslew_upper_threshold_pct_fall : 99;\n";
		f << "\tslew_upper_threshold_pct_rise : 99;\n";
	}
	void liberty_suffix(std::ostream& f)
	{
		f << "}\n";
	}
	struct LibertyItemizer {
		std::ostream& f;
		int indent;
		LibertyItemizer(std::ostream& f) : f(f), indent(0) {};
		void item(std::string key, std::string val)
		{
			f << std::string(indent, '\t') << key << " : \"" << val << "\";\n";
		}
	};
	void liberty_flop(Module* base, Module* derived, std::ostream& f)
	{
		auto base_name = base->name.str().substr(1);
		auto derived_name = derived->name.str().substr(1);

		FfTypeData ffType(base_name);
		LibertyItemizer i(f);

		if (ffType.has_gclk) {
			log_warning("Formal flip flop %s can't be modeled\n", base_name.c_str());
			return;
		}
		if (ffType.has_ce) {
			log_warning("DFFE %s can't be modeled\n", base_name.c_str());
			return;
		}

		f << "\tcell (\"" << derived_name << "\") {\n";
		auto& base_type = ct.cell_types[base_name];
		i.indent = 3;
		auto sorted_ports = derived->ports;
		// Hack for CLK and C coming before Q does
		auto cmp = [](IdString l, IdString r) { return l.str() < r.str(); };
		std::sort(sorted_ports.begin(), sorted_ports.end(), cmp);
		std::string clock_pin_name = "";
		for (auto x : sorted_ports) {
			std::string port_name = RTLIL::unescape_id(x);
			bool is_input = base_type.inputs.count(x);
			bool is_output = base_type.outputs.count(x);
			f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n";
			if (is_input && !is_output) {
				i.item("direction", "input");
			} else if (!is_input && is_output) {
				i.item("direction", "output");
			} else {
				i.item("direction", "inout");
			}
			if (port_name == "CLK" || port_name == "C") {
				i.item("clock", "true");
				clock_pin_name = port_name;
			}
			if (port_name == "Q") {
				i.item("function", "IQ");
				f << "\t\t\ttiming () {\n";
				i.indent++;
				log_assert(clock_pin_name.size());
				i.item("related_pin", clock_pin_name);
				i.indent--;
            	f << "\t\t\t}\n";
			}
			f << "\t\t}\n";
		}

		f << "\t\tff (\"IQ\",\"IQ_N\") {\n";
		i.indent = 3;
		// TODO polarities?
		if (ffType.has_clk) {
			auto pin = ffType.is_fine ? "C" : "CLK";
			i.item("clocked_on", pin);
		}
		if (ffType.has_arst) {
			auto meaning = (ffType.val_arst == State::S1) ? "preset" : "clear";
			auto pin = ffType.is_fine ? "R" : "ARST";
			i.item(meaning, pin);
		}
		auto next_state = ffType.has_ce ? "D & EN" : "D";
		i.item("next_state", next_state);
		f << "\t\t}\n";
		f << "\t}\n";
	}
	void liberty_cell(Module* base, Module* derived, std::ostream& f)
	{
		auto base_name = base->name.str().substr(1);
		auto derived_name = derived->name.str().substr(1);
		if (!ct.cell_types.count(base_name)) {
			log_debug("skip skeleton for %s\n", base_name.c_str());
			return;
		}

		if (StaticCellTypes::categories.is_ff(base_name))
			return liberty_flop(base, derived, f);

		auto& base_type = ct.cell_types[base_name];
		f << "\tcell (\"" << derived_name << "\") {\n";
		for (auto x : derived->ports) {
			bool is_input = base_type.inputs.count(x);
			bool is_output = base_type.outputs.count(x);
			f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n";
			if (is_input && !is_output) {
				f << "\t\t\tdirection : input;\n";
			} else if (!is_input && is_output) {
				f << "\t\t\tdirection : output;\n";
			} else {
				f << "\t\t\tdirection : inout;\n";
			}
			f << "\t\t}\n";
		}
		f << "\t}\n";
	}
};

struct IcellLiberty : Pass {
	IcellLiberty() : Pass("icell_liberty", "write Liberty interfaces for used internal cells") {}
	void help() override
	{
		//   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
		log("\n");
		log("    icell_liberty <liberty_file>\n");
		log("\n");
		log("Write Liberty files modeling the interfaces of used internal cells.\n");
		log("\n");
		log("Models are not guaranteed to be logically sound.\n");
		log("\n");
	}
	void execute(std::vector<std::string> args, RTLIL::Design *d) override
	{
		log_header(d, "Executing ICELL_LIBERTY pass.\n");

		size_t argidx;
		std::string liberty_filename;
		auto liberty_file = std::make_unique<std::ofstream>();

		for (argidx = 1; argidx < args.size(); argidx++) {
			break;
		}
		if (argidx < args.size())
			liberty_filename = args[argidx++];
		else
			log_cmd_error("no Liberty filename specified\n");

		if (liberty_filename.size()) {
			liberty_file->open(liberty_filename.c_str());
			if (liberty_file->fail()) {
				log_cmd_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno));
			}
		}

		pool<RTLIL::IdString> done;
		LibertyStubber stubber = {};

		stubber.liberty_prefix(*liberty_file);

		for (auto module : d->selected_modules()) {
			for (auto cell : module->selected_cells()) {
				Module *inst_module = d->module(cell->type);
				if (!inst_module || !inst_module->get_blackbox_attribute())
					continue;
				Module *base = inst_module;
				if (!done.count(base->name)) {
					stubber.liberty_cell(base, base, *liberty_file);
					done.insert(base->name);
				}
			}
		}

		stubber.liberty_suffix(*liberty_file);
	}
} IcellLiberty;

PRIVATE_NAMESPACE_END
