
using namespace std;

class lights
{
	public:
		lights();
		void readLightModes();  // read light modes from database
		void calculateLightSchedules();  // calculate light modes
		void monitorLightSchedules();  // continually monitor light modes
		void addLightMode(short pinID);  // add new light mode
		void deleteLightMode(short ID);  // delete light mode
		void enableLightMode(short pinID, short val);  // enable/disable light mode
		void adjustLightLevel(short ID, short value);  // adjust levels for each channel
		void lightLevelSliderStopped(short ID, short value);  // save light level when slider stops moving
		void changeTime(short ID, int time);  // change light mode time
		void frontendResponse();  // needed to send response back to front end 
	private:
        short modeID[500] = {};  // track mode ID
		short modePinID[500] = {};  // track mode pinID
		unsigned long long modeEnd[500] = {};  // track mode time in milliseconds
		short modeEndLevel[500] = {};  // track if mode level
		short modeEnabled[500] = {};  // track if mode is enabled
		short prevFadeValue[500] = {};  // tracks when the fade value changes so we only send command to pca9685 when needed
		short channelPinID[48] = {};  // track pinID for each channel - up to 48 channels
		short totalModesPerChannel[48] = {};  // track total number of modes for each channel - up to 48 channels


		// short* modeID = new short[500];  // track mode ID
		// short* modePinID = new short[500];  // track mode pinID
		// unsigned long long* modeEnd = new unsigned long long[500];  // track mode time in milliseconds
		// short* modeEndLevel = new short[500];  // track if mode level
		// short* modeEnabled = new short[500];  // track if mode is enabled
		// short* prevFadeValue = new short[500];  // tracks when the fade value changes so we only send command to pca9685 when needed
		// short* channelPinID = new short[48];  // track pinID for each channel - up to 48 channels
		// short* totalModesPerChannel = new short[48];  // track total number of modes for each channel - up to 48 channels

		short totalLightModes = 0;  // track total number of light modes
		short totalChannels = 0;  // track total number of channels
		short testFade = 0;  // track if we are testing the fade
		short testFadeStep = 0;
		unsigned long long startedTest = 0;
		bool pauseFading = 0;  // pause fading during updates
};
lights::lights(){}

void lights::readLightModes() {  // read light modes from database
	if (startupComplete == 0) {
		std::cout << " " << std::endl;
		std::cout << "readLightModes--------------------------------" << std::endl;
	}
	
	pauseFading = 1;

    std::fill_n(modeID, 500, 0);  // reset array
    std::fill_n(modePinID, 500, 0);  // reset array
    std::fill_n(modeEnd, 500, 0);  // reset array
    std::fill_n(modeEndLevel, 500, 0);  // reset array
    std::fill_n(modeEnabled, 500, 0);  // reset array
    std::fill_n(channelPinID, 48, 0);  // reset array
    std::fill_n(totalModesPerChannel, 48, 0);  // reset array

    MYSQL_RES *res;
    MYSQL_ROW row;

	std::string query = "SELECT pinID FROM lightModes GROUP BY pinID";  // get pinIDs from modes so we know how many channels are used
    connectMysql();
	mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

	totalChannels = 0;
    while ((row = mysql_fetch_row(res)) != NULL)
    {
		channelPinID[totalChannels] = atoi(row[0]);  // track pinID for each channel
		totalChannels++;  // track total number of channels
	}
    mysql_free_result(res);

	query = "SELECT modeID, pinID, start, startLevel, enabled FROM lightModes ORDER BY pinID ASC, start ASC";  // get modes
	mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

	totalLightModes = 0;
    while ((row = mysql_fetch_row(res)) != NULL)
    {
		modeID[totalLightModes] = atoi(row[0]);
		modePinID[totalLightModes] = atoi(row[1]);
		modeEnd[totalLightModes] = atoi(row[2]) * 1000;  // convert from seconds to milliseconds
		modeEndLevel[totalLightModes] = atoi(row[3]);
		modeEnabled[totalLightModes] = atoi(row[4]);
		prevFadeValue[totalLightModes] = 0;
		totalLightModes++;
	}
    mysql_free_result(res);
    mysql_disconnect();

	// count number of modes for each channel or pinID
	for(short c=0; c < totalChannels; c++)  // go through each channel
	{
		short channelMode = 0;
		for(short a=0; a < totalLightModes; a++)  // go through each mode
		{
			if (channelPinID[c] == modePinID[a])  // check if mode is for this channel
			{
				channelMode++;  // count modes for channel
			}
		}
		totalModesPerChannel[c] = channelMode;  // store and go to next
	}
	
	if (startupComplete == 0) {
		std::cout << "totalLightModes     - " << totalLightModes << std::endl;
		std::cout << "totalChannels       - " << totalChannels << std::endl;
		
		std::cout << "channelPinID   ----- ";
		for(short a=0; a < totalChannels; a++) {std::cout << channelPinID[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "totalModesPerChannel ";
		for(short a=0; a < totalChannels; a++) {std::cout << totalModesPerChannel[a] << ", ";}
		std::cout << "" << std::endl;
		
		std::cout << "modeID   ------------ ";
		for(short a=0; a < totalLightModes; a++) {std::cout << modeID[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "modePinID ----------- ";
		for(short a=0; a < totalLightModes; a++) {std::cout << modePinID[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "modeEnd ------------- ";
		for(short a=0; a < totalLightModes; a++) {std::cout << modeEnd[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "modeEndLevel -------- ";
		for(short a=0; a < totalLightModes; a++) {std::cout << modeEndLevel[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "modeEnabled---------- ";
		for(short a=0; a < totalLightModes; a++) {std::cout << modeEnabled[a] << ", ";}
		std::cout << "" << std::endl;

		std::cout << "" << endl;
	}

	// add midnight milliseconds to each mode time
	for(short a=0; a < totalLightModes; a++)  // go through each light mode
	{
		modeEnd[a] = modeEnd[a] + midnightMillis;  // convert start millisconds to timestamp
	}

	calculateLightSchedules();

    // buffer[0] = 0x00; 
    // buffer[1] = 0xEF; 


	// write(pca9685I2Cfile[driverLoc], buffer, length) 

	// pca9685.adjustPinLevel(115, 95);  // adjust pca9685 pin
	// int wake 	= settings & 0xEF;									// Set sleep bit to 0
	
    // char buffer[2];

    // buffer[0] = 0x00; 
    // buffer[1] = 0xEF; 

    // write(pca9685I2Cfile[0], buffer, 2); 

}

void lights::calculateLightSchedules() {  // calculate light modes
    // std::cout << " " << std::endl;
    // std::cout << "calculateLightSchedules -------------------------------- " << std::endl;

   	// std::cout << "midnightMillis         - " << midnightMillis << std::endl;

	// if (testFade == 1)
	// {

	// }
	// else 
	// {
		short arrayLoc = -1;  // used to track which array location to check
		for(short c=0; c < totalChannels; c++)  // go through each channel
		{
			arrayLoc = (arrayLoc + totalModesPerChannel[c]);  // add up total modes in channel as we search
			// std::cout << "arrayLoc                - " << arrayLoc << std::endl;
			// std::cout << "modeEnd[arrayLoc]       - " << modeEnd[arrayLoc] << std::endl;
			if ((currentMillisTime > modeEnd[arrayLoc]))  // look at last mode and check if day is over
			{
				for(short a=arrayLoc-totalModesPerChannel[c]+1; a <= arrayLoc; a++)  // go through each light mode and add 24 hours
				{
					// std::cout << "add day " << std::endl;
					modeEnd[a] = modeEnd[a] + 86400000;  // add 24 hours

					if (modeEnabled[a] == 0)  // if mode isn't enabled add 5 years to time so it never runs, more efficient to do this than have monitorLightSchedules run through modes
					{
						modeEnd[a] = modeEnd[a] + 157680000000;  // add 5 years
					}
				}
			}
		}
	// }

    // std::cout << "modeEnd ------------- " << std::endl;
    // for(short a=0; a < totalLightModes; a++) {
	// 	std::cout << "------ " << modeEnd[a] << " -- " << modePinID[a] << std::endl;
	// }

    // std::cout << " " << std::endl;
    // std::cout << " " << std::endl;

	pauseFading = 0;
	monitorLightSchedules();
}

void lights::monitorLightSchedules()  // continually monitor light modes
{
	if ((startupComplete == 1) && (pauseFading == 0)) 
	{
		// TEST FADE CODE BELOW, WORKS BUT ALL CHANNELS START AT SAME TIME
		// if (testFade == 1)
		// {
		// 	startedTest = currentMillisTime;
		// 	testFade = 2;
		// }
		// else if (testFade == 2)
		// {
		// 	for(short c=0; c < totalChannels; c++)  // go through each channel
		// 	{
		// 		for(short a=0; a < totalLightModes; a++)  // go through all light modes
		// 		{
		// 			if ((channelPinID[c] == modePinID[a]) && (modeEnabled[a] == 1))  // find any modes matching the channel we are searching
		// 			{
		// 				// std::cout << "currentMillisTime = " << currentMillisTime << std::endl;
		// 				short v = 0;  // led value
		// 				float f = 0;  // fade level
		// 				int e = 0;  // elapsed time
		// 				int d = 0;  // fad duration
						
		// 				e = currentMillisTime - startedTest;  // calculate time remaining
		// 				std::cout << "monitorLightSchedules = " << a << " - " << c << std::endl;
		// 				std::cout << "testFadeStep = " << testFadeStep << std::endl;

		// 				d = ((modeEnd[a+1+testFadeStep] - modeEnd[a+testFadeStep]) / 60);  // calculate fade duration
		// 				f = float(e) / float(d);  // calculate fade level
		// 				v = modeEndLevel[a+testFadeStep] - ((modeEndLevel[a+testFadeStep] - modeEndLevel[a+1+testFadeStep]) * f);  // calculate fade value
		// 				std::cout << "d = " << d << std::endl;
		// 				std::cout << "e = " << e << std::endl;
		// 				std::cout << "f = " << f << std::endl;
		// 				std::cout << "v = " << v << std::endl;

		// 				if ((e < d))  // fade to this mode if current time is before mode ends
		// 				{
		// 					if ((prevFadeValue[a] != v) && (v >= 0) && (v <= 4095)) {  // check if value changed since we updated pca9685
		// 						pca9685.adjustPinLevel(modePinID[a], v);  // adjust pca9685 pin
		// 						// health = health + 50;
		// 						std::cout << "UPDATE VALUE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - " << modePinID[a] << std::endl;
		// 						prevFadeValue[a] = v;  // store value so we don't update until needed
		// 					}
		// 				}
		// 				else if (testFadeStep == totalModesPerChannel[c]-1)  // test fade is over
		// 				{
		// 					testFadeStep = 0;
		// 					testFade = 0;
		// 					calculateLightSchedules();
		// 				}
		// 				else
		// 				{
		// 					testFadeStep++;
		// 					startedTest = currentMillisTime;
		// 				}
		// 				break;  // exit loop as we found the mode to fade, go to next channel
		// 			}
		// 		}
		// 	}

		// }
		// else 
		// {
			short arrayLoc = -1;  // used to check last mode of day so we know when the day ends
			for(short c=0; c < totalChannels; c++)  // go through each channel
			{
				arrayLoc = (arrayLoc + totalModesPerChannel[c]);  // add up modes per channel so we know the last mode for this channel array location
				if ((currentMillisTime > modeEnd[arrayLoc]))  // check if day is over
				{
					// std::cout << "FADE OVER !!!!!!!!!!!!!!!!!!!!! - " << c << std::endl;
					pauseFading = 1;
					calculateLightSchedules();
				}

				short modeNumber = 0;  // track which mode number is being faded
				for(short a=0; a < totalLightModes; a++)  // go through all light modes
				{
					if ((channelPinID[c] == modePinID[a]) && (modeEnabled[a] == 1))  // find any modes matching the channel we are searching
					{
						if ((currentMillisTime < modeEnd[a]))  // fade to this mode if current time is before mode ends
						{
							// std::cout << "monitorLightSchedules = " << a << " - " << c << std::endl;
							// std::cout << "currentMillisTime = " << currentMillisTime << std::endl;
							short v = 0;  // led value
							float f = 0;  // fade level
							int e = 0;  // elapsed time
							int d = 0;  // fad duration
							short prevMode = a - 1;  // get previous mode array location

							// std::cout << "prevModeA   = " << prevMode << std::endl;
							if (modeNumber == 0) {  // if we are fading to the first mode for channel we need to get the last mode data to fade
								prevMode = arrayLoc;  // last mode for channel
								d = modeEnd[a] - (modeEnd[prevMode] - 86400000);  // calculate fade duration
							}
							else {  // fading after the first mode
								d = modeEnd[a] - modeEnd[prevMode];  // calculate fade duration
							}
							// std::cout << "prevModeB     = " << prevMode << std::endl;
							e = modeEnd[a] - currentMillisTime;  // calculate time remaining
							f = float(e) / float(d);  // calculate fade level
							v = modeEndLevel[a] - ((modeEndLevel[a] - modeEndLevel[prevMode]) * f);  // calculate fade value
							// std::cout << "e = " << e << std::endl;
							// std::cout << "d = " << d << std::endl;
							// std::cout << "f = " << f << std::endl;
							// std::cout << "v = " << v << std::endl;

							if ((prevFadeValue[a] != v) && (v >= 0) && (v <= 4095)) {  // check if value changed since we updated pca9685
								pca9685.adjustPinLevel(modePinID[a], v);  // adjust pca9685 pin
								// health = health + 50;
								// std::cout << "UPDATE VALUE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - " << modePinID[a] << std::endl;
								prevFadeValue[a] = v;  // store value so we don't update until needed
							}
							break;  // exit loop as we found the mode to fade, go to next channel
						}
						modeNumber++;  // count modes for this channel as we search
					}
				}
				// std::cout << " " << std::endl;
			}
		// }
	}
}

void lights::adjustLightLevel(short ID, short value) {  // adjust levels for each channel
    // std::cout << "mode  - " << mode << std::endl;
    // std::cout << "pin   - " << pin << std::endl;
    // std::cout << "value - " << value << std::endl;
	int arrayLoc = 0;
	for (short a=0; a < totalLightModes; a++)  // find channel number based on pin number
	{
		if (modeID[a] == ID) {break;}
		else {arrayLoc++;}
	}
		
	modeEndLevel[arrayLoc] = value;  // store new value
	pca9685.adjustPinLevel(modePinID[arrayLoc], value);  // adjust pca9685 pin
}

void lights::lightLevelSliderStopped(short ID, short value) {  // save light level when slider stops moving
    // std::cout << "lightLevelSliderStopped  - " << endl;
	adjustLightLevel(ID, value);
	
    connectMysql();
    std::string query = "UPDATE lightModes SET startLevel = '" + to_string(value) + "' WHERE modeID = '" + to_string(ID) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect();
}

void lights::changeTime(short ID, int time) {  // change light mode time
    // std::cout << "changeTime  - " << endl;
	pauseFading = 1;
	int arrayLoc = 0;
	for (short a=0; a < totalLightModes; a++)  // find channel number based on pin number
	{
		if (modeID[a] == ID) {break;}
		else {arrayLoc++;}
	}
		
    connectMysql();
    std::string query = "UPDATE lightModes SET start = '" + to_string(time) + "' WHERE modeID = '" + to_string(ID) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect();

	readLightModes();
}

void lights::deleteLightMode(short ID) {  // delete light mode
    // std::cout << "deleteLightMode  - " << endl;
	pauseFading = 1;
    connectMysql();
    std::string query = "DELETE FROM lightModes WHERE modeID = '" + to_string(ID) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect(); 

	readLightModes();
    // std::cout << "totalLightModes    - " << totalLightModes << std::endl;
}

void lights::addLightMode(short pinID) {  // add new light mode
    // std::cout << "addLightMode  - " << endl;
	pauseFading = 1;
	connectMysql();
	std::string query = "INSERT INTO lightModes(pinID) VALUES('" + to_string(pinID) + "')";
	mysql_query(conn, query.c_str());  // run query
	mysql_disconnect(); 

	readLightModes();
}

void lights::enableLightMode(short pinID, short val) {  // enable/disable light mode
	pauseFading = 1;
    connectMysql();
	std::string query = "UPDATE lightModes SET enabled = '" + to_string(val) + "' WHERE pinID = '" + to_string(pinID) + "'";
	mysql_query(conn, query.c_str());  // run query
    mysql_disconnect(); 
	readLightModes();
}

void lights::frontendResponse() {  // needed to send response back to front end 
	outgoingDataString = "{\"li\":[";
	outgoingDataString += "{\"id\":\"";
	outgoingDataString += "0";
	outgoingDataString += "\"}";
	outgoingDataString += "]}";
}

lights lights;

