//---------------------------------------------------------------------------
#include "stdafx.h"
//------------------------------------------------------------------------------
#pragma hdrstop

#include <string>
#include <windows.h> 

//#include <IniFiles.hpp>
#include "Validator.h"
#include "inifile.h"
#include "stdTools.h"
//#include "ud_Module.h"

//---------------------------------------------------------------------------
//Validator* validator=NULL;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
unsigned char ConReset[6] = { 0x02,0x03,0x06,0x30,0x41,0xB3 }; //RESET Перезагрузить валидатор

unsigned char ConReACK[6] = { 0x02,0x03,0x06,0x00,0xC2,0x82 }; //Ответ валидатору что всё нормально...
unsigned char ConStPoll[6] = { 0x02,0x03,0x06,0x33,0xDA,0x81 }; //POLL 33H Request for Bill-to-Bill unit activity Status (Перейти в режим получения платежей)
unsigned char ConIdent[6] = { 0x02,0x03,0x06,0x37,0xFE,0xC7 }; //IDENTIFICATION  37H Request for Model, Serial Number, Country ISO code, Asset Number

//#pragma package(smart_init)
//---------------------------------------------------------------------------
#define POLYNOMIAL 0x08408
unsigned int GetCRC16(unsigned char* bufData, unsigned int sizeData)
{
	unsigned int CRC, i;
	unsigned char j;
	CRC = 0;
	for(i=0; i < sizeData; i++)
	{
		CRC ^= bufData[i];
		for(j=0; j < 8; j++)
		{
			if(CRC & 0x0001) {CRC >>= 1; CRC ^= POLYNOMIAL;}
			else CRC >>= 1;
		}
	}
	return CRC;
}
//---------------------------------------------------------------------------
/*unsigned int GetCRC16(char* bufData, unsigned int sizeData)
{	return GetCRC16((unsigned char*)bufData, sizeData);
}*/
//---------------------------------------------------------------------------
//Добавить нули к цифре
int addZero(int val,int cnt)
{
	for(int i=0;i<cnt;i++)
		val=val*10;
	return val;
}
//---------------------------------------------------------------------------
Validator::Validator()
{
	//Pay=0;
	cPort=new ComPort();
	bNominals=false; //Получили хоть 1 раз номиналы купюп

	pthread_mutex_init(&m_mutex, NULL);
}
//---------------------------------------------------------------------------
Validator::~Validator()
{
	stopPay();
	pthread_mutex_destroy(&m_mutex);
	delete cPort;
}
//---------------------------------------------------------------------------
//Загрузить настройки из ini файла
/*void Validator::LoadSetup()
{
	TIniFile* ini = new TIniFile();
	ini->Load("settings.ini");
	cPort->ComNumber=ini->ReadString("Validator","Port","0");
	cPort->BaudRate=ini->ReadLong("Validator","BaudRate","0");
	m_testmode=ini->ReadBool("Validator","TestMode","0");
	delete ini;
}*/
//---------------------------------------------------------------------------
bool Validator::OpenCOMPort(std::string ComNumber, int BaudRate)
{
	cPort->ComNumber = ComNumber;
	cPort->BaudRate = BaudRate;

	cPort->Close();

	if (cPort->Open(cPort->ComNumber))
	{
		if (!cPort->Setup(2))
			return false;

		if (!cPort->SetTimeout(500)) //50 это 5 секунд так как задаётся в 1/10 секунды
			return false;
	}
	else return false;
	return true;
}
//---------------------------------------------------------------------------
//Сравнить последние 2 байта с CRC данных
bool Validator::CheckOnCRC(unsigned char* lpBuffer,unsigned long nSize)
{
	if(nSize<2) return false;
	unsigned int CRC=GetCRC16(lpBuffer,nSize-2);
	unsigned int* pCRC=(unsigned int*)&lpBuffer[nSize-2];
	return (CRC==*pCRC);
}
//---------------------------------------------------------------------------
//req - Строка запроса без CRC
//ans - Ответ на команду
//Результата совпал CRC или нет
bool Validator::SendCommand(std::string req, std::string &ans)
{
	unsigned short crc=GetCRC16((unsigned char*)req.c_str(),req.size());
	req+=((char*)(&crc))[0]; req+=((char*)(&crc))[1];
	unsigned int BytesWritten=cPort->Write(req.c_str(),req.size());
	//Чтение ответа
	ans="";
	for(int i=0;i<255;i++)
	{
		Sleep(10);
		char ch;
		unsigned int BytesRead=cPort->Read(&ch,1);
		if (BytesRead>0) ans+=ch; else break;
	}
	//Проверка ответа на CRC
	if(ans.size()>=2)
	{
		crc=GetCRC16((unsigned char*)ans.c_str(),ans.size()-2);
		if(crc==((unsigned short*)(&ans.c_str()[ans.size()-2]))[0]) return true;
	}
	return false;
}
//---------------------------------------------------------------------------
//Инициализация переменных валидатора
bool Validator::Start()
{
	bool result=true;

	if(result) result=result && Reset(); //Перезагрузить валидатор
	if(result) result=result && getSerialNumber(); //Получить серийный номер валидатора
	if(result) result=result && getNominals(); //Получить номиналы прошитых купюр
	if(result) result=result && SetEnableBillTypes(true); //Разрешить приём всех прошитых купюр

	return result || m_testmode; //В тестовом режиме всегда всё OK
}
//---------------------------------------------------------------------------
//Перезагрузить валидатор (RESET 30H Command for Bill-to-Bill unit to self-reset)
bool Validator::Reset()
{
	std::string req,ans;
	req+=(char)0x02; req+=(char)0x03; req+=(char)0x06; req+=(char)0x30;
	return SendCommand(req,ans);
}
//---------------------------------------------------------------------------
//Получить статус валидатора (GET STATUS 31H Request for Bill-to-Bill unit set-up status)
bool Validator::GetStatus()
{
	std::string req,ans;
		req+=(char)0x02; req+=(char)0x03; req+=(char)0x06; req+=(char)0x31; //req+=(char)0x41; req+=(char)0xB3;
		bool r=SendCommand(req,ans);
	if(r && ans.length()>6) //Почему то всегда без данных
		{
			//Первые 3 байта показывают какие типы платижей разрешены
			//Следующие 3 байта указывают какой уровень защиты установлен
			//Следующие 3 По моему какие касеты готовы принимать деньги
		}
		return r;
}
//---------------------------------------------------------------------------
//Разрешить либо запретить приём заданных видов купюр
//enable - true разрешить всё, false запретить всё
bool Validator::SetEnableBillTypes(bool enable)
{
	std::string req,ans;
	if(enable)
		{	req+=(char)0x02; req+=(char)0x03; req+=(char)0x0C; req+=(char)0x34;
				req+=(char)0xFF; req+=(char)0xFF; req+=(char)0xFF; //24 бита
				req+=(char)0x00; req+=(char)0x00; req+=(char)0x00; //24 бита
		}else
		{	req+=(char)0x02; req+=(char)0x03; req+=(char)0x0C; req+=(char)0x34;
				req+=(char)0x00; req+=(char)0x00; req+=(char)0x00; //24 бита
				req+=(char)0x00; req+=(char)0x00; req+=(char)0x00; //24 бита
		}
		bool r=SendCommand(req,ans);
		return r;
}
//---------------------------------------------------------------------------
bool Validator::getSerialNumber()
{
	unsigned int BytesWritten=cPort->Write(ConIdent,sizeof(ConIdent));
	unsigned int BytesRead=cPort->Read(ReadAr,sizeof(ReadAr));
	if(!CheckOnCRC(ReadAr,BytesRead)) return false;
	//Нужный ли нам ответ
	if(BytesRead!=39)
	{
		Utility::logrotate("validator.log","Не удалось получить номер партии валидатора");
		return false;
	}
	//Выбираем № партии
	std::string str1((char*)&ReadAr[3],15);
	BatchNumber= Utility::trim(str1);

	std::string str2((char*)&ReadAr[18],12);
	SerialNumber= Utility::trim(str2);

	Sleep(100);
	return true;
}
//---------------------------------------------------------------------------
//Получить номиналыы купюр принимаемые валидатором
bool Validator::getNominals()
{
	unsigned char ConGetBT[6] = { 0x02,0x03,0x06,0x41,0x4F,0xD1 }; //GET BILL TABLE Какие купюры прошиты в валидаторе
	ConGetBT[5] = GetCRC16(ConGetBT, 5); //Считаю CRC

	bNominals=false;
	Sleep(100); //на всяк
	unsigned int BytesWritten=cPort->Write(ConGetBT,sizeof(ConGetBT));
	unsigned int BytesRead=cPort->Read(ReadAr,sizeof(ReadAr));
	if(!CheckOnCRC(ReadAr,BytesRead)) return bNominals;
	Sleep(100); //на всяк
	for(unsigned char i=0;i<23;i++)
	{
		bt[i].val=addZero(ReadAr[3+i*5],ReadAr[3+i*5+4]);
		//Код страны 3 буквы
		bt[i].country[0]=ReadAr[3+i*5+1];
		bt[i].country[1]=ReadAr[3+i*5+2];
		bt[i].country[2]=ReadAr[3+i*5+3];
		bt[i].val=addZero(ReadAr[3+i*5],ReadAr[3+i*5+4]);
	}
		bNominals=true;
	return bNominals;
}
//---------------------------------------------------------------------------
//Попытка приёма платежа, выполняется в цикле в отдельном потоке.
//state - текущее состояние валидатора (См. документацию)
//результат принятая сумма или 0 если ничего не принято
int Validator::getTHPay(int &state)
{
	state=0;
	int result = 0;
	if(!cPort->isOpen()){Sleep(1000); return result;}

	unsigned char buffer[255];
	memset(buffer, 0, sizeof(buffer));

	unsigned int BytesWritten=0;
	unsigned int BytesRead=0;

	BytesWritten=cPort->Write(ConReACK, sizeof(ConReACK)); //Ответ валидатору что всё нормально
	Sleep(10);

	BytesWritten=cPort->Write(ConStPoll, sizeof(ConStPoll)); //Перейти в режим получения платежей
	BytesRead=cPort->Read(buffer,sizeof(buffer));
	if(CheckOnCRC(buffer,BytesRead))
	{
		switch (buffer[3])
		{
		case 0x13://19 Ещё в режиме инициализации после выполнения команды RESET
			//Utility::logrotate("validator.log","(State)0x13 Инициализация");
			break;
		case 0x14: //20 Означает что находимся в режиме ожидания приёма платежа (IDLING – The state in which Bill-to-Bill is ready accept bills)
			//Utility::logrotate("validator.log","(State)0x14 В режиме ожидания купюры");
			break;
		case 0x15: //21 Производит сканирование купюры и определяется наминал (У меня 3 раза на купюру)
			state = 0x15;
			//Utility::logrotate("validator.log","(State)21 Прошла 1 из проверок");
			break;
		case 0x17: //23 Приём купюры в отсек если не получится положить то вернёт купюру обратно
			state = 0x17;
			//Utility::logrotate("validator.log","(State)23 Приём купюры!");
			break;
		case 0x19://25 DISABLED – The Bill-to-Bill unit has been disabled by the Controller and also the state in which Bill-to-Bill unit is after initialization.
			state = 0x19;
			//Utility::logrotate("validator.log","(State)25 отменён приём!!!");
			SetEnableBillTypes(true);
			break;
		case 0x1c: //28 Извлечена купюра
			//Utility::logrotate("validator.log","(State)28 Купюра извлечена");
			break;
		case 0x41: //41H Заполнился мешок под завязку...
			//Utility::logrotate("validator.log","(State)0x41 Заполнился мешок под завязку...");
			break;
		case 0x45: //69 Обнаружена попытка обмана "фальшивка".
		    //Utility::logrotate("validator.log","(State)69 Незнаю");
			break;
		case 0x81: //129 В какую касету попала купюра
			state = 0x81;
			//Utility::logrotate("validator.log","(State)129 Принята купюра");
			if (buffer[4]<23)
			{
				result = bt[buffer[4]].val; //В касете buffer[5]
				Utility::logrotate("validator.log", "(Pay)" + Utility::IntToStdStr(result));
			}
			break;
		default:
			Utility::logrotate("validator.log", "(State???)" + Utility::IntToStdStr(buffer[3]));
		}

		Utility::logrotate("validator.log", "buffer[3] = " + Utility::IntToStdStr(buffer[3]));
		Utility::logrotate("validator.log", "state = " + Utility::IntToStdStr(state));

	}
	Sleep(100);

	return result;
}
//---------------------------------------------------------------------------
//Завершение работы по приёму платежей
void Validator::endPay()
{
	SetEnableBillTypes(false);

	unsigned int BytesWritten,BytesRead;
	BytesWritten=cPort->Write(ConReACK , sizeof(ConReACK));

	BytesWritten=cPort->Write(ConStPoll , sizeof(ConStPoll));
	BytesRead=cPort->Read(ReadAr,sizeof(ReadAr));
}
//---------------------------------------------------------------------------
int Validator::GetState() //Если 0 то всё в порядке
{
	if(m_testmode) return 0;
	//if(! bNominals) getNominals();  //todo не подходит для проверки
	if(! bNominals) return 1;
	return 0;
}
//---------------------------------------------------------------------------
std::string Validator::GetStatusDescription(char StatusCode) //Описание статуса
{
	if (StatusCode == 0x00)
		return "OK";
	else
	if (StatusCode == 1)
		return "Не удалось получить номиналы купюр";
	else
		return "Error";
}
//---------------------------------------------------------------------------
bool Validator::startPay() //Стартуем поток
{
	if (!execute_thread) //Чтобы не запускать поток заново
	{
		int ret = pthread_create(&m_threadid, NULL, Validator::thread_func, (void*)this);
		if (ret == 0)
			execute_thread = true;
	}
	return execute_thread;
}
//---------------------------------------------------------------------------
bool Validator::stopPay() //Стопим поток с ожиданием его завершения
{
	if (execute_thread)
	{
		execute_thread = false;
		void* ret = NULL;
		pthread_join(m_threadid, &ret);
	}
	return !execute_thread;
}
//---------------------------------------------------------------------------
void* Validator::thread_func(void *pData)
{
	Validator* validator=(Validator*) pData;
	while (validator->execute_thread)
	{
		int pay,state;
		pay = validator->getTHPay(state);
		//Защищаем переменную мютексом
		if (pthread_mutex_lock(&validator->m_mutex) == 0)
		{
			validator->m_payState = state;
			pthread_mutex_unlock(&validator->m_mutex);
		}
		if (pay > 0) //Если произвели оплату то добавляем в список платежей
		{
			if (pthread_mutex_lock(&validator->m_mutex) == 0) //Защищаем список мютексом
			{
				validator->m_pays.add(pay);
				pthread_mutex_unlock(&validator->m_mutex);
			}
		}
	}
	validator->endPay();
	return NULL;
}
//---------------------------------------------------------------------------
//Получить значение из нокопившегося списка оплат, значение удаляется из списка
//Если что заплатили то результат больше нуля 
int Validator::getPay()
{
	int pay = 0;
	if (pthread_mutex_lock(&m_mutex) == 0) //Защищаем список мютексом
	{
		if (m_pays.count() > 0)
		{
			pay = m_pays.get(0);
			m_pays.del((unsigned int)0);
		}
		pthread_mutex_unlock(&m_mutex);
	}
	return pay;
}
//---------------------------------------------------------------------------
int Validator::getPayState()
{
	int result = 0;
	if (pthread_mutex_lock(&m_mutex) == 0) //Защищаем список мютексом
	{
		result = m_payState;
		pthread_mutex_unlock(&m_mutex);
	}
	return result;
}
//---------------------------------------------------------------------------