#include <stdio.h>
#include <unistd.h>
#include <string>

#include "../hubolib.h"
#include "../hubocfg.h" // Required for changing default I2C device.

using namespace HuboLib;
using namespace std;

/*
Compile and link: 
	g++ BatteryTest.cpp -L../ -lhubo -lpthread -lrt -o BatteryTest
Run:
	sudo ./BatteryTest
Purpose:
	This little app allows for charging/discharging a battery up/down to a given value while capturing the voltage at a given rate.
	The Hubo relays will be used for switching the power supply as well as the load. 
	Charging/dischargiong can be interrupted for a given time in order to measure the relaxation voltage (hence estimating capacity).

	# Discharging down to 2152 digits relaxation voltage (relaxation time is 900s)
	# AD channel: 				0
	# Sampling rate [ms]: 		1000ms
	# Relay: 					7 (128)
	# Duty count:  				3600s -> 60min
	# Relax count: 				900s -> 15min
	# Charge/Discharge: 		d
	# Digits after relaxation: 	2152
	./BatteryTest 0 1000 128 3600 900 d 2152 discharge_test.csv	

	# Charging up to 2710 digit relaxation voltage (relaxation time is 900s)
	# AD channel: 				0
	# Sampling rate [ms]: 		1000ms
	# Relay: 					6 (64)
	# Duty count:  				3600s -> 60min
	# Relax count: 				900s -> 15min
	# Charge/Discharge: 		c
	# Digits after relaxation: 	2710
	./BatteryTest 0 1000 64 3600 900 c 2710 charge_test.csv

	Note - in this example a voltage devider 1:8 was used in front of the Hubo AD-channel in order to measure voltages up to 20V.
*/

string 			GetDateTime			(char seperator, char timeSeperator);
bool 			AppendFile			(string sFile, string sText);
unsigned long 	GetADValueAndLog	(unsigned char adPin, string sFile);
string			Convert2String		(long value);
char 			g_buffer 			[1024];

int main(int argc, char* argv[])
{
	if (argc != 9)
	{
		printf ("Usage: \n  BatteryTest <AD pins> <sample rate [ms]> <DO pins> <duty count> <relax count> <charge/discharge {c, d}> <stop at AD> <filename>\n");
		printf ("  example: BatteryTest 0 1000 128 3600 900 d 2152 discharge_test.csv\n");
		return 0;
	}

	unsigned char 	adPin 			= 0; 		// 0..7
	long 			logSpeed 		= 1000;		// [ms]
	unsigned char 	doPins			= 0;		// 0..255
	long			dutyCount		= 1;		// 1..n
	long			relaxCount		= 1;		// 1..n
	char			chargeDischarge	= 'c';		// c, d
	unsigned long	stopAtADValue	= 0;		// 0..n
	string 			sFile			= "";		// log file name

	// 1 adPin
	if (sscanf (argv[1], "%hhu", &adPin) != 1 || adPin>7 )
	{
		printf ("AD Pin could not be determined!\n");
		return 0;
	}
	
	// 2 logSpeed
	if (sscanf (argv[2], "%ld", &logSpeed) != 1)
	{
		printf ("Logspeed could not be determined!\n");
		return 0;
	}
	
	// 3 doPins
	if (sscanf (argv[3], "%hhu", &doPins) != 1)
	{
		printf ("DO Pin(s) could not be determined!\n");
		return 0;
	}

	// 4 dutyCount
	if (sscanf (argv[4], "%ld", &dutyCount) != 1 || dutyCount<1)
	{
		printf ("Duty count could not be determined!\n");
		return 0;
	}
	
	// 5 relaxCount
	if (sscanf (argv[5], "%ld", &relaxCount) != 1 || relaxCount<1)
	{
		printf ("Relax count could not be determined!\n");
		return 0;
	}
	
	// 6 chargeDischarge
	if (sscanf (argv[6], "%c", &chargeDischarge) != 1 || ! (chargeDischarge=='c' || chargeDischarge=='d'))
	{
		printf ("Charge discahrge at could not be determined!\n");
		return 0;
	}
	
	// 7 stopAtADValue
	if (sscanf (argv[7], "%lu", &stopAtADValue) != 1)
	{
		printf ("Stop at AD value could not be determined!\n");
		return 0;
	}
	
	// 8 file name
	if (sscanf (argv[8], "%s", g_buffer) != 1)
	{
		printf ("File name could not be determined!\n");
		return 0;
	}
	sFile = g_buffer;
	
	// If required - set the I2C device to work with. The Raspberry Pi uses "/dev/i2c-1" which is default, the Banana Pi uses "/dev/i2c-0"
	#ifdef BPI
	    g_I2CConfig.m_sI2CDevice = "/dev/i2c-0";
	#endif
	
	// Initialize the library once in your program.
	if (!Initialize())
	{
		printf ("Error: Initialize\n");
		return 1;
	}

	// Log settings.
	sprintf (g_buffer, "%s %d %ld 0x%02X %ld %ld %c %ld %s\n", argv[0], (int) adPin, logSpeed, (int) doPins, dutyCount, relaxCount, chargeDischarge, stopAtADValue, sFile.c_str());
	string sLine = 	string(g_buffer) + "\n";
	AppendFile(sFile, sLine);
	printf ("%s", g_buffer);
	
	// For simplicity define all channel to be read from the ADC.
	unsigned short overSampling[MAX_MCP3x08_CHANNELS] = { 1, 1, 1, 1, 1, 1, 1, 1 };
	Set_MCP3x08_Oversampling (overSampling);
	
	// Wait as long until we have valid values in the buffers.
	Wait_For_MCP3x08_Buffered_Values();
	Wait_For_MCP23017_Buffered_Values();
	

	unsigned long 		adcCount 		= 0;
	bool     			bStop 			= false;
	unsigned long long 	relaxTime		= 0; // [ms]
	unsigned long long  dutyTime		= 0; // [ms]
	unsigned long long  adcDutySum	    = 0; // [digit]
	unsigned long long  adcDutyCount    = 0; // [count]
	
	while (1)
	{
		// Log short note on relaxing phase.
		sLine = "Relaxing";
		printf ("%s\n", sLine.c_str());
		AppendFile(sFile, sLine);
		Set_DO_Channels(0x00);

		for (int count=0; count<relaxCount; count++)
		{
			adcCount = GetADValueAndLog(adPin, sFile);
			usleep(1000L * logSpeed);
			relaxTime += logSpeed;
		}
		// At the end of a relaxing phase....
		if (chargeDischarge=='c')
		{
			// ... if charging check the voltage to be less than overcharge voltage level.
			if (adcCount>stopAtADValue)
			{
				sLine = "End of relaxing phase and voltage above limit.";
				printf ("%s\n", sLine.c_str());
				AppendFile(sFile, sLine);
				bStop = true;
				break;
			}
			else
				printf ("adcCount=%ld stopAtADValue=%ld\n", adcCount, stopAtADValue);
		}

		// Log short note on duty phase.
		sLine = chargeDischarge=='c' ? "Charging" : "Discharging";
		printf ("%s\n", sLine.c_str());
		AppendFile(sFile, sLine);
		Set_DO_Channels(doPins);
	
		for (int count=0; count<dutyCount; count++)
		{
			adcCount 	= GetADValueAndLog(adPin, sFile);
			
			// Check analog value and stop if trigger level is reached.
			if (chargeDischarge=='d')
			{
				// Currently actively discharging.
				if (adcCount<stopAtADValue)
				{
					sLine = "Passing lower limit while discharging.";
					printf ("%s\n", sLine.c_str());
					AppendFile(sFile, sLine);
					bStop = true;
					break;
				}
			}
			
			usleep(1000L * logSpeed);
			dutyTime += logSpeed;

			// Track average values of ADC count (hence average voltage).
			adcDutySum  += adcCount;
			adcDutyCount++;
		}
		// Trigger level reached?
		if (bStop)
			break;
	}
	
	// Clear any outputs.
	Set_DO_Channels(0x00);

	// Log for another "relax time" period.
	for (int count=0; count<relaxCount; count++)
	{
		adcCount = GetADValueAndLog(adPin, sFile);
		usleep(1000L * logSpeed);
		relaxTime += logSpeed;
	}

	// Print total relaxing time.
	sprintf (g_buffer, "Total relax time: %llds = %lfh\n", relaxTime/1000L, ((double)(relaxTime/1000L))/3600.0);
	printf ("%s", g_buffer);
	AppendFile(sFile, g_buffer);

	// Print total duty time.
	sprintf (g_buffer, "Total duty time: %llds = %lfh\n", dutyTime/1000L, ((double)(dutyTime/1000L))/3600.0);
	printf ("%s", g_buffer);
	AppendFile(sFile, g_buffer);

	// Print average adc duty value.
	if (adcDutyCount)
	{
		sprintf (g_buffer, "Average ADC duty value: %ld digit\n", (long)(adcDutySum / adcDutyCount));
		printf ("%s", g_buffer);
		AppendFile(sFile, g_buffer);
	}
	
	// More for beautiness than that it would still have any effect.
	Wait_For_MCP23017_Buffered_Values();
	
	// Free library resources.
	Uninitialize();
	
	return 0;
}

unsigned long GetADValueAndLog(unsigned char adPin, string sFile)
{
	string sLine = GetDateTime(' ', ':');
	sLine += " ";
	
	// Read channel and output its value.
	unsigned long adcCount = 0;
	double        volt = 0.0;
	if (Get_AI_Channel (adPin, adcCount, volt))
		sprintf (g_buffer, "%04ld", adcCount);
	else 
		sprintf (g_buffer, "????");
	sLine += g_buffer;

	sLine +="\n";
	printf ("%s", sLine.c_str());
	AppendFile(sFile, sLine);
	
	return adcCount;
}

string GetDateTime(char seperator, char timeSeperator)
{
	time_t t = time(0);   // get time now
    struct tm *pNow = localtime (&t);
	sprintf (g_buffer, "%02d.%02d.%d%c%02d%c%02d%c%02d", pNow->tm_mday, (pNow->tm_mon + 1), (pNow->tm_year + 1900), seperator, pNow->tm_hour, timeSeperator, pNow->tm_min, timeSeperator, pNow->tm_sec);
	// printf ("Time = %s\n", g_buffer);
	return string(g_buffer);
}

bool AppendFile(string sFile, string sText)
{
	FILE* pFile = fopen (sFile.c_str(), "a+");
	if (!pFile)
		return false;
	bool bResult = fputs (sText.c_str(), pFile) > 0;
	fflush (pFile);
	fclose(pFile);
	return bResult;
}

string Convert2String(long value)
{
	if (sprintf(g_buffer, "%ld", value) != 1)
		return string("");
	
	string s (g_buffer);
	return s;
}
