#include "Kran.h"

#define IMAGECLASS KranImages
#define IMAGEFILE <Kran/Kran.iml>
#include <Draw/iml.h>

const String CONFIG_FILE       = "kran.cfg";
const int    REQUEST_GET_TRIAD = 1;
const DWORD  TIMEOUT           = 300;

GUI_APP_MAIN
{
	SetDefaultCharset(CHARSET_WIN1251);
	IconWin32(KranImages::Kran);

	Kran().Run();
}

Kran::Kran()
	:state(STATE_STOPPED)
{
	MinimizeBox(true);
	CtrlLayout(*this);
	Icon(KranImages::Kran);
	LargeIcon(KranImages::Kran);
	Title(t_("Счётчик данных с контроллера"));

	statusButton.SetImage(KranImages::OK);
	stopButton.SetImage(KranImages::REMOVE());
	
	statusButton <<= THISBACK(OnStart);
	stopButton   <<= THISBACK(OnStop);
	
	statsQueue.SetTimeCallback(-50, THISBACK(UpdateStatsQueue));
	
	const static int MAX_FILE_N = 999;
	Date date = GetSysDate();
	String fnBase = Format("%04d-%02d-%02d", date.year, date.month, date.day);
	String fnAdd;
	int fileN = 1;
	FileIn testFile;
	do
	{
		fnAdd = Format("-%03d", fileN);
		if (!testFile.Open(fnBase+fnAdd))
			break;

		testFile.Close();
		++fileN;
	}
	while (fileN < MAX_FILE_N);
	outFileName = fnBase+fnAdd;
	
	LoadAndInit();
}

Kran::~Kran()
{
	SaveAndShutdown();

	rs232.RequestFinish();
	rs232.WaitFinished();
}

void Kran::UpdateStateUI()
{
	switch (state)
	{
	case STATE_STOPPED:
		comEdit  .Enable();
		comString.Enable();
		comLabel .Enable();

		statusButton.SetEditable(true);// .Enable();
		stopButton  .SetEditable(false);// Disable();
		statusButton = false;
		stopButton = true;
		statusButton.SetFrame(NullFrame());
		stopButton.SetFrame(InsetFrame());

		statusText = "ОТКЛЮЧЕНО";
		break;
	case STATE_STOPPING:
		comEdit  .Disable();
		comString.Disable();
		comLabel .Disable();

		statusButton.SetEditable(false);// Disable();
		stopButton  .SetEditable(false);// Disable();
		statusButton.SetFrame(NullFrame());
		stopButton.SetFrame(NullFrame());
		statusText = "Останавливаюсь...";
		break;
	case STATE_ONLINE:
		comEdit  .Disable();
		comString.Disable();
		comLabel .Disable();

		statusButton.SetEditable(false);// Disable();
		stopButton  .SetEditable(true);// Enable();
		statusButton = true;
		stopButton   = false;
		statusButton.SetFrame(InsetFrame());
		stopButton.SetFrame(NullFrame());

		statusText = "Рабочий режим";
		break;
	}
}

void Kran::LoadAndInit()
{
	if (!LoadFromFile(config, ConfigFile(CONFIG_FILE)))
	{
		config.com        = 1;
		config.comString  = "baud=38400 to=on parity=N data=8 stop=1";
	}
	
	UpdateConnectionUI();
	OnStart();		
}

void Kran::SaveAndShutdown()
{
	rs232.Close();
	StoreToFile(config, ConfigFile(CONFIG_FILE));
}

void Kran::UpdateConnectionUI()
{
	comEdit   <<= config.com;
	comString <<= config.comString;
}

void Kran::OnStart()
{
	config.com       = comEdit;
	config.comString = comString;

	if (rs232.Open(config.com))
	{
		state = STATE_ONLINE;
		Work();
	}

	UpdateStateUI();	
}

void Kran::OnStop()
{
	state = STATE_STOPPING;
	UpdateStateUI();
	rs232.ClearQueueWaitSuspended();
	rs232.Close();
	state = STATE_STOPPED;
	UpdateStateUI();
}

void Kran::Work()
{
	rs232.String2DCB(comString, &dcb);
	rs232.RequestIOSlave<Triad>(RS232RequestElement(
		THISBACK(OnReceiveData), THISBACK(OnPacketReceived),
		TIMEOUT, 0, dcb ));
}

void Kran::OnReceiveData(DWORD timeout, RS232Data *data, bool *result, void *_triad)
{
	static const char MARKER1 = (char) 0xAA;
	static const char MARKER2 = (char) 0x55;
	Triad *triad = static_cast<Triad *>(_triad);
	
	#pragma pack(push,1)
	struct TestTriadData
	{
		union
		{
			struct
			{
				DWORD d1, di1;
				DWORD d2, di2;
				DWORD d3, di3;
			};
			struct
			{
				float f1, fi1;
				float f2, fi2;
				float f3, fi3;
			};
		};
	} testTriadData;
	#pragma pack(pop)
	
	LOG(">>>");
	try
	{
		const static DWORD MINIMAL_MESSAGE_LENGTH = sizeof(MARKER1)+sizeof(MARKER1)+2*sizeof(BYTE)+sizeof(TestTriadData);

		LOG(Format(">>READ1 (%d, %d)", (int) data->GetLength(), (int) MINIMAL_MESSAGE_LENGTH));
		if (data->GetLength() < MINIMAL_MESSAGE_LENGTH)
		{//ждём данные посылки с основной триадой
			if (!rs232.ReceiveData(MINIMAL_MESSAGE_LENGTH - data->GetLength(), data, timeout))
				throw ExcNoMessage();
		}
	
		//есть маркер?
		int start = data->Find(MARKER1);
		if (start == -1)
			throw ExcNoMessage();
		LOG(Format(">>MARKER1, CLRSTART(%d)", start));
		
		//удалить мусор перед посылкой
		data->Remove(0, start);
		
		//получить всю посылку до конца, если необходимо
		int needBytes = MINIMAL_MESSAGE_LENGTH - data->GetLength();
		if (needBytes > 0)
		{
			LOG(Format(">>READ2 (%d)", needBytes));
			if (!rs232.ReceiveData(needBytes, data, timeout))
				throw ExcMessageCorrupted();
		}
		for (int i=0; i<data->GetLength();++i)
			LOG(Format("~%X", (int) (0xFF & (*data)[i])));
	
		//проверка второго маркера		
		if ((*data)[1] != MARKER2)
			throw ExcMessageCorrupted();
		LOG(">>MARKER2, ID, EXT");
		
		//опознаём номер посылки
		triad->n = (BYTE) ((*data)[2]);
		
		//опознание типа посылки (обычный, расширенный)
		triad->ext = (bool) ((*data)[3]);
	
		//проверка основной триады
		data->Remove(0, MINIMAL_MESSAGE_LENGTH - sizeof(TestTriadData));
		memcpy(&testTriadData, (const char *)(*data), sizeof(TestTriadData));
		if (testTriadData.d1 != ~testTriadData.di1 || testTriadData.d2 != ~testTriadData.di2 || testTriadData.d3 != ~testTriadData.di3)
			throw ExcMessageCorrupted();
		
		triad->f[0] = testTriadData.f1;
		triad->f[1] = testTriadData.f2;
		triad->f[2] = testTriadData.f3;
		LOG(">>TRIAD1");
	
		//проверка доп. триады
		if (triad->ext)
		{
			data->Clear();	
			if (!rs232.ReceiveData(sizeof(TestTriadData), data, timeout))
				throw ExcMessageCorrupted();
			memcpy(&testTriadData, (const char *)(*data), sizeof(TestTriadData));
			if (testTriadData.d1 != ~testTriadData.di1 || testTriadData.d2 != ~testTriadData.di2 || testTriadData.d3 != ~testTriadData.di3)
				throw ExcMessageCorrupted();
			triad->f[3] = testTriadData.f1;
			triad->f[4] = testTriadData.f2;
			triad->f[5] = testTriadData.f3;
	
			LOG(">>TRIAD2");
		}
	
		//отсылаем подтверждение
		static const char MARKER_SUCCESS = 6;
		RS232Data sendData;

		sendData.Cat(MARKER_SUCCESS);
		rs232.SendData(sendData);
		LOG(">>SENT SUCCESS");
	
		*result = true;
	}
	catch (const ExcNoMessage &)
	{
		*result = false;
		data->Clear();
		LOG(">>!EXC ExcNoMessage");
		return;
	}
	catch (const ExcMessageCorrupted &)
	{
		static const char MARKER_CORRUPTED = 21;
		RS232Data sendData;

		sendData.Cat(MARKER_CORRUPTED);
		rs232.SendData(sendData);
		*result = false;
		LOG(">>!EXC ExcMessageCorrupted");
		return;
	}
	
}

void Kran::OnPacketReceived(bool success, int id, const void *_triad)
{
	if (!rs232.GetQueueLength())
		rs232.RequestIOSlave<Triad>(RS232RequestElement(
			THISBACK(OnReceiveData), THISBACK(OnPacketReceived),
			TIMEOUT, 0, dcb ));
		
	if (!success)
		return;
	
	const Triad *triad = static_cast<const Triad *>(_triad);
	OnWriteTriad(*triad);
	OnUpdateStats(*triad);
}

void Kran::OnWriteTriad(const Kran::Triad &triad)
{
	static int  n          = 0;
	static BYTE lastTriadN = 0;
	if (triad.n < lastTriadN)
		n += 256;
	lastTriadN = triad.n;


	FileAppend file(outFileName);
	if (triad.ext)
		file << Format("%6>d%20>f%20>f%20>f%20>f%20>f%20>f\n", n+triad.n, triad.f[0], triad.f[1], triad.f[2], triad.f[3], triad.f[4], triad.f[5]);
	else
		file << Format("%6>d%20>f%20>f%20>f\n", n+triad.n, triad.f[0], triad.f[1], triad.f[2]);
}

void Kran::OnUpdateStats(const Kran::Triad &triad)
{
	if (triad.ext)
		stats.SetText(Format("[%d] %f %f %f %f %f %f", triad.n, triad.f[0], triad.f[1], triad.f[2], triad.f[3], triad.f[4], triad.f[5]));
	else
		stats.SetText(Format("[%d] %f %f %f", triad.n, triad.f[0], triad.f[1], triad.f[2]));
}

void Kran::UpdateStatsQueue()
{
	statsQueue.SetText(Format("~%d", rs232.GetQueueLength()));
}