//*******************************************************************************
//	SmartThings NodeMCU ESP32 Wifi Library
//
//	License
//	(C) Copyright 2017 Dan Ogorchock
//
//	History
//	2017-08-15  Dan Ogorchock  Created with the help of Joshua Spain
//  2017-09-05  Dan Ogorchock  Added automatic WiFi reconnect logic as ESP32 
//                             doesn't do this automatically currently
//  2018-01-01  Dan Ogorchock  Added WiFi.RSSI() data collection
//  2018-01-06  Dan Ogorchock  Simplified the MAC address printout to prevent confusion
//  2018-02-03  Dan Ogorchock  Support for Hubitat
//  2020-04-10  Dan Ogorchock  Improved network performance by disabling WiFi Sleep
//  2020-06-20  Dan Ogorchock  Add user selectable host name (repurposing the old shieldType variable)
//
//*******************************************************************************

#include "SmartThingsESP32WiFi.h"

namespace st
{
	int SmartThingsESP32WiFi::disconnectCounter = 0;

	//*******************************************************************************
	// SmartThingsESP32WiFi Constructor - Static IP
	//*******************************************************************************
	SmartThingsESP32WiFi::SmartThingsESP32WiFi(String ssid, String password, IPAddress localIP, IPAddress localGateway, IPAddress localSubnetMask, IPAddress localDNSServer, uint16_t serverPort, IPAddress hubIP, uint16_t hubPort, SmartThingsCallout_t *callout, String shieldType, bool enableDebug, int transmitInterval) :
		SmartThingsEthernet(localIP, localGateway, localSubnetMask, localDNSServer, serverPort, hubIP, hubPort, callout, shieldType, enableDebug, transmitInterval, false),
		st_server(serverPort)
	{
		ssid.toCharArray(st_ssid, sizeof(st_ssid));
		password.toCharArray(st_password, sizeof(st_password));
	}

	//*******************************************************************************
	// SmartThingsESP32WiFI Constructor - DHCP
	//*******************************************************************************
	SmartThingsESP32WiFi::SmartThingsESP32WiFi(String ssid, String password, uint16_t serverPort, IPAddress hubIP, uint16_t hubPort, SmartThingsCallout_t *callout, String shieldType, bool enableDebug, int transmitInterval) :
		SmartThingsEthernet(serverPort, hubIP, hubPort, callout, shieldType, enableDebug, transmitInterval, true),
		st_server(serverPort)
	{
		ssid.toCharArray(st_ssid, sizeof(st_ssid));
		password.toCharArray(st_password, sizeof(st_password));
	}

	//*******************************************************************************
	// SmartThingsESP32WiFI Constructor - DHCP
	//*******************************************************************************
	SmartThingsESP32WiFi::SmartThingsESP32WiFi(uint16_t serverPort, IPAddress hubIP, uint16_t hubPort, SmartThingsCallout_t *callout, String shieldType, bool enableDebug, int transmitInterval) :
		SmartThingsEthernet(serverPort, hubIP, hubPort, callout, shieldType, enableDebug, transmitInterval, true),
		st_server(serverPort)
	{
		st_preExistingConnection = true;
	}

	//*****************************************************************************
	//SmartThingsESP32WiFI::~SmartThingsESP32WiFI()
	//*****************************************************************************
	SmartThingsESP32WiFi::~SmartThingsESP32WiFi()
	{

	}

	//**************************************************************************************
	/// Event Handler for ESP32 WiFi Events (needed to implement reconnect logic for now...)
	//**************************************************************************************
	void SmartThingsESP32WiFi::WiFiEvent(WiFiEvent_t event)
	{
		Serial.printf("[WiFi-event] event: %d\n", event);

		switch (event) {
		case SYSTEM_EVENT_STA_GOT_IP:
			Serial.println("WiFi connected");
			Serial.println("IP address: ");
			Serial.println(WiFi.localIP());
			break;
		case SYSTEM_EVENT_STA_DISCONNECTED:
			Serial.println("WiFi lost connection.  Attempting to reconnect...");
			WiFi.reconnect();
			disconnectCounter++;
			if (disconnectCounter > 10) {
				Serial.println("We have recieved the STA_DISCONNECTED event over 10 times now.  Reboot...");
				ESP.restart();
			}
			break;
		case SYSTEM_EVENT_STA_START:
			Serial.println("ESP32 station start");
			break;
		case SYSTEM_EVENT_STA_CONNECTED:
			Serial.println("ESP32 station connected to AP");
			disconnectCounter = 0;
			break;
		}

	}

	//*******************************************************************************
	/// Initialize SmartThingsESP32WiFI Library
	//*******************************************************************************
	void SmartThingsESP32WiFi::init(void)
	{
		WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);

		// delete old config
		WiFi.disconnect(true);
		delay(1000);
		WiFi.onEvent(SmartThingsESP32WiFi::WiFiEvent);


		//Turn off Wirelss Access Point
		Serial.println(F("Disabling ESP32 WiFi Access Point"));
		Serial.println(F(""));
		WiFi.mode(WIFI_STA);
		//WiFi.setAutoReconnect(true);
		//WiFi.setAutoConnect(true);
		WiFi.setSleep(false);

		//get the MAC address
		String strMAC(WiFi.macAddress());
		strMAC.replace(":", "");

		//Set the hostname
		if (_shieldType == "ESP32Wifi") {
			String("ESP32-" + strMAC).toCharArray(st_devicename, sizeof(st_devicename));
		}
		else {
			_shieldType.toCharArray(st_devicename, sizeof(st_devicename));
		}
		Serial.print(F("hostName = "));
		Serial.println(st_devicename);

		bool result = WiFi.setHostname(st_devicename);
		Serial.print(F("setHostname returned "));
		Serial.println(result);

		if (st_DHCP == false)
		{
			WiFi.config(st_localIP, st_localGateway, st_localSubnetMask, st_localDNSServer);
		}

		if (!st_preExistingConnection) {
			Serial.println(F(""));
			Serial.println(F("Initializing ESP32 WiFi network.  Please be patient..."));
			//wait for ESP32 to be ready on startup
			delay(1000); //may not be necessary, but seems to help my test board start up cleanly

			// attempt to connect to WiFi network
			WiFi.begin(st_ssid, st_password);
			Serial.print(F("Attempting to connect to WPA SSID: "));
			Serial.println(st_ssid);
		}

		int count =0;
		while (WiFi.status() != WL_CONNECTED) {
			count++;
			Serial.print(F("."));
			delay(500);	// wait for connection:
			if (count > 10) {
				Serial.println(F("what is taking so long?"));
				count = 0;
			}
		}

		Serial.println();

		st_server.begin();

		Serial.println(F(""));
		Serial.println(F("Enter the following three lines of data into ST App on your phone!"));
		Serial.print(F("localIP = "));
		Serial.println(WiFi.localIP());
		Serial.print(F("serverPort = "));
		Serial.println(st_serverPort);
		Serial.print(F("MAC Address = "));
		Serial.println(strMAC);
		Serial.println(F(""));
		Serial.print(F("SSID = "));
		Serial.println(st_ssid);
		Serial.print(F("PASSWORD = "));
		Serial.println(st_password);
		Serial.print(F("hubIP = "));
		Serial.println(st_hubIP);
		Serial.print(F("hubPort = "));
		Serial.println(st_hubPort);
		Serial.print(F("RSSI = "));
		Serial.println(WiFi.RSSI());
		Serial.println(F(""));
		Serial.println(F("SmartThingsESP32WiFI: Intialized"));
		Serial.println(F(""));

		RSSIsendInterval = 5000;
		previousMillis = millis() - RSSIsendInterval;

	}

	//*****************************************************************************
	// Run SmartThingsESP32WiFI Library
	//*****************************************************************************
	void SmartThingsESP32WiFi::run(void)
	{
		String readString;
		String tempString;
		String strRSSI;

		if (WiFi.isConnected() == false)
		{
			if (_isDebugEnabled)
			{
				Serial.println(F("**********************************************************"));
				Serial.println(F("**** WiFi Disconnected.  ESP32 should auto-reconnect.  ***"));
				Serial.println(F("**********************************************************"));
			}
			//WiFi.reconnect();
			//init();
		}
		else
		{
			if (millis() - previousMillis > RSSIsendInterval)
			{

				previousMillis = millis();

				if (RSSIsendInterval < RSSI_TX_INTERVAL)
				{
					RSSIsendInterval = RSSIsendInterval + 1000;
				}

				strRSSI = String("rssi ") + String(WiFi.RSSI());
				send(strRSSI);

				if (_isDebugEnabled)
				{
					Serial.println(strRSSI);
				}
			}
		}

		WiFiClient client = st_server.available();
		if (client) {
			boolean currentLineIsBlank = true;
			while (client.connected()) {
				if (client.available()) {
					char c = client.read();
					//read char by char HTTP request
					if (readString.length() < 200) {
						//store characters to string
						readString += c;
					}
					else
					{
						if (_isDebugEnabled)
						{
							Serial.println(F(""));
							Serial.println(F("SmartThings.run() - Exceeded 200 character limit"));
							Serial.println(F(""));
						}
					}
					// if you've gotten to the end of the line (received a newline
					// character) and the line is blank, the http request has ended,
					// so you can send a reply
					if (c == '\n' && currentLineIsBlank) {
						//now output HTML data header
						tempString = readString.substring(readString.indexOf('/') + 1, readString.indexOf('?'));

						if (tempString.length() > 0) {
							client.println(F("HTTP/1.1 200 OK")); //send new page
							client.println();
						}
						else {
							client.println(F("HTTP/1.1 204 No Content"));
							client.println();
							client.println();
							if (_isDebugEnabled)
							{
								Serial.println(F("No Valid Data Received"));
							}
						}
						break;
					}
					if (c == '\n') {
						// you're starting a new line
						currentLineIsBlank = true;
					}
					else if (c != '\r') {
						// you've gotten a character on the current line
						currentLineIsBlank = false;
					}
				}
			}

			delay(1);
			//stopping client
			client.stop();

			//Handle the received data after cleaning up the network connection
			if (tempString.length() > 0) {
				if (_isDebugEnabled)
				{
					Serial.print(F("Handling request from ST. tempString = "));
					Serial.println(tempString);
				}
				//Pass the message to user's SmartThings callout function
				tempString.replace("%20", " ");  //Clean up for Hubitat
				_calloutFunction(tempString);
			}

			readString = "";
			tempString = "";
		}
	}

	//*******************************************************************************
	/// Send Message out over Ethernet to the Hub
	//*******************************************************************************
	void SmartThingsESP32WiFi::send(String message)
	{
		if (WiFi.isConnected() == false)
		{
			if (_isDebugEnabled)
			{
				Serial.println(F("**********************************************************"));
				Serial.println(F("**** WiFi Disconnected.  ESP32 should auto-reconnect.  ***"));
				Serial.println(F("**********************************************************"));
			}

			//WiFi.reconnect();
			//init();
		}

		//WiFiClient st_client;

		//Make sure the client is stopped, to free up socket for new connection
		st_client.stop();

		if (st_client.connect(st_hubIP, st_hubPort))
		{
			st_client.println(F("POST / HTTP/1.1"));
			st_client.print(F("HOST: "));
			st_client.print(st_hubIP);
			st_client.print(F(":"));
			st_client.println(st_hubPort);
			st_client.println(F("CONTENT-TYPE: text"));
            st_client.println(F("CONNECTION: CLOSE"));
			st_client.print(F("CONTENT-LENGTH: "));
			st_client.println(message.length());
			st_client.println();
			st_client.print(message);
		}
		else
		{
			//connection failed;
			if (_isDebugEnabled)
			{
				Serial.println(F("***********************************************************"));
				Serial.println(F("***** SmartThings.send() - Ethernet Connection Failed *****"));
				Serial.println(F("***********************************************************"));
				Serial.print(F("hubIP = "));
				Serial.print(st_hubIP);
				Serial.print(F(" "));
				Serial.print(F("hubPort = "));
				Serial.println(st_hubPort);

				Serial.println(F("***********************************************************"));
				Serial.println(F("**** WiFi Disconnected.  ESP32 should auto-reconnect.  ***"));
				Serial.println(F("***********************************************************"));
			}

			//WiFi.reconnect();
			//init();      //Re-Init connection to get things working again

			if (_isDebugEnabled)
			{
				Serial.println(F("***********************************************************"));
				Serial.println(F("******        Attempting to resend missed data      *******"));
				Serial.println(F("***********************************************************"));
			}


			st_client.flush();
			st_client.stop();
			if (st_client.connect(st_hubIP, st_hubPort))
			{
				st_client.println(F("POST / HTTP/1.1"));
				st_client.print(F("HOST: "));
				st_client.print(st_hubIP);
				st_client.print(F(":"));
				st_client.println(st_hubPort);
				st_client.println(F("CONTENT-TYPE: text"));
				st_client.print(F("CONTENT-LENGTH: "));
				st_client.println(message.length());
				st_client.println();
				st_client.println(message);
			}

		}

        // Wait for a response
        unsigned long timeout = millis();
        while(!st_client.available())
        {
            if(millis() - timeout > 1000)
            {
                Serial.println(F("Post request timed out\n"));
                st_client.stop();
                return;
            }
        }


		//if (_isDebugEnabled) { Serial.println(F("WiFi.send(): Reading for reply data "));}
		// read any data returned from the POST
        //while (st_client.connected()) {
        while (st_client.available() && st_client.connected()) {
			char c = st_client.read(); //gets byte from ethernet buffer
            /*if((int) c == 255)
            {
                if(_isDebugEnabled)
                    Serial.println(F("Breaking due to invalid value"));
                break;
            }*/
            if (_isDebugEnabled) { Serial.print(c); } //prints byte to serial monitor
		}

		delay(1);
		st_client.stop();
	}
}
