#ifndef _ZnControl2_RS232Thread_h_
#define _ZnControl2_RS232Thread_h_

#include <windows.h>
#include <Core/Core.h>
using namespace Upp;

#ifndef WIN32
#error "Sorry, RS232 class is currently Windows-only!"
#endif

class RS232
{
public:
	RS232()
		: h(INVALID_HANDLE_VALUE)
	{
		ioEvent    = CreateEvent(NULL, TRUE, FALSE, NULL);
		ZeroMemory(&o, sizeof(o));
		o.hEvent = ioEvent;
	}
	
	~RS232()
	{
		Close();
		CloseHandle(ioEvent);
	}
	
	bool Open(int n)
	{
		Close();
	
		char comName[0xFF];
		sprintf(comName, "\\\\.\\COM%d", n);
		
		h = CreateFile (comName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
		if (h == INVALID_HANDLE_VALUE)
			return false;
		
		ZeroMemory(&curDCB, sizeof(curDCB));
		SetStandardTimeouts();
	
		return true;
	}
	
	void Close()
	{
		if (h != INVALID_HANDLE_VALUE)
		{
			CloseHandle(h);
			h = INVALID_HANDLE_VALUE;
		}
	}
	
	void Settings(const Upp::String &s)
	{
		DCB dcb;
		memcpy(&dcb, &curDCB, sizeof(curDCB));
		BuildCommDCB(s, &dcb);

		if (!memcmp(&dcb, &curDCB, sizeof(dcb)))
			return;
		
		memcpy(&curDCB, &dcb, sizeof(dcb));
		if (!SetCommState(h, &curDCB))
			LOG("SetCommState -");
		else
			LOG(Format("SetCommState + (baud:%d, parity:%d, stop:%d)", (int) dcb.BaudRate, (int) dcb.Parity, (int) dcb.StopBits));
	}

	bool SendData(const StringBuffer &data, dword timeout = INFINITE)
	{
		dword written;
		WriteFile(h, const_cast<StringBuffer &>(data).Begin(), data.GetLength(), &written, &o);
		dword result = WaitForSingleObject(ioEvent, timeout);
		switch (result)
		{
		case WAIT_OBJECT_0:
			return true;
		case WAIT_TIMEOUT:
			CancelIo(h);
			return false;
		}
		
		return false;
	}
	
	bool ReceiveData(dword size, StringBuffer &data, dword timeout = INFINITE)
	{
		bool   success = false;
		dword  read    = 0;
		int    offs    = data.GetCount();
		
		data.SetCount(data.GetCount()+size);
		int io = ReadFile(h, data.Begin()+offs, size, &read, &o);
		dword result = WaitForSingleObject(ioEvent, timeout);
		switch (result)
		{
		case WAIT_OBJECT_0:
			GetOverlappedResult(h, &o, &read, FALSE);
			if (read)
				success = true;
			break;
		case WAIT_TIMEOUT:
			data.SetCount(data.GetCount()-size);
			CancelIo(h);
		}
		
		return success;
	} 
	
	void SetStandardTimeouts(dword readTimeout = 4000)
	{
		COMMTIMEOUTS curTimeouts;
		
		curTimeouts.ReadIntervalTimeout         = 0;
		curTimeouts.ReadTotalTimeoutMultiplier  = 0;
		curTimeouts.ReadTotalTimeoutConstant    = readTimeout;
		
		curTimeouts.WriteTotalTimeoutMultiplier = 0;
		curTimeouts.WriteTotalTimeoutConstant   = 0;
		
		SetCommTimeouts(h, &curTimeouts);
	}

private:
	HANDLE       h;
	HANDLE       ioEvent;
	DCB          curDCB;
	OVERLAPPED   o;
};

struct RS232Element : Moveable<RS232Element>
{
	enum Type {BYTE, WORD, DWORD, INT8, INT16, INT32, CRC, STRING};
	
	RS232Element(Type _t, int _op1 = 0, int _op2 = 0) 
	                              :t(_t),                   op1(_op1), op2(_op2), constant(false)      {}
	RS232Element(byte b)          :t(RS232Element::BYTE),   op1(0),    op2(0),    constant(true), v(b) {}
	RS232Element(const Upp::String &s) :t(RS232Element::STRING), op1(0),    op2(0),    constant(true), v(s) {}
	
	Value v;
	int   op1;
	int   op2;
	Type  t;
	bool  constant;
};

class RS232Protocol
{
public:
	RS232Protocol(int _attempts = 3) :attempts(_attempts) {}
	
	RS232Protocol & Byte()  {elements << RS232Element(RS232Element::BYTE);  return *this;}
	RS232Protocol & Word()  {elements << RS232Element(RS232Element::WORD);  return *this;}
	RS232Protocol & DWord() {elements << RS232Element(RS232Element::DWORD); return *this;}
	RS232Protocol & Int8()  {elements << RS232Element(RS232Element::INT8);  return *this;}
	RS232Protocol & Int16() {elements << RS232Element(RS232Element::INT16); return *this;}
	RS232Protocol & Int32() {elements << RS232Element(RS232Element::INT32); return *this;}
	RS232Protocol & CRC(int pos = 0, int l = -1)
	                        {elements << RS232Element(RS232Element::CRC, pos, l);  return *this;}
	RS232Protocol & String(int l = -1) {elements << RS232Element(RS232Element::STRING, l);}

	RS232Protocol & operator()(byte b) {elements << RS232Element(b);               return *this;}
	RS232Protocol & operator()(const Upp::String &s) {elements << RS232Element(s); return *this;}

	Value & operator[] (int i) {ASSERT(i >= 0 && i < elements.GetCount()); return elements[i].v;}
	
	void Send(RS232 &rs232, dword timeout = INFINITE)
	{
		StringBuffer s;
		for (int i=0; i<elements.GetCount(); ++i)
		{
			bool checkNull = true;
			switch (elements[i].t)
			{
			case RS232Element::BYTE:  {byte  v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(byte)); } break;
			case RS232Element::WORD:  {word  v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(word)); } break;
			case RS232Element::DWORD: {dword v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(dword));} break;
			case RS232Element::INT8:  {int8  v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(int8)); } break;
			case RS232Element::INT16: {int16 v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(int16));} break;
			case RS232Element::INT32: {int32 v = (int)elements[i].v; s.Cat(reinterpret_cast<const char *>(&v), sizeof(int32));} break;
			case RS232Element::CRC:   {
			                           checkNull = false;
			                           byte v = 0; 
			                           int pos2 = elements[i].op2 < 0 ? s.GetCount()-1 : elements[i].op1+elements[i].op2;
			                           for (int j=elements[i].op1; j<=pos2; ++j)
			                               v += static_cast<byte>(s[j]);
			                           s.Cat(reinterpret_cast<const char *>(&v), sizeof(byte));
			                          }                                        break;
			case RS232Element::STRING:{
			                           Upp::String str = static_cast<Upp::String>(elements[i].v);
			                           if (elements[i].op1 >= 0 && str.GetCount() > elements[i].op1)
			                               str.Trim(elements[i].op1);
			                           s.Cat(str);
			                          }                                        break;
			}
			
			if (checkNull)
			{
				ASSERT(!elements[i].v.IsNull());
			}
		}
		rs232.SendData(s, timeout);
	}
	
	bool Receive(RS232 &rs232, dword timeout = INFINITE)
	{
		for (int att=0; att<attempts; ++att)
			if (_Receive(rs232, timeout))
				return true;
		
		return false;
	}
	
	bool _Receive(RS232 &rs232, dword timeout = INFINITE)
	{
		StringBuffer s;
		dword t0      = GetTickCount();

		int sze = 0;
		for (int i=0; i<elements.GetCount(); ++i)
		{
			int l = 0;
			switch (elements[i].t)
			{
			case RS232Element::BYTE:  l=sizeof(byte);  break;
			case RS232Element::WORD:  l=sizeof(word);  break;
			case RS232Element::DWORD: l=sizeof(dword); break;
			case RS232Element::INT8:  l=sizeof(int8);  break;
			case RS232Element::INT16: l=sizeof(int16); break;
			case RS232Element::INT32: l=sizeof(int32); break;
			case RS232Element::CRC:   l=sizeof(byte);  break;
			case RS232Element::STRING:l= elements[i].op1 < 0 ? (static_cast<Upp::String>(elements[i].v)).GetLength() : elements[i].op1; break;
			}
			sze += l;
		}
		
		if (!rs232.ReceiveData(sze,s,timeout))
			return false;
		
		while (timeout == INFINITE || GetTickCount()-t0 <= timeout)
		{
			bool checkOK = true;
			int offs = 0;
			int l;
			byte b = 0;
			for (int i=0; i<elements.GetCount(); ++i)
			{
				Value oldV = elements[i].v;
				switch (elements[i].t)
				{
				case RS232Element::BYTE:  elements[i].v = *(reinterpret_cast<byte  *>(s.Begin()+offs)); offs +=1; break;
				case RS232Element::WORD:  elements[i].v = *(reinterpret_cast<word  *>(s.Begin()+offs)); offs +=2; break;
				case RS232Element::DWORD: elements[i].v = *(reinterpret_cast<int32 *>(s.Begin()+offs)); offs +=4; break;
				case RS232Element::INT8:  elements[i].v = *(reinterpret_cast<int8  *>(s.Begin()+offs)); offs +=1; break;
				case RS232Element::INT16: elements[i].v = *(reinterpret_cast<int16 *>(s.Begin()+offs)); offs +=2; break;
				case RS232Element::INT32: elements[i].v = *(reinterpret_cast<int32 *>(s.Begin()+offs)); offs +=4; break;
				case RS232Element::CRC:
					b = 0;
					for (int bi=elements[i].op1; bi<=(elements[i].op2 < 0 ? offs-1 : elements[i].op1+elements[i].op2); ++bi)
						b += static_cast<byte>(s[bi]);
					oldV = b;
					elements[i].v = *(reinterpret_cast<byte  *>(s.Begin()+offs)); offs +=1; break;
				case RS232Element::STRING:
					l = (elements[i].op1 < 0 ? (static_cast<Upp::String>(elements[i].v)).GetLength() : elements[i].op1);
					elements[i].v = Upp::String(s.Begin()+offs, l);
					offs += l;
					break;
				}
				
				if (elements[i].constant && elements[i].v != oldV)
				{
					elements[i].v = oldV;
					checkOK = false;
					break;
				}
			}
			if (checkOK)
				return true;
			
			Upp::String strs(s);
			strs.Remove(0);
			s = strs;
			if (!rs232.ReceiveData(1,s,timeout))
				return false;
		}
		return false;
	}
	
private:
	Vector<RS232Element> elements;
	int attempts;
};
#endif
