#include <cstdio>
#include <cstdint>

#include "../common/CSerial.h"
#include "../common/CSocket.h"
#include "../common/LE9xxCommand.h"

#define AI_NUM 5

CComm *CreateIO()
{
	// -------------------------------------------------
	// Select the communication method with LE-910R.
	// For the USB interface, open the virtual COM port.
	// For the WiFi interfaces, open a TCP socket.
	// -------------------------------------------------
#if 1
	// Open COM port
	CSerial *comm = new CSerial();
	char device[] = "/dev/ttyUSB0";
	if (!comm->Open((void *)device))
	{
		printf("Interface open error.\n");
		delete comm;
		return nullptr;
	}
#else
	// Open TCP socket
	// (IP Address = 192.168.0.110, Control port number = 10101)
	CSocket *comm = new CSocket();
	CSocket::CONNECTION connetion = {"192.168.0.110", 10101};
	if (!comm->Open((void *)&connetion))
	{
		printf("Interface open error.\n");
		delete comm;
		return nullptr;
	}
#endif

	return comm;
}

LE9xxCommand *Connect(CComm *comm)
{
	// ------------------
	// Connect to LE-910R
	// ------------------
	FRAME Res;
	LE9xxCommand *cmd = new LE9xxCommand(comm);

	// Invalid connection maintenance command transmission from LE-910R
	cmd->TransmitCommandWait(CMD_CONNECT, 0x20, 0, NULL, &Res);
	if ((Res.res != RES_OK) && (Res.res != RES_ALREADY_CONNECTED))
	{
		printf("Connection error. [0x%02X]\n", Res.res);
		delete cmd;
		return nullptr;
	}
	printf("Connection OK.\n");

	return cmd;
}

bool Setup(LE9xxCommand *cmd, LE910_RANGE (&range_cfg)[AI_NUM])
{
	// -----------------
	// Setup for LE-910R
	// ------------------
	// Thermocouple measurement setting
	// Ch = AI5, Type = K
	// Option = Internal compensation & Enable disconnection detection(output=0x800000)
	FRAME Res;
	uint8_t tc_cfg[3] = {0x10, 0, 0x03};
	cmd->TransmitCommandWait(CMD_TEMP_SET, 0x00, sizeof(tc_cfg), tc_cfg, &Res);
	if (Res.res != RES_OK)
	{
		printf("Thermocouple measurement setting error. [0x%02X]\n", Res.res);
		return false;
	}

	// AD converter conversion speed setting ( SPS=10sps )
	uint8_t sps = 0;
	cmd->TransmitCommandWait(CMD_AD_SPS_SET, 0x00, 1, &sps, &Res);
	if (Res.res != RES_OK)
	{
		printf("AD converter conversion speed setting error. [0x%02X]\n", Res.res);
		return false;
	}

	// Analog input range setting
	for (int i = 0; i < AI_NUM; i++)
	{
		uint8_t range[2];
		range[0] = 0x01 << i;	 // Ch mask
		range[1] = range_cfg[i]; // Range
		cmd->TransmitCommandWait(CMD_AD_RANGE_SET, 0x00, sizeof(range), range, &Res);
		if (Res.res != RES_OK)
		{
			printf("Analog input range setting error.(AI%d) [0x%02X]\n", i + 1, Res.res);
			return false;
		}
	}

	// Analog transfer cycle setting ( 1 second cycle )
	uint8_t cycle = 1;
	cmd->TransmitCommandWait(CMD_AD_CYCLE_SET, 0x00, 1, &cycle, &Res);
	if (Res.res != RES_OK)
	{
		printf("Analog transfer cycle setting error. [0x%02X]\n", Res.res);
		return false;
	}

	return true;
}

bool Start(LE9xxCommand *cmd)
{
	// ------------------------
	// Start analog measurement
	// ------------------------
	// Start measuring analog data to PC
	FRAME Res;
	uint8_t target = 0x01;
	cmd->TransmitCommandWait(CMD_AD_RUN, 0x00, 1, &target, &Res);
	if (Res.res != RES_OK)
	{
		printf("Start analog measurement error. [0x%02X]\n", Res.res);
		return false;
	}

	// Waiting for analog measurement start notification
	cmd->ReceiveCommandWait(CMD_AD_RUN_RESULT, &Res);
	if (Res.data[0] != target)
	{
		printf("Measurement did not start.\n");
		return false;
	}

	return true;
}

void Measure(LE9xxCommand *cmd, LE910_RANGE (&range_cfg)[AI_NUM])
{
	// -------------------------------
	// Acquire and convert analog data
	// -------------------------------
	// Measurement data is sent every second from LE-910R.
	// The measurement is performed 10 times.
	FRAME Res;
	uint32_t count = 0;
	while (count++ < 10)
	{
		// Waiting for a Analog measurement data transmission command
		cmd->ReceiveCommandWait(CMD_AD_TRANSFER_DATA, &Res);

		uint32_t seqno, year, mon, mday, hour, min, sec, msec;
		// Sequence number
		seqno = (uint32_t)Res.data[0] << 24;
		seqno |= (uint32_t)Res.data[1] << 16;
		seqno |= (uint32_t)Res.data[2] << 8;
		seqno |= (uint32_t)Res.data[3];
		printf("No.%2d,", seqno);
		// Date Time
		year = (uint32_t)Res.data[4] + 2000;
		mon = (uint32_t)Res.data[5];
		mday = (uint32_t)Res.data[6];
		hour = (uint32_t)Res.data[7];
		min = (uint32_t)Res.data[8];
		sec = (uint32_t)Res.data[9];
		msec = (uint32_t)Res.data[10];
		printf("%4d/%d/%d,%2d:%02d:%02d.%02d", year, mon, mday, hour, min, sec, msec);

		// Conversion of analog data
		for (int ch = 0; ch < AI_NUM; ch++)
		{
			uint32_t ad;
			ad = (uint32_t)Res.data[11 + ch * 3] << 16;
			ad |= (uint32_t)Res.data[12 + ch * 3] << 8;
			ad |= (uint32_t)Res.data[13 + ch * 3];
			float val = cmd->ConvertAnalogDataFromLE910(range_cfg[ch], ad);

			printf(",AI%d:", ch + 1);
			switch (range_cfg[ch])
			{
			case LE910_RANGE_100mV:
				printf("%.3f[mV]", val);
				break;
			case LE910_RANGE_1V:
				printf("%.5f[V]", val);
				break;
			case LE910_RANGE_10V:
			case LE910_RANGE_30V:
				printf("%.4f[V]", val);
				break;
			case LE910_RANGE_I_250ohm:
			case LE910_RANGE_I_50ohm:
				printf("%.4f[mA]", val);
				break;
			case LE910_RANGE_TC:
				if ((val == 0x800000) || (val == 0x7FFFFF))
					printf("BO");
				else
					printf("%.1f[degC]", val);
				break;
			}
		}
		printf("\n");
	}
}

bool Stop(LE9xxCommand *cmd)
{
	// -----------------------
	// Stop analog measurement
	// -----------------------
	FRAME Res;
	uint8_t target = 0x01;
	cmd->TransmitCommandWait(CMD_AD_STOP, 0x00, 1, &target, &Res);
	if (Res.res != RES_OK)
	{
		printf("Stop analog measurement error. [0x%02X]\n", Res.res);
		return false;
	}

	// Waiting for analog measurement stop notification
	cmd->ReceiveCommandWait(CMD_AD_STOP_RESULT, &Res);
	if (Res.data[0] != target)
	{
		printf("Measurement did not stop.\n");
		return false;
	}

	return true;
}

bool Disconnect(LE9xxCommand *cmd)
{
	// ----------------------
	// Disonnect from LE-910R
	// ----------------------
	FRAME Res;
	cmd->TransmitCommandWait(CMD_DISCONNECT, 0x00, 0, NULL, &Res);
	if (Res.res != RES_OK)
	{
		printf("Disconnection error. [0x%02X]\n", Res.res);
		return false;
	}
	printf("Disconnection OK.\n");

	return true;
}

int main()
{
	int ret = -1;

	// AI1 = 100mV, AI2 = 10V, AI3 = 30V, AI4 = 4-20mA, AI5 = Thermocouple
	LE910_RANGE range_cfg[AI_NUM] = {LE910_RANGE_100mV, LE910_RANGE_10V, LE910_RANGE_30V, LE910_RANGE_I_250ohm, LE910_RANGE_TC};

	CComm *comm = CreateIO();
	if (!comm)
	{
		return -1;
	}

	LE9xxCommand *cmd = Connect(comm);
	if (!cmd)
	{
		goto error_comm;
	}

	if (!Setup(cmd, range_cfg))
	{
		goto error_cmd;
	}


	if (!Start(cmd))
	{
		goto error_cmd;
	}

	Measure(cmd, range_cfg);

	if (!Stop(cmd))
	{
		goto error_cmd;
	}

	ret = 0;

error_cmd:
	Disconnect(cmd);

error_comm:
	comm->Close();
	delete cmd;

error_exit:
	delete comm;

	return ret;
}
