#include <thread>
#include <readline/readline.h>
#include <readline/history.h>

#include "coincell.h"
#include "log.h"
#include "options.h"

#include "tokenize.h"
#include "vcpps.h"
#include "heaters.h"
#include "multiplexers.h"
#include "coincells.h"
#include "startupfailure.h"

void devicefailed(Vcpps& psu, int device, uint16_t serial, int code)
{
	std::stringstream ss;
	Log(Log::WARN)<<"Heater device "<<device<<" with serial number "<<serial<<" has "
	<<(code == Heaters::ERR_RECOVERED ? "recovered" : "failed")<<" with code "<<code;
}

void heaterfailed(int heater, int device)
{
	Log(Log::WARN)<<"Heater "<<heater<<" on device "<<device<<" has failed";
}

bool preparePsu(Vcpps* psu, float voltage, float current)
{
	Vcpps::Status status;
	Log(Log::INFO, true, false)<<"Setting psu voltage to "<<voltage;
	bool ret = psu->setVoltage(voltage);
	if(!ret)
	{
		Log(Log::ERROR, true, false)<<"Unable to set psu voltage";
		return false;
	}
	Log(Log::INFO, true, false)<<"Setting psu current to "<<current;
	ret = psu->setCurrent(current);
	if(!ret)
	{
		Log(Log::ERROR, true, false)<<"Unable to set psu current";
		return false;
	}
	ret = psu->setEnabled(true);
	if(!ret)
	{
		Log(Log::ERROR, true, false)<<"Unable to enable psu output";
		return false;
	}

	Log(Log::INFO, true, false)<<"Waiting for psu to stablize";
	std::this_thread::sleep_for(std::chrono::seconds(2));

	ret = psu->getStatus(status);
	ret = psu->getStatus(status);
	if(!ret)
	{
		Log(Log::ERROR)<<"Unable to read psu state, abort";
		psu->setEnabled(false);
		return false;
	}
	else if(status.curent_limited)
	{
		Log(Log::ERROR)<<"Psu is overcurrent at "<<status.current<<" abort";
		psu->setEnabled(false);
		return false;
	}

	Log(Log::INFO, true, false)<<"PSU voltage: "<<status.voltage<<" current: "<<status.current
	<<" is currently "<<(status.curent_limited ? "" : "not")<<" current limited";
	return true;
}

void print_cmd_help(const std::string& line)
{
	Log(Log::INFO, true, false)<<line<<" is not a valid command. Valid commands: connect (c), disconnect (d), set (s), get (g), clear."<<
		"all commands except clear require a number containing the id of the coincell after the command";
}

int main(int argc, char** argv)
{
	Log::level = Log::INFO;
	Log::sendmailLevel = Log::NEVER;
	Log::headers = false;

	Config config;
	argp_parse(&argp, argc, argv, 0, 0, &config);

	try
	{
		Log(Log::INFO, true, false)<<"Geting PSU";
		Vcpps psu(config.psuPort);
		psu.setEnabled(false);

		Log(Log::INFO, true, false)<<"Aquireing heaters and multiplexers";
		Heaters heaters(config.heaterSerials,
						[&psu](int device, uint16_t serial, int code){devicefailed(psu, device, serial, code);},
						&heaterfailed);
		Multiplexers multiplexers(config.multiplexerSerials);

		std::vector<std::unique_ptr<CoinCell>> coinCells = asign_coincells(config.cells, &heaters, &multiplexers);

		bool ret = preparePsu(&psu, 10, 10);
		if(!ret)
			return 2;

		while(true)
		{
			const char* linePtr =  readline("> ");
			if(!linePtr)
				continue;
			std::string line = std::string(linePtr);

			if(line.empty())
				continue;

			std::vector<std::string> tokens = tokenize(line, ' ');

			int coincellId = -1;
			if(tokens.size() >= 2)
			{
				coincellId = std::stol(tokens[1]);
				if(coincellId < 0 || coincellId > coinCells.size())
					Log(Log::ERROR, true, false)<<"Invalid coin cell id: "<<tokens[1];
			}

			if(coincellId < 0 && tokens[0] != "clear" && tokens[0] != "get")
			{
				print_cmd_help(line);
			}

			if(tokens[0] == "connect" || tokens[0] == "c")
			{
				bool ret = coinCells[coincellId]->setConnected(true);
				if(!ret)
					Log(Log::ERROR, true, false)<<"unable to connect coin cell "<<coincellId;
				else
					Log(Log::ERROR, true, false)<<"connected coin cell "<<coincellId;
			}
			else if(tokens[0] == "disconnect" || tokens[0] == "d")
			{
				bool ret = coinCells[coincellId]->setConnected(false);
				if(!ret)
					Log(Log::ERROR, true, false)<<"unable to disconnect coin cell "<<coincellId;
				else
					Log(Log::ERROR, true, false)<<"disconnected coin cell "<<coincellId;
			}
			else if(tokens[0] == "set" || tokens[0] == "s")
			{
				if(tokens.size() < 3)
				{
					Log(Log::ERROR, true, false)<<"This command requires a temperature";
					continue;
				}

				float temperature = std::stod(tokens[2]);
				if(temperature < 10 || temperature > 100)
				{
					Log(Log::ERROR, true, false)<<"Temperature "<<tokens[2]<<" is invalid";
					continue;
				}
				coinCells[coincellId]->setEnabled(true);
				coinCells[coincellId]->setTemperature(temperature);
			}
			else if(tokens[0] == "get" || tokens[0] == "g")
			{
				float temperature;
				if(coincellId < 0)
				{
					for(size_t i = 0; i < coinCells.size(); ++i)
					{
						std::unique_ptr<CoinCell>& cell = coinCells[i];
						bool ret = cell->getTemperature(temperature);

						if(!ret)
							Log(Log::ERROR, true, false)<<"Cell "<<i<<" UNABLE TO READ";
						else
							Log(Log::INFO, true, false)<<"Cell "<<i<<" temperature: "<<temperature;
					}

					auto connected = multiplexers.getConnected();
					for(size_t i = 0; i < connected.size(); ++i)
						Log(Log::INFO, true, false)<<"multiplexer channel "<<i<<' '<<(connected[i] ? "connected" : "disconnected");
				}
				else
				{
					bool ret = coinCells[coincellId]->getTemperature(temperature);

					if(!ret)
					{
						Log(Log::ERROR, true, false)<<"Cell "<<coincellId<<" UNABLE TO READ";
						continue;
					}

					float setpoint;
					ret = coinCells[coincellId]->getSetpoint(setpoint);

					if(!ret)
					{
						Log(Log::ERROR, true, false)<<"Cell "<<coincellId<<" UNABLE TO READ";
						continue;
					}

					bool enabled = coinCells[coincellId]->getEnabled();

					Log(Log::ERROR, true, false)<<"Cell "<<coincellId<<':';
					Log(Log::ERROR, true, false)<<"\ttemperature: "<<temperature<<"\n\tsetpoint: "<<setpoint;
					Log(Log::ERROR, true, false)<<"\tenabled: "<<(enabled ? "true" : "false");
				}
			}
			else if(tokens[0] == "clear")
			{
				multiplexers.disconnectAll();
				for(std::unique_ptr<CoinCell>& cell :coinCells)
					cell->setEnabled(false);
			}
			add_history(line.c_str());
		}
	}
	catch(const startup_failure& err)
	{
		Log(Log::ERROR, true, false)<<err.what();
		return 1;
	}

}