#include "Connector.h"
namespace mdd {
	json Connector::add(const json& args) {
		json ret;
		auto jsub = args.find("subject");
		auto jobj = args.find("object");
		if (jsub == args.end() || jobj == args.end())
		{
			return ret;
		}

		auto jsub_typ = jsub->find("type");
		if (jsub_typ == jsub->end())
		{
			return ret;
		}
		ret["operation"] = "add";
		json jargs;
		ProcessorBase* proc_ptr = nullptr;
		if (*jsub_typ == "processor")
		{
			if (proc_ptr = dynamic_cast<ProcessorBase*>(&(*_root->getModule(*jsub))))
			{
				jargs["subject"] = proc_ptr->getIdentifier();
			}
			else {
				json j;
				return j;
			}
		}

		auto jobj_typ = jobj->find("type");
		if (jobj_typ == jobj->end())
		{
			json j;
			return j;
		}

		if (*jobj_typ == "processor")
		{
			proc_ptr->addModule(Registration().generateProcessor((*jobj)["key"].get<std::string>()));
			jargs["object"] = proc_ptr->getModules().back()->dump();
		}
		else if (*jobj_typ == "module")
		{
			proc_ptr->addModule(Registration().generateModule((*jobj)["key"].get<std::string>()));
			jargs["object"] = proc_ptr->getModules().back()->dump();
		}
		else if(*jobj_typ == "connection")
		{
			auto jobj_in = jobj->find("input");
			auto jobj_out = jobj->find("output");
			IModule::Ptr module_in_ptr = _root->getModule(*jobj_in);
			IModule::Ptr module_out_ptr = _root->getModule(*jobj_out);
			if (module_in_ptr != nullptr && module_out_ptr != nullptr) {
				IInput::Ptr in_ptr = nullptr;
				std::string id = (*jobj_in)["key"].get<std::string>() + std::to_string((*jobj_in)["appendix"].get<int>());
				for (size_t i = 0; i < module_in_ptr->getNumInputs(); i++)
				{
					auto in = module_in_ptr->getInput(i);
					if (in->getID() == id)
					{
						in_ptr = in;
						break;
					}
				}

				IOutput::Ptr out_ptr = nullptr;
				id = (*jobj_out)["key"].get<std::string>() + std::to_string((*jobj_out)["appendix"].get<int>());
				for (size_t i = 0; i < module_out_ptr->getNumOutputs(); i++)
				{
					auto out = module_out_ptr->getOutput(i);
					if (out->getID() == id)
					{
						out_ptr = out;
						break;
					}
				}

				if (in_ptr != nullptr && out_ptr != nullptr)
				{
					in_ptr->connect(out_ptr);
					jargs["object"] = *jobj;
				}
			}
		}
		else if (*jobj_typ == "input") {
			IModule::Ptr module_in_ptr = _root->getModule(*jobj);
			if (module_in_ptr != nullptr) {
				IInput::Ptr in_ptr = nullptr;
				std::string id = (*jobj)["key"].get<std::string>() + std::to_string((*jobj)["appendix"].get<int>());
				for (size_t i = 0; i < module_in_ptr->getNumInputs(); i++)
				{
					auto in = module_in_ptr->getInput(i);
					if (in->getID() == id)
					{
						in_ptr = in;
						break;
					}
				}
				return proc_ptr->addInput(in_ptr);
			}
		}
		else if (*jobj_typ == "output") {
			IModule::Ptr module_out_ptr = _root->getModule(*jobj);
			if (module_out_ptr != nullptr) {
				IOutput::Ptr out_ptr = nullptr;
				std::string id = (*jobj)["key"].get<std::string>() + std::to_string((*jobj)["appendix"].get<int>());
				for (size_t i = 0; i < module_out_ptr->getNumOutputs(); i++)
				{
					auto out = module_out_ptr->getOutput(i);
					if (out->getID() == id)
					{
						out_ptr = out;
						break;
					}
				}
				return proc_ptr->addOutput(out_ptr);
			}
		}
		else {
			return ret;
		}
		ret["args"] = jargs;
		return ret;
	}

	json Connector::remove(const json& args) {
		json ret;
		auto jsub = args.find("subject");
		auto jobj = args.find("object");
		if (jsub == args.end() || jobj == args.end())
		{
			return ret;
		}
		auto jsub_typ = jsub->find("type");
		if (jsub_typ == jsub->end())
		{
			return ret;
		}
		ret["operation"] = "remove";
		json jargs;
		if (*jsub_typ == "processor")
		{
			if (ProcessorBase* proc_ptr = dynamic_cast<ProcessorBase*>(&(*_root->getModule(*jsub))))
			{
				auto module_ptr = proc_ptr->getModule(*jobj);
				jargs["subject"] = proc_ptr->getIdentifier();
				jargs["object"] = module_ptr->getIdentifier();
				proc_ptr->removeModule(module_ptr);
			}
		}
		ret["args"] = jargs;
		return ret;
	}

	json Connector::change(const json& args) {
		json ret;
		auto jsub = args.find("subject");
		auto jobj = args.find("object");
		if (jsub == args.end() || jobj == args.end())
		{
			return ret;
		}
		auto jsub_typ = jsub->find("type");
		if (jsub_typ == jsub->end())
		{
			return ret;
		}
		ret["operation"] = "change";
		json jargs;
		if (*jsub_typ == "processor")
		{
			if (*jsub == _root->getIdentifier())
			{
				jargs["subject"] = _root->getIdentifier();
				if (_root->configure(*jobj))
				{
					jargs["object"] = *jobj;
				}
			}
			else
			{
				IModule::Ptr module_ptr = _root->getModule(*jsub);
				if (module_ptr == nullptr) {
					std::cout << "[Connector]: change: Was not able to find module." << std::endl;
					return ret;
				}
				jargs["subject"] = module_ptr->getIdentifier();
				module_ptr->configure(*jobj);
				auto jobj_conf = jobj->find("configure");
				if (jobj_conf != jobj->end())
				{
					jargs["object"] = module_ptr->dump();
				}
				else {
					jargs["object"] = *jobj;
				}
			}
		}
		else if (*jsub_typ == "module")
		{
			IModule::Ptr module_ptr = _root->getModule(*jsub);
			if (module_ptr == nullptr) {
				std::cout << "[Connector]: change: Was not able to find module." << std::endl;
				return ret;
			}
			jargs["subject"] = module_ptr->getIdentifier();
			module_ptr->configure(*jobj);
			if (jobj->contains("configure"))
			{
				jargs["object"] = module_ptr->dump();
			}
			else {
				jargs["object"] = *jobj;
			}
		}
		else if (*jsub_typ == "input")
		{
			IModule::Ptr module_ptr = _root->getModule(*jsub);
			if (module_ptr == nullptr) {
				std::cout << "[Connector]: change: Was not able to find module." << std::endl;
				return ret;
			}
			IInput::Ptr in_ptr = module_ptr->getInput(*jsub);
			if (in_ptr == nullptr) {
				std::cout << "[Connector]: change: Was not able to find input." << std::endl;
				return ret;
			}
			in_ptr->configure(*jobj);
			jargs["subject"] = in_ptr->getIdentifier();
			jargs["object"] = *jobj;
		}
		else if (*jsub_typ == "output")
		{
			IModule::Ptr module_ptr = _root->getModule(*jsub);
			if (module_ptr == nullptr) {
				std::cout << "[Connector]: change: Was not able to find module." << std::endl;
				return ret;
			}
			IOutput::Ptr out_ptr = module_ptr->getOutput(*jsub);
			if (out_ptr == nullptr) {
				std::cout << "[Connector]: change: Was not able to find output." << std::endl;
				return ret;
			}
			out_ptr->configure(*jobj);
			jargs["subject"] = out_ptr->getIdentifier();
			jargs["object"] = *jobj;
		}
		else if (*jsub_typ == "optimizer") {
			_opt->configure(*jobj);
			jargs["subject"] = _opt->getIdentifier();
			jargs["object"] = *jobj;
		}
		else {
			json j;
			return j;
		}
		ret["args"] = jargs;
		return ret;
	}

	json Connector::state(const json& args) {
		json ret;
		bool found = false;
		//init
		if (args == "all")
		{
			ret = encode();
		}
		//Toolbox
		if (args.contains("module"))
		{
			found = true;
			if (args["module"] == "all")
			{
				
				auto modules = regi.getModules();
				for (size_t i = 0; i < modules.size(); i++)
				{
					json jmodule;
					jmodule["key"] = modules[i];
					jmodule["type"] = "module";
					ret["module"].push_back(jmodule);
				}
			}
		}
		if (args.contains("processor"))
		{
			found = true;
			if (args["processor"] == "all")
			{
				auto processors = regi.getProcessors();
				for (size_t i = 0; i < processors.size(); i++)
				{
					json jprocessor;
					jprocessor["key"] = processors[i];
					jprocessor["type"] = "processor";
					ret["processor"].push_back(jprocessor);
				}
			}
		}

		//
		auto jtype = args.find("type");
		if (jtype != args.end())
		{
			found = true;
			if (*jtype == "optimizer")
			{
				ret["info"] = _opt->getConfiguration();
				//ret["info"]["ID"] = _opt->getIdentifier();
			}
			else
			{
				auto module_ptr = _root->getModule(args);
				if (*jtype == "processor")
				{
					if (IProcessor* processor_ptr = dynamic_cast<IProcessor*>(&(*module_ptr)))
					{
						ret["info"] = processor_ptr->getConfiguration();
						//ret["info"]["ID"] = processor_ptr->getIdentifier();
					}
				}
				else if (*jtype == "module")
				{
					ret["info"] = module_ptr->getConfiguration();
					//ret["info"]["ID"] = module_ptr->getIdentifier();
				}
				else if (*jtype == "input")
				{
					auto ptr = module_ptr->getInput(args);
					ret["info"] = ptr->getConfiguration();
					//ret["info"]["ID"] = ptr->getIdentifier();
				}
				else if (*jtype == "output")
				{
					auto ptr = module_ptr->getOutput(args);
					ret["info"] = ptr->getConfiguration();
					//ret["info"]["ID"] = ptr->getIdentifier();
				}
			}
		}


		if (!found)
		{
			ret["error"] = "Reached end of state parser!";
		}
		//std::cout << "[Connector]: state: " << j.dump() << std::endl;
		json jret;
		jret["operation"] = "state";
		jret["args"] = ret;
		return jret;
	}

	json Connector::save(const json& args) {
		std::ofstream out("save.json");
		json jfile;
		jfile["processor"] = _root->dump();
		jfile["optimizer"] = _opt->dump();
		out << std::setw(4) << jfile << std::endl;
		json ret;
		ret["operation"] = "save";
		return ret;
	}

	json Connector::load(const json& args) {
		if (std::filesystem::exists("save.json")) {
			std::ifstream in("save.json");
			json jfile;
			in >> jfile;
			auto jproc = jfile.find("processor");
			if (jproc != jfile.end())
			{
				if (jproc->contains("key"))
				{
					_root = regi.generateProcessor((*jproc)["key"].get<std::string>());
					_root->load((*jproc));
				}
			}
			auto jopt = jfile.find("optimizer");
			if (jopt != jfile.end()) {
				if (jopt->contains("key"))
				{
					_opt = regi.generateOptimizer((*jopt)["key"].get<std::string>());
					_opt->connect(_root);
					_opt->load((*jopt));
				}
			}
		}
		else
		{
			_root = regi.generateProcessor("ProcessorStandard");
			json jconfig = _root->getConfiguration();
			jconfig["GUI"]["name"]["value"]	= "root";
			_root->configure(jconfig);
			_opt = regi.generateOptimizer("OptimizerEvolutionary");
			_opt->connect(_root);
		}
		_opt->attachCallback(std::bind(&Connector::optimizerCallback,this,std::placeholders::_1));
		json ret;
		ret["operation"] = "state";
		ret["args"].push_back(encode());
		return ret;
	}

	json Connector::optimizerCallback(const json& callback) {
		auto jit = callback.find("operation");
		if (jit != callback.end())
		{
			if (jit->get<std::string>() == "autosave")
			{
				return save(callback);
			}
		}
		return _callback(callback);
	}

	Connector::Connector()
	{
		json path;
		load(path);
	}

	json Connector::decode(const json& request)
	{
		json ret;

		// check for correct structure
		if (!request.contains("operation") || !request.contains("args")) {
			ret["error"] = "Missing json member!";
			std::cout << "[Connector]: " << "Missing json member!" << std::endl;
			return ret;
		}
		
		auto it = _ops.find(request["operation"].get<std::string>());
		if (it != _ops.end())
		{
			ret = it->second(request["args"]);
			//std::cout << "[Connector]: " << ret.dump() << std::endl;
			return ret;
		}
		ret["error"] = "Operation is not supported!";
		return ret;
	}

	json  Connector::encode()
	{
		json ret;
		ret["processor"] = _root->dump();
		ret["optimizer"]["permutations"] = _opt->getPermutations();
		std::cout << "[Connector]: encode: " << ret.dump() << std::endl;
		return ret;
	}

	void Connector::attachCallback(std::function<json(const json&)> callback)
	{
		_callback = callback;
	}
}

