// Arduino build process info: https://github.com/arduino/Arduino/wiki/Build-Process

#include "WebOTA.h"
#include "Version.h"
#include <Arduino.h>
#include "config.h"
#include <WiFiClient.h>

#ifdef ESP32
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <WiFi.h>

#include "config.h"
#include "osd.h"
#include "webcontent.h"
#include "sdFormat.h"
#include <SdFat.h>
#include <sdios.h>
#include <Wire.h>
#include <WireSlave.h>
static const char TAG[] = __FILE__;
WebServer OTAServer(9999);
#endif
WebOTA webota;
extern Sd2Card card;
extern uint32_t cardSizeBlocks;
extern uint32_t cardCapacityMB;
extern bool isSDInit;
extern bool nrst_state;
extern bool masterboot;
#ifdef ESP8266
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>

ESP8266WebServer OTAServer(9999);
#endif
extern struct osd_display_t osd_display;
extern t_osd_info  osd_info;
const char* PARAM_INPUT_1 = "state";
int WebOTA::init(const unsigned int port, const char *path) {
	this->port = port;
	this->path = path;

	// Only run this once
	if (this->init_has_run) {
		return 0;
	}

	add_http_routes(&OTAServer, path);
	OTAServer.begin(port);

	Serial.printf("WebOTA url   : http://%s.local:%d%s\r\n\r\n", this->mdns.c_str(), port, path);

	// Store that init has already run
	this->init_has_run = true;

	return 1;
}

// One param
int WebOTA::init(const unsigned int port) {
	return WebOTA::init(port, "/webota");
}

// No params
int WebOTA::init() {
	return WebOTA::init(8080, "/webota");
}

int WebOTA::handle() {
	// If we haven't run the init yet run it
	if (!this->init_has_run) {
		WebOTA::init();
	}

	OTAServer.handleClient();
#ifdef ESP8266
	MDNS.update();
#endif

	return 1;
}

long WebOTA::max_sketch_size() {
	long ret = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;

	return ret;
}

// R Macro string literal https://en.cppreference.com/w/cpp/language/string_literal
const char gotek_version_html[] PROGMEM = "<title>SweProj Info Page</title><h1>" GOTEK_FULL_NAME " Info Page</h1> <br> Firmware version: " GOTEK_VERSION;

const char ota_version_html[] PROGMEM = "<h1>" GOTEK_FULL_NAME " Version " GOTEK_VERSION " Firmware Update</h1>";

const char ota_upload_form[] PROGMEM = R"!^!(

<form method="POST" action="#" enctype="multipart/form-data" id="upload_form">
    <input type="file" name="update" id="file">
    <input type="submit" value="Update">
</form>

<div id="prg_wrap" style="border: 0px solid; width: 100%;">
   <div id="prg" style="text-shadow: 2px 2px 3px black; padding: 5px 0; display: none; border: 1px solid #008aff; background: #002180; text-align: center; color: white;"></div>
</div>

<script>
var domReady = function(callback) {
	document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
};

domReady(function() {
	var myform = document.getElementById('upload_form');
	var filez  = document.getElementById('file');

	myform.onsubmit = function(event) {
		event.preventDefault();

		var formData = new FormData();
		var file     = filez.files[0];

		if (!file) { return false; }

		formData.append("files", file, file.name);

		var xhr = new XMLHttpRequest();
		xhr.upload.addEventListener("progress", function(evt) {
			if (evt.lengthComputable) {
				var per = Math.round((evt.loaded / evt.total) * 100);
				var prg = document.getElementById('prg');

				prg.innerHTML     = per + "%"
				prg.style.width   = per + "%"
				prg.style.display = "block"
			}
		}, false);
		xhr.open('POST', location.href, true);

		// Set up a handler for when the request finishes.
		xhr.onload = function () {
			if (xhr.status === 200) {
				//alert('Success');
			} else {
				//alert('An error occurred!');
			}
		};

		xhr.send(formData);
   }
});
</script>)!^!";


#ifdef ESP8266
int WebOTA::add_http_routes(ESP8266WebServer *server, const char *path) {
#endif
#ifdef ESP32
int WebOTA::add_http_routes(WebServer *server, const char *path) {
#endif
	// Index page
	server->on("/", HTTP_GET, [server]() {
		String html = stylecss;
		String outputStateValue="";
		if (nrst_state){
			outputStateValue="checked";
		}else{
			outputStateValue="";
		}
		IPAddress ip = WiFi.localIP();
		String localIP = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
		html += FPSTR(gotek_version_html);
		//html += "<br><a href=\"https://git.sweproj.com/ABC80/SweFlops\">https://git.sweproj.com/ABC80/SweFlops/</a>";
		//html += "<br><br>";
		html += "<div class=\"card\">";
		html += "<h3>Gotek data</h3>";
		html += "<h3><span id=\"gotek\">0</span></h3><br>";
		html += "</div>";
		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','l');\" ontouchstart=\"toggleCheckbox('on','l');\"  \">LEFT</button>&nbsp";
		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','s');\" ontouchstart=\"toggleCheckbox('on','s');\"  \">SELECT</button>&nbsp";
		html += "<button class=\"button\" onmousedown=\"toggleCheckbox('on','r');\" ontouchstart=\"toggleCheckbox('on','r');\"  \">RIGHT</button>&nbsp";
		html += "<br><br>";
		html += "<h4>Enable disc transfer <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox2(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
		html += "<br><br>";
		html += "<b>Supported Services</b>";
		html += "<ul>";
		html += "<li>WebDAV at port 8080<br>http://" + localIP + ":8080</li><br>";
		html += "<li>FTP at port 21 (FileZilla is supported)<br>ftp://" + localIP + "</li><br>";
		html += "<li>Firmware update<br><a href=\"http://" + localIP + "/webota\">http://" + localIP + "/webota</a></li><br>";
		html += "<li>Format flash<br><a href=\"http://" + localIP + "/format\">http://" + localIP + "/format</a></li><br>";
		html += "</ul>";
		html += "<br>";
		html += "<br>";
		html += "HINT: The SweProj Gotek V1.03 device supports up to 32 GB microSD/TF cards.";
		html += "<br>";
		html += "<br>";
		html += "<script>";

		html += "setInterval(function() {";
  		// Call a function repetatively with .1 Second interval
  		html += "getData();";
		html += "}, 100);"; 

		html += "function toggleCheckbox2(element) {";
  		html += "var xhr = new XMLHttpRequest();";
  		html += "if(element.checked){xhr.open(\"GET\", \"/onw\", true); }";
  		html += "else { xhr.open(\"GET\", \"/offw\", true); }";
  		html += "xhr.send();";
		html += "}";

		html += "function getData() {";
  		html += "var xhttp = new XMLHttpRequest();";
  		html += "xhttp.onreadystatechange = function() {";
    	html += "if (this.readyState == 4 && this.status == 200) {";
      	html += "document.getElementById(\"gotek\").innerHTML =";
      	html += "this.responseText;";
    	html += "}";
  		html += "};";
  		html += "xhttp.open(\"GET\", \"readgotek\", true);";
  		html += "xhttp.send();";
		html += "}";
		html += "function toggleCheckbox(x,b) {";
     	html += "var xhr = new XMLHttpRequest();";
     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
     	html += "xhr.send();";
  		html += "}";
		html += "</script>";
		server->send(200, "text/html", html.c_str());
	});
server->on("/format", HTTP_GET, [server]() {
		String html = stylecss;
		IPAddress ip = WiFi.localIP();
		String localIP = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
		html += FPSTR(gotek_version_html);
		//html += "<br><a href=\"https://git.sweproj.com/ABC80/SweFlops\">https://git.sweproj.com/ABC80/SweFlops/</a>";
		//html += "<br><br>";
		html += "<div class=\"card\">";
		html += "<h3>Gotek Format</h3>";
		html += "</div>";
		html += "<h3><span id=\"gotekformat\">0</span></h3><br>";
		html += "</div>";
		html += "<button class=\"button\" onmousedown=\"formatCheckbox('on','f');\" ontouchstart=\"formatCheckbox('on','f');\"  \">FORMAT</button>&nbsp";
		html += "<br>";
		html += "<br>";
		html += "HINT: The SweProj Gotek V1.03 device supports up to 32 GB microSD/TF cards.";
		html += "<br>";
		html += "<br>";
		html += "<script>";
		html += "function formatCheckbox(x,b) {";
     	html += "var xhr = new XMLHttpRequest();";
     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
     	html += "xhr.send();";
  		html += "}";
		html += "function getData() {";
  		html += "var xhttp = new XMLHttpRequest();";
  		html += "xhttp.onreadystatechange = function() {";
    	html += "if (this.readyState == 4 && this.status == 200) {";
      	html += "document.getElementById(\"gotekformat\").innerHTML =";
      	html += "this.responseText;";
    	html += "}";
  		html += "};";
  		html += "xhttp.open(\"GET\", \"formatgotek\", true);";
  		html += "xhttp.send();";
		html += "}";
		html += "function toggleCheckbox(x,b) {";
     	html += "var xhr = new XMLHttpRequest();";
     	html += "xhr.open(\"GET\",  \"/\" + x+b, true);";
     	html += "xhr.send();";
  		html += "}";
		html += "</script>";
		server->send(200, "text/html", html.c_str());
	});
	// Upload firmware page
	server->on(path, HTTP_GET, [server,this]() {
		String html = "";
		if (this->custom_html != NULL) {
			html += this->custom_html;
		} else {
			html += FPSTR(ota_version_html);
		}

		html += FPSTR(ota_upload_form);
		server->send_P(200, "text/html", html.c_str());
	});

	// Handling uploading firmware file
	server->on(path, HTTP_POST, [server,this]() {
		server->send(200, "text/plain", (Update.hasError()) ? "Update: fail\n" : "Update: OK!\n");
		delay(500);
		ESP.restart();
	}, [server,this]() {
		HTTPUpload& upload = server->upload();

		if (upload.status == UPLOAD_FILE_START) {
			Serial.printf("Firmware update initiated: %s\r\n", upload.filename.c_str());

			//uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
			uint32_t maxSketchSpace = this->max_sketch_size();

			if (!Update.begin(maxSketchSpace)) { //start with max available size
				Update.printError(Serial);
			}
		} else if (upload.status == UPLOAD_FILE_WRITE) {
			/* flashing firmware to ESP*/
			if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
				Update.printError(Serial);
			}

			// Store the next milestone to output
			uint16_t chunk_size  = 51200;
			static uint32_t next = 51200;

			// Check if we need to output a milestone (100k 200k 300k)
			if (upload.totalSize >= next) {
				Serial.printf("%dk ", next / 1024);
				next += chunk_size;
			}
		} else if (upload.status == UPLOAD_FILE_END) {
			if (Update.end(true)) { //true to set the size to the current progress
				Serial.printf("\r\nFirmware update successful: %u bytes\r\nRebooting...\r\n", upload.totalSize);
			} else {
				Update.printError(Serial);
			}
		}

	});
	// Gotek page
	server->on("/readgotek", HTTP_GET, [server]() {
		char html[200] ;
		char br[]="<br>";
		for(uint i=0;i<osd_display.rows;i++){
		memcpy(html+osd_display.cols*(i)+(4*i),&osd_display.text[i],osd_display.cols);
		memcpy(html+osd_display.cols*(i+1)+(4*i),br,4);
		}
		memcpy(html+osd_display.cols*(osd_display.rows)+(4*(osd_display.rows)),"\0",1);

		server->send_P(200, "text/html", html);
	});
	// Receive an HTTP GET request
	server->on("/onl", HTTP_GET, [server] () {
		osd_info.buttons = osd_info.buttons | 0x01;
		osd_info.buttons2 =osd_info.buttons ;
		server->send_P(200, "text/plain", "ok");
	});
	server->on("/ons", HTTP_GET, [server] () {
		osd_info.buttons = osd_info.buttons | 0x04;
		osd_info.buttons2 =osd_info.buttons ;
		server->send_P(200, "text/plain", "ok");
	});
	server->on("/onr", HTTP_GET, [server] () {
		osd_info.buttons = osd_info.buttons | 0x02;
		osd_info.buttons2 =osd_info.buttons ;
		server->send_P(200, "text/plain", "ok");
	});
	server->on("/onw", HTTP_GET, [server] () {

		digitalWrite(GOTEK_SD_SEL    ,HIGH);	
		digitalWrite(GOTEK_STM_RST    ,LOW);	

		digitalWrite(1,HIGH);
		nrst_state=true;
		server->send_P(200, "text/plain", "ok");
    });

    server->on("/offw", HTTP_GET, [server] () {
		
		digitalWrite(GOTEK_SD_SEL    ,LOW);	
		digitalWrite(GOTEK_STM_RST    ,HIGH);
		masterboot=false;
		isSDInit = false;
		digitalWrite(1,LOW);
		nrst_state=false;

		server->send_P(200, "text/plain", "ok");
	});

	server->on("/onf", HTTP_GET, [server] () {

		digitalWrite(GOTEK_SD_SEL    ,HIGH);	
		digitalWrite(GOTEK_STM_RST    ,LOW);	

		digitalWrite(1,HIGH);

		nrst_state=true;
#if GOTEK_SDFAT2
		 m_card = cardFactory.newCard(SD_CONFIG);
		ExFatFormatter exFatFormatter;
		FatFormatter fatFormatter;
#else
if (!card.begin(GOTEK_SD_CS,SD_SCK_MHZ(50))) {
        ESP_LOGE(TAG,"SD initialization failure!");
        ESP_LOGE(TAG,"Is the SD card inserted correctly?");
        ESP_LOGE(TAG,"Is chip select correct at the top of this program?");
    sdError("card.begin failed");
}else{
	ESP_LOGE(TAG,"SD INIT!!!!!");
}
#endif
		uint32_t cardSectorCount = 0;
		uint8_t sectorBuffer[512];
#ifdef GOTEK_SDFAT2
		cardSectorCount = m_card->sectorCount();
#else
	cardSectorCount = card.cardSize();
#endif
 	ESP_LOGI(TAG,"Formating.");
  // Format exFAT if larger than 32GB.
#if GOTEK_SDFAT2
   Serial.println("Card size: "+String(cardSectorCount * 5.12e-7));
   Serial.println(" GB (GB = 1E9 bytes)\n");
#else
  cardSizeBlocks = card.cardSize();
  if (cardSizeBlocks == 0) {
    sdError("cardSize");
  }
  cardCapacityMB = (cardSizeBlocks + 2047)/2048;

   ESP_LOGI(TAG,"Card Size: %s %s",String( 1.048576*cardCapacityMB)," MB");

#endif
     ESP_LOGI(TAG,"Card will be formated ");
  if (cardSectorCount > 67108864) {
    ESP_LOGI(TAG,"exFAT");
  } else if (cardSectorCount > 4194304) {
    ESP_LOGI(TAG,"FAT32");
  } else {
   ESP_LOGI(TAG,"FAT16");
  }
  bool rtn=false;
#ifdef GOTEK_SDFAT2
  rtn = cardSectorCount > 67108864 
                 ? exFatFormatter.format(m_card, sectorBuffer, &Serial)
                 : fatFormatter.format(m_card, sectorBuffer, &Serial);
#else
	formatCard() ;
	rtn=true;
#endif
	digitalWrite(GOTEK_SD_SEL    ,LOW);	
	digitalWrite(GOTEK_STM_RST    ,HIGH);
	masterboot=false;
	isSDInit = false;
	digitalWrite(1,LOW);
	nrst_state=false;
 	if (!rtn) {

	 	ESP_LOGE(TAG,"Failed");
	}else{
		ESP_LOGI(TAG,"Success.");
		server->send_P(200, "text/plain", "ok");
	}
	});

	server->begin();

	return 1;
}

// If the MCU is in a delay() it cannot respond to HTTP OTA requests
// We do a "fake" looping delay and listen for incoming HTTP requests while waiting
void WebOTA::delay(unsigned int ms) {
	// Borrowed from mshoe007 @ https://github.com/scottchiefbaker/ESP-WebOTA/issues/8
	decltype(millis()) last = millis();

	while ((millis() - last) < ms) {
		OTAServer.handleClient();
		::delay(5);
	}
}

void WebOTA::set_custom_html(char const * const html) {
	this->custom_html = html;
}

int WebOTA::init_mdns(const char *host) {
	// Use mdns for host name resolution
	if (!MDNS.begin(host)) {
		Serial.println("Error setting up MDNS responder!");

		return 0;
	}

	Serial.printf("mDNS started : %s.local\r\n", host);

	webota.mdns = host;

	return 1;
}

String WebOTA::ip2string(IPAddress ip) {
	String ret = String(ip[0]) + "." +  String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);

	return ret;
}

int WebOTA::init_wifi(const char *mdns_hostname) {

	Serial.println("");
	Serial.printf("Connected to '%s'\r\n\r\n",WiFi.SSID().c_str());

	String ipaddr = ip2string(WiFi.localIP());
	Serial.printf("IP address   : %s\r\n", ipaddr.c_str());
	Serial.printf("MAC address  : %s \r\n", WiFi.macAddress().c_str());

	init_mdns(mdns_hostname);

	return 1;
}
