#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <arpa/inet.h>
#include <termios.h>
#include <poll.h>

//this is the only file needed to be included
//also link the powermon_lib.a together with your application
//the following extra linker command line options are needed:
//-lstdc++ -lbluetooth -ldbus-1
#include <powermon.h>


volatile bool connected = false;
volatile bool disconnected = false;
volatile bool ready = false;



static bool should_exit(void)
{
	pollfd pfd{};
	pfd.fd = STDIN_FILENO;
	pfd.events = POLLIN;

	const int32_t ret = poll(&pfd, 1, 0); // timeout = 0 → non-blocking
	if (ret > 0 && (pfd.revents & POLLIN)) 
		return true;

	return false;
}


int32_t main(int32_t argc, char** argv) 
{
	printf("\nPowerMon Connect Example. Thornwave Labs Inc.\n");
	printf("\nPress ENTER to exit\n\n");

	Powermon* const powermon = Powermon::createInstance();
	if (powermon == nullptr)
	{
		printf("\nCannot create the Powermon instance");
		return EXIT_FAILURE;
	}


	//Let's set our connect callback. This gets called when a connection is successfully established
	powermon->setOnConnectCallback([]() 
	{
		printf("\nDevice connected\n"); fflush(stdout);
		connected = true;
	});

    //... and our disconnect callback. This gets called when a device gets disconnected or a connection attempt fails.
	powermon->setOnDisconnectCallback([](Powermon::DisconnectReason reason) 
	{
		printf("\nDevice disconnected, reason: %u\n", reason); fflush(stdout);
		disconnected = true;
	});

	

	//Now we are ready to connect to a device
	//Here are the options:
	// - we can connect to a Bluetooth LE device (PowerMon or PowerMon-5S)
	// - we can connect to a WiFi or Ethernet device located in the same network - we need an IP address
	// - we can connect to a WiFi or Ethernet device located remotely - we need the access keys



	//WIFI / ETHERNET REMOTE CONNECTION

	//this is the URL needed to connect to a demo device
	//we can use functions provided by the Powermon class to decode this URL into the access keys required to connect
	const char* url = "https://applinks.thornwave.com/?n=DemoUnit&s=36129e86da98dda9&h=40&c=HzotUykUSvP/Ox0xdUpYVw%3D%3D&k=//////////////////////////////////////////8%3D";
	Powermon::DeviceIdentifier id;
	if (id.fromURL(url))
	{
		printf("\nURL decoded successfully");
		printf("\n  Device name: %s", id.name.c_str());
		printf("\n  Device model: %s", Powermon::getHardwareString(id.hardware_revision_bcd).c_str());
		printf("\n  Device serial number: %016lX", id.serial);

		printf("\n  Device channel ID: ");
		for(uint32_t i = 0; i < CHANNEL_ID_SIZE; i++)
			printf("%02X", id.access_key.channel_id[i]);

		printf("\n  Device encryption key: ");
		for(uint32_t i = 0; i < ENCRYPTION_KEY_SIZE; i++)
			printf("%02X", id.access_key.encryption_key[i]);

		//let's connect
		powermon->connectIp(id.access_key);
	}
	else
	{
		printf("\nThe URL provided is invalid\n");
		return EXIT_FAILURE;
	}
	

    
	//WIFI / ETHERNET LOCAL CONNECTION
	//struct in_addr ip_addr;
	//inet_pton(AF_INET, "192.168.1.230", &ip_addr);	//use the IP address of your device. You can get that from the PowerMon advertisments.
	//powermon->connectIp(ip_addr.s_addr);

	
	//BLE CONNECTION
	//BLE devices send the monitor data as a notification twice a second.
	//subscribe to the notification
	powermon->setOnMonitorDataCallback([](const Powermon::MonitorData &data)
	{
		printf("\nV1: %.3fV, V2: %.3fV, I: %.3fA, P: %.2fW, Coulombs: %.3fAh, Energy: %.3fWh, PS: %s", 
							data.voltage1, data.voltage2, data.current, data.power,
							data.coulomb_meter / 1000.0, data.energy_meter / 1000.0,
							Powermon::getPowerStatusString(data.power_status).c_str()
						);
		fflush(stdout);
	});

	//use the MAC address of your device. You can get that from the PowerMon advertisments.
	//const uint64_t ble_mac_address = (uint64_t)0xFBC46D6D5398;		//pay attention to big endian vs little endian
	//powermon->connectBle(ble_mac_address);


	printf("\nConnecting ... "); fflush(stdout);
	//wait to either connect or fail
	while(!should_exit() && !connected && !disconnected)
        usleep(10 * 1000);

	//as you noticed, the PowerMon API is asynchronous. This means, you make a request by calling a member function in 
	//the Powermon class and specifying a lambda as a parameter. That function returns immediately. The lambda will be 
	//called later when the response of the request arrives or the request times out.
	//For this reason, in this example we need to keep the thread alive, hence the volatile bool connected, disconnected and ready.
	//In your application, chances are that it's event driven and the Powermon library will fit nicely in that environment

	if (connected)
	{
		//this is our first request sent to the device
		//again, make abstraction of the ready flag. It's here because we need to keep this thread blocked until we get a response
		//It is highly recommended to start any connection with this request. Also keep the DeviceInfo structure because
		//you may need information about this device to make decisions about how to interpret the data
		ready = false;
		powermon->requestGetInfo([](uint16_t status, const Powermon::DeviceInfo& info) 
		{
			if (status == Powermon::RSP_SUCCESS) 
			{
				printf("\nDevice Information\n-----------------\n");

				printf("\nDevice name: %s", info.name.c_str());
				printf("\nFirmware version: %x.%02x", info.firmware_version_bcd >> 8, info.firmware_version_bcd & 0xFF);
				printf("\nHardware ID: %x.%x", info.hardware_revision_bcd >> 4, info.hardware_revision_bcd & 0xF);

				fflush(stdout);
			}
			else 
			{
				printf("\r\nError retrieving device information");
			}

			ready = true;
		});
		while(ready == false) usleep(50 * 1000);			//wait until we get a response


		while(!should_exit() && !disconnected)
		{
			//network devices dont't send monitor data as a notification. Instead, it must be requested.
			if (Powermon::hasNetwork(powermon->getLastDeviceInfo().hardware_revision_bcd))
			{
				powermon->requestGetMonitorData([](Powermon::ResponseCode response, const Powermon::MonitorData& data)
				{
					if (response == Powermon::ResponseCode::RSP_SUCCESS)
					{
						printf("\nV1: %.3fV, V2: %.3fV, I: %.3fA, P: %.2fW, Coulombs: %.3fAh, Energy: %.3fWh, PS: %s", 
							data.voltage1, data.voltage2, data.current, data.power,
							data.coulomb_meter / 1000.0, data.energy_meter / 1000.0,
							Powermon::getPowerStatusString(data.power_status).c_str()
						);
					}
					else
					{
						printf("\nFailed to get monitor data. Response code: %u", response);
					}

					fflush(stdout);
				});
			}

			//delay 2 seconds and check if we should exit every 10ms. 
			//This is done so we can respond quickly to the user request of terminating the program.
			for(uint32_t i = 0; i < 200; i++)
				if (!should_exit())
					usleep(10 * 1000);
				else
					break;
		}

		powermon->disconnect();

		//wait for the device to disconnect. If the connection failed when trying to establish it, disconnected will already be true
		while(!disconnected)
        	usleep(50 * 1000);
	}

    delete powermon;

	printf("\n");

    return EXIT_SUCCESS;
}
