#include <cstdio>
#include <cstdint>

#include <unistd.h>

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

CComm *CreateIO()
{
	// -------------------------------------------------
	// Select the communication method with LE-940R.
	// 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.130, Control port number = 10101)
	CSocket *comm = new CSocket();
	CSocket::CONNECTION connetion = {"192.168.0.130", 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-940R
	// ------------------
	FRAME Res;
	LE9xxCommand *cmd = new LE9xxCommand(comm);

	// Invalid connection maintenance command transmission from LE-940R
	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 Output(LE9xxCommand *cmd)
{
	// -----------------
	// Setup for LE-940R
	// ------------------
	// Input output type
	char input[64] = "";
	LE940_TYPE type;
	printf("Enter the output type number.\n");
	printf(" %d : 32V range\n", LE940_TYPE_VOLTAGE_32V);
	printf(" %d : 4-20mA range\n", LE940_TYPE_CURRENT);
	printf("> ");
	scanf("%d%*[^\n]", &type);
	if ((type < 0) || (type >= LE940_TYPE_NUM))
	{
		printf("The input number is invalid.\n");
		return false;
	}

	float min, max, outIfOpen, outIfShort;
	char *unit;
	switch (type)
	{
	case LE940_TYPE_VOLTAGE_32V:
	case LE940_TYPE_VOLTAGE_32V_1:
		min = -32;
		max = 32;
		unit = "V";
		break;
	case LE940_TYPE_CURRENT:
	case LE940_TYPE_CURRENT_1:
		min = 0;
		max = 20;
		unit = "mA";
		break;
	}

	// Set the output value if DIN is short
	printf("Set the output value if DIN is short (range of %.0f%s to %.0f%s).\n", min, unit, max, unit);
	printf("> ");
	scanf("%f%*[^\n]", &outIfShort);
	if ((outIfShort < min) || (outIfShort > max))
	{
		printf("Input value is invalid.\n");
		return false;
	}

	// Set the output value if DIN is open
	printf("Set the output value if DIN is open (range of %.0f%s to %.0f%s).\n", min, unit, max, unit);
	printf("> ");
	scanf("%f%*[^\n]", &outIfOpen);
	if ((outIfOpen < min) || (outIfOpen > max))
	{
		printf("Input value is invalid.\n");
		return false;
	}

	// Acquires the current output value and state of DIN every 100 milliseconds during 10 seconds.
	FRAME Res;
	struct timespec start;
	clock_gettime(CLOCK_MONOTONIC_RAW, &start);
	for (;;)
	{
		struct timespec now;
		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
		if (now.tv_sec - start.tv_sec >= 10)
		{
			break;
		}

		// Get the current DIN state
		cmd->TransmitCommandWait(CMD_DIN_GET, 0x00, 0, NULL, &Res);
		if (Res.res != RES_OK)
		{
			printf("\nFailed to get current state of DIN. [0x%02X]\n", Res.res);
			break;
		}
		printf("DIN: %s\n", Res.data[0] == 0 ? "off" : "on");

		// Changes the output value according to the state of DIN
		uint32_t out = cmd->ConvertRealDataLE940(type, Res.data[0] == 0 ? outIfOpen : outIfShort);
		uint8_t vout[3] = {0};
		vout[0] = (uint8_t)type;
		vout[1] = (uint8_t)(out >> 8);
		vout[2] = (uint8_t)(out & 0xFF);
		cmd->TransmitCommandWait(CMD_DA_OUT_SET, 0x00, sizeof(vout), vout, &Res);
		if (Res.res != RES_OK)
		{
			printf("Analog output error. [0x%02X]\n", Res.res);
			break;
		}

		usleep(100000);
	}

	// Stop output
	uint8_t vout[3] = {0};
	vout[0] = (uint8_t)type;
	cmd->TransmitCommandWait(CMD_DA_OUT_SET, 0x00, sizeof(vout), vout, &Res);
	if (Res.res != RES_OK)
	{
		printf("Analog output error. [0x%02X]\n", Res.res);
		return false;
	}

	return true;
}

bool Disconnect(LE9xxCommand *cmd)
{
	// ----------------------
	// Disonnect from LE-940R
	// ----------------------
	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;

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

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

	if (!Output(cmd))
	{
		goto error_comm;
	}

	ret = 0;

error_cmd:
	Disconnect(cmd);

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

error_exit:
	delete comm;

	return ret;
}
