#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <ESP32Encoder.h>

TaskHandle_t pulseTaskHandle;
ESP32Encoder pulseCount_Encoder;
ESP32Encoder pulseWidth_Encoder;
ESP32Encoder pulseSpace_Encoder;
ESP32Encoder gate_freq_Encoder;
ESP32Encoder pulseWidthModifier_Encoder;
ESP32Encoder pulseSpaceModifier_Encoder;
ESP32Encoder gateModifier_Encoder;

// #### Change Me - Local Wifi Info ####
const char *SSID = "NETGEAR";
const char *PWD = "12345678";

// #### Output Pins ####
int pinChannel1 = 2;
int pinChannel2 = 4;

int pulseCount_EncoderPIN1 = 14;			//Wire to DT of Encoder
int pulseCount_EncoderPIN2 = 13;			//Wire to CLK of Encoder
int pulseWidth_EncoderPIN1 = 35;			//Wire to DT of Encoder
int pulseWidth_EncoderPIN2 = 34;			//Wire to CLK of Encoder
int pulseSpace_EncoderPIN1 = 19;			//Wire to DT of Encoder
int pulseSpace_EncoderPIN2 = 18;			//Wire to CLK of Encoder
int gate_freq_EncoderPIN1 = 22;				//Wire to DT of Encoder
int gate_freq_EncoderPIN2 = 23;				//Wire to CLK of Encoder

int pulseWidthModifier_EncoderPIN1 = 27;	//Wire to DT of Encoder
int pulseWidthModifier_EncoderPIN2 = 26;	//Wire to CLK of Encoder
int pulseSpaceModifier_EncoderPIN1 = 5;		//Wire to DT of Encoder
int pulseSpaceModifier_EncoderPIN2 = 32;	//Wire to CLK of Encoder
int gateModifier_EncoderPIN1 = 25;			//Wire to DT of Encoder
int gateModifier_EncoderPIN2 = 33;			//Wire to CLK of Encoder

// Web server running on port 80
WebServer server(80);

// #### CHANGE ME ####
// Set your Static IP address
IPAddress local_IP(192, 168, 1, 8);
// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);

IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);   //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

// JSON data buffer
StaticJsonDocument<500> jsonDocument;
char buffer[500];

// env variable
float width = 500;
float space = 500;
float widthmodifier = 1;
float spacemodifier = 1;
float width1 = 0;
float space1 = 0;
float widthelongationmodifier = 1;
float spaceelongationmodifier = 1;
float gate = 5000;
float gatemodifier = 1;
int count = 5;
int enablegate = 1;
int invertOne = 0;
int invertTwo = 0;
int reference1 = LOW;
int reference2 = HIGH;
int enablealternate = 0;

void connectToWiFi() {
  Serial.print("Connecting to ");
  Serial.println(SSID);
  // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.print("Connected. IP: ");
  Serial.println(WiFi.localIP());
}

void setup_routing() {
  server.enableCORS();
  server.on("/metrics", getMetrics);
  server.on("/set", HTTP_POST, handlePost);
  server.begin();
}

void create_json(float width, float space, float count, float gate, float enablegate, float widthelongationmodifier, float spaceelongationmodifier, float enablealternate,
  float widthmodifier, float spacemodifier, float gatemodifier, int invertOne, int invertTwo) {
  jsonDocument.clear();
  jsonDocument["width"] = width;
  jsonDocument["space"] = space;
  jsonDocument["widthmodifier"] = widthmodifier;
  jsonDocument["spacemodifier"] = spacemodifier;
  jsonDocument["widthelongationmodifier"] = widthelongationmodifier;
  jsonDocument["spaceelongationmodifier"] = spaceelongationmodifier;
  jsonDocument["count"] = count;
  jsonDocument["gate"] = gate;
  jsonDocument["gatemodifier"] = gatemodifier;
  jsonDocument["enablegate"] = enablegate;
  jsonDocument["enablealternate"] = enablealternate;
  serializeJson(jsonDocument, buffer);
}

void getMetrics() {
  create_json(width, space, count, gate, enablegate, widthelongationmodifier, spaceelongationmodifier, enablealternate, widthmodifier, spacemodifier, gatemodifier, invertOne, invertTwo);
  server.send(200, "application/json", buffer);
}

void handlePost() {
  if (server.hasArg("plain") == false) {
      Serial.println("no plain mode");
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);
  width = jsonDocument["width"];
  widthmodifier = jsonDocument["widthmodifier"];
  space = jsonDocument["space"];
  spacemodifier = jsonDocument["spacemodifier"];
  widthelongationmodifier = jsonDocument["widthelongationmodifier"];
  spaceelongationmodifier = jsonDocument["spaceelongationmodifier"];
  count = jsonDocument["count"];
  gate = jsonDocument["gate"];
  gatemodifier = jsonDocument["gatemodifier"];
  enablegate = jsonDocument["enablegate"];
  enablealternate = jsonDocument["enablealternate"];

  invertOne = jsonDocument["invertOne"];
  invertTwo = jsonDocument["invertTwo"];

  pulseCount_Encoder.setCount(count);
  pulseWidth_Encoder.setCount(width);
  pulseSpace_Encoder.setCount(space);
  gate_freq_Encoder.setCount(gate);

  pulseWidthModifier_Encoder.setCount(widthmodifier);
  pulseSpaceModifier_Encoder.setCount(spacemodifier);
  gateModifier_Encoder.setCount(gatemodifier);

  serializeJson(jsonDocument, buffer);
  server.send(200, "application/json", buffer);
}

void customDelay(long delayValue)
{
  long i = 0;
  int x = 0;
  int delayValueloops = delayValue * 1.1;
  for(i=0;i<delayValueloops;i++){
    for(x=0;x<32;x++){
      __asm__("nop\n\t");
    }
  }
}

void pulseClock(void * parameter) {
  if(invertOne == 1) {
    reference1 = HIGH;
  }
  if(invertTwo == 1) {
    reference2 = HIGH;
  }

  for(;;){ // ** Start of infinite loop **
   width1 = width * widthmodifier;
   space1 = space * spacemodifier;
   for(int i=0; i<count; i++){
      width1 = width1 * widthelongationmodifier;
      space1 = space1 * spaceelongationmodifier;
      REG_WRITE(GPIO_OUT_W1TS_REG, BIT2);//GPIO2 HIGH (set)
      // REG_WRITE(GPIO_OUT_W1TS_REG, BIT4);//GPIO2 HIGH (set)
      customDelay(width1);
      REG_WRITE(GPIO_OUT_W1TC_REG, BIT2);//GPIO2 LOW (clear)
      // REG_WRITE(GPIO_OUT_W1TC_REG, BIT4);//GPIO2 LOW (clear)
      customDelay(space1);
    }

    if(enablegate){
      customDelay(gate * gatemodifier);
    }
    
    width1 = width * widthmodifier;
    space1 = space * spacemodifier;
    for(int i=0; i<count; i++){
       width1 = width1 * widthelongationmodifier;
       space1 = space1 * spaceelongationmodifier;
       // REG_WRITE(GPIO_OUT_W1TS_REG, BIT2);//GPIO2 HIGH (set)
       REG_WRITE(GPIO_OUT_W1TS_REG, BIT4);//GPIO2 HIGH (set)
       customDelay(width1);
       // REG_WRITE(GPIO_OUT_W1TC_REG, BIT2);//GPIO2 LOW (clear)
       REG_WRITE(GPIO_OUT_W1TC_REG, BIT4);//GPIO2 LOW (clear)
       customDelay(space1);
     }

     if(enablealternate) {
       if(reference1 == HIGH){
         reference1 = LOW;
         reference2 = HIGH;
       } else {
         reference1 = HIGH;
         reference2 = LOW;
       }

       digitalWrite(pinChannel2, reference1);
       digitalWrite(pinChannel1, reference2);
     }

    if(enablegate){
      customDelay(gate * gatemodifier);
    }
  }
  vTaskDelete(NULL);
}

void setup() {
  Serial.begin(115200);
  Serial.println("setup");
  connectToWiFi();				//Delete this line if using without internet
  setup_routing();				//Delete this line if using without internet

  pinMode(pinChannel2, OUTPUT);
  pinMode(pinChannel1, OUTPUT);
  digitalWrite(pinChannel2, LOW);
  digitalWrite(pinChannel1, LOW);

  pulseCount_Encoder.attachSingleEdge(pulseCount_EncoderPIN1, pulseCount_EncoderPIN2);
  pulseCount_Encoder.setFilter(1023);
  pulseCount_Encoder.setCount(count);
  pulseWidth_Encoder.attachSingleEdge(pulseWidth_EncoderPIN1, pulseWidth_EncoderPIN2);
  pulseWidth_Encoder.setFilter(1023);
  pulseWidth_Encoder.setCount(width);
  pulseSpace_Encoder.attachSingleEdge(pulseSpace_EncoderPIN1, pulseSpace_EncoderPIN2);
  pulseSpace_Encoder.setFilter(1023);
  pulseSpace_Encoder.setCount(space);
  gate_freq_Encoder.attachSingleEdge(gate_freq_EncoderPIN1, gate_freq_EncoderPIN2);
  gate_freq_Encoder.setFilter(1023);
  gate_freq_Encoder.setCount(gate);

  pulseWidthModifier_Encoder.attachSingleEdge(pulseWidthModifier_EncoderPIN1, pulseWidthModifier_EncoderPIN2);
  pulseWidthModifier_Encoder.setFilter(1023);
  pulseWidthModifier_Encoder.setCount(widthmodifier);
  pulseWidthModifier_Encoder.clearCount();

  pulseSpaceModifier_Encoder.attachSingleEdge(pulseSpaceModifier_EncoderPIN1, pulseSpaceModifier_EncoderPIN2);
  pulseSpaceModifier_Encoder.setFilter(1023);
  pulseSpaceModifier_Encoder.setCount(spacemodifier);
  pulseSpaceModifier_Encoder.clearCount();

  gateModifier_Encoder.attachSingleEdge(gateModifier_EncoderPIN1, gateModifier_EncoderPIN2);
  gateModifier_Encoder.setFilter(1023);
  gateModifier_Encoder.setCount(gatemodifier);
  gateModifier_Encoder.clearCount();

	pulseCount_Encoder.clearCount();
	pulseWidth_Encoder.clearCount();
	pulseSpace_Encoder.clearCount();
	gate_freq_Encoder.clearCount();

  ESP32Encoder::useInternalWeakPullResistors=UP;

  xTaskCreatePinnedToCore(pulseClock,"Pulse Clock",10000,NULL,1,&pulseTaskHandle,1);
}

void loop() {
  server.handleClient();							//Delete this line if using without internet
  if(count!=pulseCount_Encoder.getCount()){
    count=pulseCount_Encoder.getCount();
    if (count<1){
      pulseCount_Encoder.setCount(1);
      count=1;
    }
    Serial.print("count ");
    Serial.println(count);
  }
  if(width!=pulseWidth_Encoder.getCount()){
    width=pulseWidth_Encoder.getCount();
    if (width<1){
      pulseWidth_Encoder.setCount(1);
      width=1;
    }
    Serial.print("width: ");
    Serial.println(width);
  }
  if(widthmodifier!=pulseWidthModifier_Encoder.getCount()){
    widthmodifier=pulseWidthModifier_Encoder.getCount();
    if (widthmodifier<1){
      pulseWidthModifier_Encoder.setCount(1);
      widthmodifier=1;
    }
    Serial.print("widthmodifier: ");
    Serial.println(widthmodifier);
  }
  if(space!=pulseSpace_Encoder.getCount()){
    space=pulseSpace_Encoder.getCount();
    if (space<1){
      pulseSpace_Encoder.setCount(1);
      space=1;
    }
    Serial.print("space: ");
    Serial.println(space);
  }
  if(spacemodifier!=pulseSpaceModifier_Encoder.getCount()){
    spacemodifier=pulseSpaceModifier_Encoder.getCount();
    if (spacemodifier<1){
      pulseSpaceModifier_Encoder.setCount(1);
      spacemodifier=1;
    }
    Serial.print("spacemodifier: ");
    Serial.println(spacemodifier);
  }
  if(gate!=gate_freq_Encoder.getCount()){
    gate=gate_freq_Encoder.getCount();
    if (gate<1){
      gate_freq_Encoder.setCount(1);
      gate=1;
    }
    Serial.print("gate: ");
    Serial.println(gate);
  }
  if(gatemodifier!=gateModifier_Encoder.getCount()){
    gatemodifier=gateModifier_Encoder.getCount();
    if (gatemodifier<1){
      gateModifier_Encoder.setCount(1);
      gatemodifier=1;
    }
    Serial.print("gatemodifier: ");
    Serial.println(gatemodifier);
  }
  delay(500);
}
