//********************************//

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <FS.h>

#include "config.h"

//********* preprocessor block *********//

#ifdef DEVELOPMENT
#define LIGHT_NAME "Dimmable Hue Light (DEV)"
#else
#define LIGHT_NAME LIGHT_NAME_STR
#endif

//********* Config block *********//

// blue, warmwhite, purple, white&red&green
// blau, schwarz, rot, weiß
// ch1, ch2, ch3, ch4
// D1, D2, D7, D5
uint8_t pins[LIGHTS_COUNT] = { 5, 4, 13, 14 };

#ifndef DEVELOPMENT
IPAddress strip_ip   (192, 168,   0, 26);   // choose an unique IP Adress
#endif
#ifdef DEVELOPMENT
IPAddress strip_ip   (192, 168,   0, 27);   // choose an unique IP Adress
#endif
IPAddress gateway_ip (192, 168,   0,  1);  // Router IP
IPAddress subnet_mask(255, 255, 255,  0);
IPAddress dns        (192, 168,   0,  1);

//********************************//

#define LIGHT_VERSION 2.1

#define LAST_STATE_STARTUP_LIGHT_LAST_STATE 0
#define LAST_STATE_STARTUP_LIGHT_ON_STATE   1
#define LAST_STATE_STARTUP_LIGHT_OFF_STATE  2

#define LIGHT_STATE_ON  1
#define LIGHT_STATE_OFF 0

#define TIMING_CONTROL_ENABLED  1
#define TIMING_CONTROL_DISABLED 0

#define SCENE_RELEAX  0
#define SCENE_BRIGHT  1
#define SCENE_NIGHTLY 2

#define TEST_PWM_STATE_INIT     0
#define TEST_PWM_STATE_CH1_INC  1
#define TEST_PWM_STATE_CH1_DEC  2
#define TEST_PWM_STATE_CH2_INC  3
#define TEST_PWM_STATE_CH2_DEC  4
#define TEST_PWM_STATE_CH3_INC  5
#define TEST_PWM_STATE_CH3_DEC  6
#define TEST_PWM_STATE_CH4_INC  7
#define TEST_PWM_STATE_CH4_DEC  8

//********************************//

uint8_t scene;
uint8_t tc_enabled;
uint8_t tc_enabled_old;

bool test_pwm = false;
uint32_t test_pwm_lastcheck_ms = 0;
uint8_t test_pwm_state = TEST_PWM_STATE_INIT;

bool light_state[LIGHTS_COUNT];
bool in_transition;

int default_transitiontime = 4; // 4 = 4 seconds

uint16_t transitiontime[LIGHTS_COUNT];
uint16_t bri[LIGHTS_COUNT];
uint16_t current_pwm[LIGHTS_COUNT];

float step_level[LIGHTS_COUNT];
float current_bri[LIGHTS_COUNT];
byte mac[6];

ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdateServer;

uint32_t last_lightengine_activity = 0;

//********************************//

void apply_scene(uint8_t new_scene, uint8_t light)
{
  if (new_scene == SCENE_RELEAX)
  {
    bri[light] = 144;
  } else if (new_scene == SCENE_BRIGHT)
  {
    bri[light] = 254;
  } else if (new_scene == SCENE_NIGHTLY)
  {
    bri[0] = 25;
    bri[1] = 0;
    bri[2] = 0;
    bri[3] = 0;
  }
}

//********************************//

void process_lightdata(uint8_t light, float tt)
{
  if (light_state[light])
  {
    step_level[light] = (bri[light] - current_bri[light]) / tt;

  } else {
    step_level[light] = current_bri[light] / tt;
  }
}

//********************************//

void lightEngine()
{

  if (millis() < (last_lightengine_activity + TIME_LIGHTENGINE_INTERVAL_MS)) {
    // abort processing, the transition setting is a delay of seconds
    return;
  }

  last_lightengine_activity = millis();

  for (int i = 0; i < LIGHTS_COUNT; i++)
  {

    if (light_state[i])
    {
      if (bri[i] != current_bri[i])
      {
        in_transition = true;
        current_bri[i] += step_level[i] / BRI_MOD_STEPS_PER_SEC;
        if ((step_level[i] > 0.0 && current_bri[i] > bri[i]) || (step_level[i] < 0.0 && current_bri[i] < bri[i]))
        {
          current_bri[i] = bri[i];
          //Serial.println("Reached target bri[" + (String)i + "] = " + (String)bri[i]);
        }

        uint16_t tmp_pwm = calcPWM(current_bri[i]);
        current_pwm[i] = tmp_pwm;
        //Serial.println("lon: pin" + (String)i + " = PWM(" + (String)tmp_pwm + ")");
        analogWrite(pins[i], tmp_pwm);
      }
    } else { // light state is off

      if (current_bri[i] != 0)
      {
        in_transition = true;
        current_bri[i] += step_level[i] / BRI_MOD_STEPS_PER_SEC;
        if (current_bri[i] < 0)
        {
          current_bri[i] = 0;
          //Serial.println("Reached target bri[" + (String)i + "] = " + (String)bri[i]);
        }

        uint16_t tmp_pwm = calcPWM(current_bri[i]);
        current_pwm[i] = tmp_pwm;
        //Serial.println("loff: pin" + (String)i + " = PWM(" + (String)tmp_pwm + ")");
        analogWrite(pins[i], tmp_pwm);
      }
    }

  }  // for loop end

  if (in_transition)
  {
    delay(6);
    in_transition = false;
  }
}

//********************************//

uint16_t calcPWM(float tbri)
{
  uint16_t tmp_pwm = PWM_OFF;
  if (tbri > 0.0)
  {
    tmp_pwm = PWM_MIN + (int)(tbri * BRI_TO_PWM_FACTOR);
  }
  if (tmp_pwm > PWM_MAX) {
    tmp_pwm = PWM_MAX;
  }

  return tmp_pwm;
}

//********************************//

void read_eeprom_config()
{
  uint8_t tmp = EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS);
  if (tmp == TIMING_CONTROL_DISABLED)
  {
    tc_enabled = TIMING_CONTROL_DISABLED;

  } else if (tmp == TIMING_CONTROL_ENABLED)
  {
    tc_enabled = TIMING_CONTROL_ENABLED;

  } else {
    // Write default value
    EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_DISABLED);
    EEPROM.commit();
    tc_enabled = TIMING_CONTROL_DISABLED;
    Serial.println("Written default timing control config to EEPROM (disabled)");
  }
  Serial.println("Timing Control status: " + (String)tc_enabled);

  if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) > 2)
  {
    // set the default value on uninitialized EEPROM
    EEPROM.write(EEPROM_LAST_STATE_STARTUP_ADDRESS, 0);
    EEPROM.commit();
    Serial.println("Written default 'last state' config to EEPROM");
  }
  Serial.println("Last state startup setting: " + (String)EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS));

  if (EEPROM.read(EEPROM_SCENE_ADDRESS) > 2)
  {
    // set the default value on uninitialized EEPROM
    EEPROM.write(EEPROM_SCENE_ADDRESS, 0);
    EEPROM.commit();
    Serial.println("Written default scene config to EEPROM");
  }
  Serial.println("Scene setting: " + (String)EEPROM.read(EEPROM_SCENE_ADDRESS));

#ifdef USE_STATIC_IP
  if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) > 1)
  {
    EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, 0);
    EEPROM.commit();
    Serial.println("Written default dynamic IP setting (disabled) to EEPROM");
  }
#else
  if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) > 1)
  {
    EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, 1);
    EEPROM.commit();
    Serial.println("Written default dynamic IP setting (enabled) to EEPROM");
  }
#endif
  Serial.println("Dynamic IP setting: " + (String)EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS));

  for (uint8_t light = 0; light < LIGHTS_COUNT; light++)
  {
    apply_scene(EEPROM.read(EEPROM_SCENE_ADDRESS), light);
    step_level[light] = bri[light] / 150.0;

    if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) == LAST_STATE_STARTUP_LIGHT_LAST_STATE ||
        (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) == LAST_STATE_STARTUP_LIGHT_ON_STATE &&
         EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_ON))
    {
      light_state[light] = true;  // set the light state to on
    }
    Serial.println("light[" + (String)light + "] = " + (String)light_state[light]);
  }

}

//********************************//

void setup()
{
  EEPROM.begin(256);

  SPIFFS.begin();

#ifdef DEVELOPMENT
  // Serial is not used in non development mode
  Serial.begin(SERIAL_BAUD_RATE);
#endif

  Serial.flush();
  delay(1000);

  //Serial.println("Flash size: " + (String)ESP.getFlashChipSize());

  Dir dir = SPIFFS.openDir("/");
  Serial.println("\n\nSPIFFS directory content:");
  while (dir.next())
  {
    String fileName = dir.fileName();
    size_t fileSize = dir.fileSize();
    Serial.printf("Datei Name: %s, Größe: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
  }

  if (EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS) == 0)
  {
    WiFi.config(strip_ip, gateway_ip, subnet_mask, dns);
  }

  read_eeprom_config();

  for (int j = 0; j < 200; j++)
  {
    lightEngine();
  }

  WiFi.mode(WIFI_STA);
  WiFiManager wifiManager;
  wifiManager.setConfigPortalTimeout(120);
  wifiManager.autoConnect(LIGHT_NAME);

  IPAddress myIP = WiFi.localIP();
  Serial.print("IP: ");
  Serial.println(myIP);

  analogWriteFreq(PWM_FREQ);

  if (!light_state[0])
  {
    // Show that we are connected
    analogWrite(pins[0], 100);
    delay(500);
    analogWrite(pins[0], 0);
  }

  WiFi.macAddress(mac);

  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED off by making the voltage HIGH

  httpUpdateServer.setup(&server);  // start http server

  init_webserver();

  Serial.println("Init timinc control");
  tc_init();

  Serial.println("Starting webserver");
  server.begin();
}  // end of setup

//********************************//

void loop()
{
  server.handleClient();

  ESP.wdtFeed();

  lightEngine();

  //Serial.println("tc_enabled = " + (String)tc_enabled);
  tc_update_loop();

  ESP.wdtFeed();

  test_pwm_main();
  delay(100);
}

//********************************//

void handleNotFound()
{
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++)
  {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

//********************************//

void init_webserver()
{

  server.on("/state", HTTP_PUT, []()
  {  // HTTP PUT request used to set a new light state
    DynamicJsonDocument root(512);
    DeserializationError error = deserializeJson(root, server.arg("plain"));

    if (error) {
      server.send(404, "text/plain", "FAIL. " + server.arg("plain"));
    } else {

      for (JsonPair state : root.as<JsonObject>())
      {
        const char* key = state.key().c_str();
        int light = atoi(key) - 1;
        JsonObject values = state.value();

        uint8_t tmp = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS);
        if (values.containsKey("on"))
        {
          if (values["on"])
          {
            light_state[light] = true;
            if (tmp == LAST_STATE_STARTUP_LIGHT_LAST_STATE && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_OFF)
            {
              EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_ON);
            }
          } else {
            light_state[light] = false;
            if (tmp == LAST_STATE_STARTUP_LIGHT_LAST_STATE && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == LIGHT_STATE_ON)
            {
              EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_OFF);
            }
          }
        }

        if (values.containsKey("bri"))
        {
          bri[light] = values["bri"];
        }

        if (values.containsKey("bri_inc"))
        {
          bri[light] += (int)values["bri_inc"];
          if (bri[light] > 255) bri[light] = 255;
          else if (bri[light] < 1) bri[light] = 1;
        }

        if (values.containsKey("transitiontime"))
        {
          default_transitiontime = values["transitiontime"];
          if (tc_enabled == TIMING_CONTROL_DISABLED)
          {
            for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++)
            {
              // set the default transition time for all lights
              process_lightdata(i, default_transitiontime);
            }
          }
        }

      }
      String output;
      serializeJson(root, output);
      server.send(200, "text/plain", output);
    }
  });

  server.on("/state", HTTP_GET, []()
  {  // HTTP GET request used to fetch current light state
    uint8_t light = server.arg("light").toInt() - 1;
    DynamicJsonDocument root(512);
    root["on"]     = light_state[light];
    root["bri"]    = bri[light];
    root["curbri"] = (int)current_bri[light];
    root["curpwm"] = current_pwm[light];
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });

  server.on("/detect", []()
  {  // HTTP GET request used to discover the light type
    char macString[32] = { 0 };
    sprintf(macString, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    DynamicJsonDocument root(1024);
    root["name"]     = LIGHT_NAME;
    root["lights"]   = LIGHTS_COUNT;
    root["protocol"] = "native_multi";
    root["modelid"]  = "LWB010";
    root["type"]     = "dimmable_light";
    root["mac"]      = String(macString);
    root["version"]  = LIGHT_VERSION;
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });

  server.on("/test_pwm", []()
  {
    test_pwm = true;
    tc_enabled_old = tc_enabled;
    tc_enabled = TIMING_CONTROL_DISABLED;

    server.send(200, "text/html", "OK");
  });

  server.on("/tc_data_blocks_read", []()
  {
    String output = tc_getJsonData();

    server.send(200, "application/json", output);
  });

  server.on("/tc_data_blocks_store", []()
  {
    if (server.hasArg("data"))
    {
      String jsonData = server.arg("data");
      Serial.println("Received: " + jsonData);
      tc_jsonDataBlocksToEEPROM(jsonData);
      server.send(200, "text/html", "tcdata saved");
    }
  });

  server.on("/js_top", []()
  {
    server.send(200, "text/html", replacePlaceholder(loadSPIFFSFile("/top.js")));
  });

  server.on("/js_bottom", []()
  {
    server.send(200, "text/html", replacePlaceholder(loadSPIFFSFile("/bottom.js")));
  });

  server.on("/css", []()
  {
    server.send(200, "text/css", loadSPIFFSFile("/style.css"));
  });

  server.on("/", []()
  {

    if (server.hasArg("transition"))
    {
      default_transitiontime = server.arg("transition").toFloat();
      if (tc_enabled == TIMING_CONTROL_DISABLED)
      {
        for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++)
        {
          // set the default transition time for all lights
          process_lightdata(i, default_transitiontime);
          Serial.println("transition for light " + (String)i + " set to " + (String)default_transitiontime);
        }
      }
    }

    // startup behavior switch handling
    if (server.hasArg("startup"))
    {
      int startup = server.arg("startup").toInt();
      if (EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS) != startup)
      {
        EEPROM.write(EEPROM_LAST_STATE_STARTUP_ADDRESS, startup);

        for (uint8_t i = 0; i < LIGHTS_COUNT; i++) {
          uint8_t tmp = (light_state[i] == true ? LIGHT_STATE_ON : LIGHT_STATE_OFF);
          if (EEPROM.read(EEPROM_LAST_STATE_ADDRESS + i) != tmp)
          {
            EEPROM.write(EEPROM_LAST_STATE_ADDRESS + i, tmp);
          }
        }

        EEPROM.commit();
        Serial.print("Startup behavior set to ");
        Serial.println(EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS));
      }
    }

    // timing controller switch handling
    if (server.hasArg("tc"))
    {
      if (server.arg("tc") == "true")
      {
        if (tc_enabled == TIMING_CONTROL_DISABLED)
        {
          if (EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS) != TIMING_CONTROL_ENABLED)
          {
            tc_enabled = TIMING_CONTROL_ENABLED;
            EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_ENABLED);
            EEPROM.commit();
            Serial.print("Timing control = ");
            Serial.println(EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS));
            tc_update_main(); // call the main update function to read data and set the light states
          }
        }

      } else {  // tc is set to false or something else

        if (tc_enabled == TIMING_CONTROL_ENABLED)
        {
          tc_enabled = TIMING_CONTROL_DISABLED;
          if (EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS) != TIMING_CONTROL_DISABLED)
          {
            EEPROM.write(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS, TIMING_CONTROL_DISABLED);
            EEPROM.commit();
            Serial.print("Timing control = ");
            Serial.println(EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS));

            for (uint8_t i = 0 ; i < LIGHTS_COUNT; i++)
            {
              // set the default transition time for all lights
              process_lightdata(i, default_transitiontime);
            }
          }
        }
      }
    }

    // scene switch handling
    if (server.hasArg("scene")) {
      scene = server.arg("scene").toInt();
      if (EEPROM.read(EEPROM_SCENE_ADDRESS) != scene) {
        EEPROM.write(EEPROM_SCENE_ADDRESS, scene);
        EEPROM.commit();
        Serial.print("Scene set to ");
        Serial.println(EEPROM.read(EEPROM_SCENE_ADDRESS));
      }
    }

    if (server.hasArg("dip")) {
      uint8_t tmp = EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS);
      uint8_t tmp2 = (server.arg("dip") == "true" ? 1 : 0);
      if (tmp != tmp2) {
        EEPROM.write(EEPROM_DYNAMIC_IP_ADDRESS, tmp2);
        EEPROM.commit();
        Serial.print("Set dynamic IP to ");
        Serial.println(EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS));
      }
    }

    // process the received data for every light
    for (int light = 0; light < LIGHTS_COUNT; light++)
    {

      if (server.hasArg("bri" + (String)light))
      {
        int tmp = (int)server.arg("bri" + (String)light).toInt();
        
        if (tmp > 255)
        {
          tmp = 255;
        } else if (tmp < 0) {
          tmp = 0;
        }
        bri[light] = tmp;
        Serial.print("Brightness ");
        Serial.print(light);
        Serial.print(" set to ");
        Serial.println(bri[light]);
      }

      if (server.hasArg("on" + (String)light))
      {
        uint8_t tmp = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS);
        if (server.arg("on" + (String)light) == "true" && light_state[light] == false)
        {
          light_state[light] = true;
          if (tmp == 0 && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == 0)
          {
            EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_ON);
            EEPROM.commit();
          }
          Serial.print("Light ");
          Serial.print(light);
          Serial.print(" state set to ");
          Serial.println(light_state[light]);

        } else if (server.arg("on" + (String)light) == "false" && light_state[light] == true)
        {
          light_state[light] = false;
          if (tmp == 0 && EEPROM.read(EEPROM_LAST_STATE_ADDRESS + light) == 1)
          {
            EEPROM.write(EEPROM_LAST_STATE_ADDRESS + light, LIGHT_STATE_OFF);
            EEPROM.commit();
          }
          Serial.print("Light ");
          Serial.print(light);
          Serial.print(" state set to ");
          Serial.println(light_state[light]);
        }

        if (tc_enabled == TIMING_CONTROL_DISABLED)
        {
          process_lightdata(light, default_transitiontime);
        }
      } else {
        // light is off
        if (tc_enabled == TIMING_CONTROL_DISABLED)
        {
          process_lightdata(light, default_transitiontime);
        }
      }

      // start alerting for every light
      if (server.hasArg("alert"))
      {
        if (light_state[light])
        {
          current_bri[light] = 0;
        } else {
          current_bri[light] = 255;
        }
      }

    }  // process all lights

    if (server.hasArg("resettc"))
    {  // reqrite the tc config and reboot
      tc_write_default();
      ESP.reset();
    }

    if (server.hasArg("reset"))
    {
      ESP.reset();
    }

    // ***** Generate static HTML page ***** //

    String tmp1 = genHMTLTop();

    String tmp2 = genLightControlHTML();

    String tmp3 = getIndexHTMLMiddle();

    String tmp4 = genConfigHTML();

    String tmp5 = genHMTLBottom();

    server.send(200, "text/html", tmp1 + tmp2 + tmp3 + tmp4 + tmp5);

  });

  server.on("/reset", []()
  { // trigger manual reset
    server.send(200, "text/html", "reset");
    delay(1000);
    ESP.restart();
  });

  server.onNotFound(handleNotFound);
}

//********************************//

String genHMTLTop()
{
  return replacePlaceholder(getIndexHTMLTop());
}

String genHMTLBottom()
{
  return replacePlaceholder(getIndexHTMLBottom());
}

String genConfigHTML()
{
  // +++++ Generate config part of the page +++++
  return replacePlaceholder(getConfigHTML());
}

String genTCEditHTML()
{
  return replacePlaceholder(getTCDataEditHTML());
}

String genLightControlHTML()
{
  String http_content = "";
  // +++++ Generate lights part of the HTML page +++++
  // Light control
  for (uint8 light_num = 0; light_num < LIGHTS_COUNT; light_num++)
  {
    // Generate lights part of the HTML page
    String tmp_light_content = getLightControlHTML();

    // on/off buttons and slider
    tmp_light_content.replace("{{LIGHT_NUMBER}}", (String)(light_num + 1));
    tmp_light_content.replace("{{LIGHT_NUMBER_DEC}}", (String)light_num);

    // add the lights code to the html output string
    http_content += tmp_light_content;
  }
  return http_content;
}

//********************************//

String replacePlaceholder(String http_content)
{

  http_content.replace("{{LIGHT_NAME}}", (String)LIGHT_NAME);

  http_content.replace("{{LIGHT_COUNT}}", (String)LIGHTS_COUNT);

  int tc_val = EEPROM.read(EEPROM_TIMING_CONTROL_ENABLED_ADDRESS);
  if (tc_val == TIMING_CONTROL_ENABLED)
  {
    http_content.replace("{{TC_LINK_PRIMARY_ON}}", "pure-button-primary");

  } else {
    http_content.replace("{{TC_LINK_PRIMARY_ON}}", "");
  }
  if (tc_val == TIMING_CONTROL_DISABLED)
  {
    http_content.replace("{{TC_LINK_PRIMARY_OFF}}", "pure-button-primary");

  } else {
    http_content.replace("{{TC_LINK_PRIMARY_OFF}}", "");
  }

  http_content.replace("{{TRANSITION_TIME}}", (String)default_transitiontime);

  int ls_val = EEPROM.read(EEPROM_LAST_STATE_STARTUP_ADDRESS);
  if (ls_val == LAST_STATE_STARTUP_LIGHT_LAST_STATE)
  {
    http_content.replace("{{STARTUP_SELECTED_LS_0}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{STARTUP_SELECTED_LS_0}}", "");
  }

  if (ls_val == LAST_STATE_STARTUP_LIGHT_ON_STATE)
  {
    http_content.replace("{{STARTUP_SELECTED_ON_1}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{STARTUP_SELECTED_ON_1}}", "");
  }

  if (ls_val == LAST_STATE_STARTUP_LIGHT_OFF_STATE)
  {
    http_content.replace("{{STARTUP_SELECTED_OFF_2}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{STARTUP_SELECTED_OFF_2}}", "");
  }

  // scene
  int sc_val = EEPROM.read(EEPROM_SCENE_ADDRESS);
  if (sc_val == SCENE_RELEAX)
  {
    http_content.replace("{{SCENE_SELECTED_RELAX_0}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{SCENE_SELECTED_RELAX_0}}", "");
  }
  if (sc_val == SCENE_BRIGHT)
  {
    http_content.replace("{{SCENE_SELECTED_BRIGHT_1}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{SCENE_SELECTED_BRIGHT_1}}", "");
  }
  if (sc_val == SCENE_NIGHTLY)
  {
    http_content.replace("{{SCENE_SELECTED_NIGHT_2}}", "selected=\"selected\"");
  } else {
    http_content.replace("{{SCENE_SELECTED_NIGHT_2}}", "");
  }

  // Wifi settings
  http_content.replace("{{WIFI_SSID}}", WiFi.SSID());

  // Network settings
  uint8_t dip = EEPROM.read(EEPROM_DYNAMIC_IP_ADDRESS);
  if (dip)
  {
    http_content.replace("{{DIP_LINK_ON_PRIMARY}}", "pure-button-primary");
    http_content.replace("{{DIP_LINK_OFF_PRIMARY}}", "");
  } else {
    http_content.replace("{{DIP_LINK_OFF_PRIMARY}}", "pure-button-primary");
    http_content.replace("{{DIP_LINK_ON_PRIMARY}}", "");
  }

  // network config
  http_content.replace("{{WIFI_CFG_IP}}", WiFi.localIP().toString());
  http_content.replace("{{WIFI_CFG_GW}}", WiFi.gatewayIP().toString());
  http_content.replace("{{WIFI_CFG_NM}}", WiFi.subnetMask().toString());
  http_content.replace("{{WIFI_CFG_DNS}}", WiFi.dnsIP().toString());

  // add the current ip address to the page
  http_content.replace("{{IP_ADDRESS}}", WiFi.localIP().toString());
  
  // set the pwm values
  http_content.replace("{{PWM_MIN}}", (String)PWM_MIN);
  http_content.replace("{{PWM_MAX}}", (String)PWM_MAX);

  return http_content;
}

//********************************//

String loadSPIFFSFile(String fname)
{
  File file = SPIFFS.open(fname, "r");
  if (!file)
  {
    Serial.println("Failed to open file " + fname);
    return "";
  }
  String contents = file.readString();
  file.close();
  return contents; 
}

//********************************//

String getIndexHTMLTop()
{
  // load file
  return loadSPIFFSFile("/index_template_top.html");
}

String getIndexHTMLMiddle()
{
  // load file
  return loadSPIFFSFile("/index_template_middle.html");
}

String getIndexHTMLBottom()
{
  // load file
  return loadSPIFFSFile("/index_template_bottom.html");
}

//********************************//

String getConfigHTML()
{
  // load file
  return loadSPIFFSFile("/config_template.html");
}

//********************************//

String getTCDataEditHTML()
{
  // load file
  return loadSPIFFSFile("/tc_data_edit.html");
}

//********************************//

String getLightControlHTML()
{
  // load file
  return loadSPIFFSFile("/light_control_template.html");
}

//********************************//

String formatBytes(size_t bytes)
{
  if (bytes < 1024)
  {
    return String(bytes) + " B";
  } else if (bytes < (1024 * 1024))
  {
    return String(bytes / 1024.0) + " KB";
  } else if (bytes < (1024 * 1024 * 1024))
  {
    return String(bytes / 1024.0 / 1024.0) + " MB";
  } else {
    return String(bytes / 1024.0 / 1024.0 / 1024.0) + " GB";
  }
}

//********************************//

void test_pwm_main()
{
  if (test_pwm == false)
  {
    return;
  } else if ((test_pwm_lastcheck_ms + PWM_TEST_INTERVA_MS) <= millis())
  {
    test_pwm_lastcheck_ms = millis();

    switch (test_pwm_state)
    {
      // ----------------------- //
      case TEST_PWM_STATE_INIT:
        // disable the lights
        for (uint8_t i = 0; i < LIGHTS_COUNT; i++)
        {
          light_state[i]    = false;
          bri[i]            = 0;
          current_bri[i]    = 0;
          current_pwm[i]    = 0;
          transitiontime[i] = 0;
          process_lightdata(i, transitiontime[i]);
        }

        light_state[0] = true;
        test_pwm_state = TEST_PWM_STATE_CH1_INC;
        break;

      // ----------------------- //
      case TEST_PWM_STATE_CH1_INC:

        if (bri[0] < 255)
        {
          bri[0] += TEST_PWM_CHG_CNT;
          transitiontime[0] = 1;
          process_lightdata(0, transitiontime[0]);

        } else {

          test_pwm_state = TEST_PWM_STATE_CH1_DEC;
        }
        break;

      case TEST_PWM_STATE_CH1_DEC:

        if (bri[0] > 0)
        {
          bri[0] -= TEST_PWM_CHG_CNT;
          transitiontime[0] = 1;
          process_lightdata(0, transitiontime[0]);

        } else {

          light_state[0] = false;
          bri[0]            = 0;
          current_bri[0]    = 0;
          current_pwm[0]    = 0;
          transitiontime[0] = 0;
          process_lightdata(0, transitiontime[0]);

          light_state[1] = true;
          test_pwm_state = TEST_PWM_STATE_CH2_INC;
        }
        break;

      // ----------------------- //
      case TEST_PWM_STATE_CH2_INC:

        if (bri[1] < 255)
        {
          bri[1] += TEST_PWM_CHG_CNT;
          transitiontime[1] = 1;
          process_lightdata(1, transitiontime[1]);

        } else {

          test_pwm_state = TEST_PWM_STATE_CH2_DEC;
        }
        break;

      case TEST_PWM_STATE_CH2_DEC:

        if (bri[1] > 0)
        {
          bri[1] -= TEST_PWM_CHG_CNT;
          transitiontime[1] = 1;
          process_lightdata(1, transitiontime[1]);

        } else {

          light_state[1] = false;
          bri[1]            = 0;
          current_bri[1]    = 0;
          current_pwm[1]    = 0;
          transitiontime[1] = 0;
          process_lightdata(1, transitiontime[1]);

          light_state[2] = true;
          test_pwm_state = TEST_PWM_STATE_CH3_INC;
        }
        break;

      // ----------------------- //
      case TEST_PWM_STATE_CH3_INC:

        if (bri[2] < 255)
        {
          bri[2] += TEST_PWM_CHG_CNT;
          transitiontime[2] = 1;
          process_lightdata(2, transitiontime[2]);

        } else {

          test_pwm_state = TEST_PWM_STATE_CH3_DEC;
        }
        break;

      case TEST_PWM_STATE_CH3_DEC:

        if (bri[2] > 0)
        {
          bri[2] -= TEST_PWM_CHG_CNT;
          transitiontime[2] = 1;
          process_lightdata(2, transitiontime[2]);

        } else {

          light_state[2]    = false;
          bri[2]            = 0;
          current_bri[2]    = 0;
          current_pwm[2]    = 0;
          transitiontime[2] = 0;
          process_lightdata(2, transitiontime[2]);

          light_state[3] = true;
          test_pwm_state = TEST_PWM_STATE_CH4_INC;
        }
        break;

      // ----------------------- //
      case TEST_PWM_STATE_CH4_INC:

        if (bri[3] < 255)
        {
          bri[3] += TEST_PWM_CHG_CNT;
          transitiontime[3] = 1;
          process_lightdata(3, transitiontime[3]);

        } else {

          test_pwm_state = TEST_PWM_STATE_CH4_DEC;
        }
        break;

      case TEST_PWM_STATE_CH4_DEC:

        if (bri[3] > 0)
        {
          bri[3] -= TEST_PWM_CHG_CNT;
          transitiontime[3] = 1;
          process_lightdata(3, transitiontime[3]);

        } else {

          test_pwm = false;
          tc_enabled = tc_enabled_old;

          for (uint8_t i = 0; i < LIGHTS_COUNT; i++)
          {
            light_state[i]    = false;
            bri[i]            = 0;
            current_bri[i]    = 0;
            current_pwm[i]    = 0;
            transitiontime[i] = 0;
            process_lightdata(i, transitiontime[i]);
          }

          tc_reset();
          tc_update_main(); // load the tc if required

          test_pwm_state = TEST_PWM_STATE_INIT;
        }
        break;

      // ----------------------- //
      default:
        test_pwm_state = TEST_PWM_STATE_INIT;
    }

    Serial.println("---");
    for (uint8_t i = 0; i < LIGHTS_COUNT; i++)
    {
      Serial.println("light_state[" + (String)i + "]    = " + (String)light_state[i]);
      Serial.println("bri[" + (String)i + "]            = " + (String)bri[i]);
      Serial.println("current_bri[" + (String)i + "]    = " + (String)current_bri[i]);
      Serial.println("current_pwm[" + (String)i + "]    = " + (String)current_pwm[i]);
      Serial.println("transitiontime[" + (String)i + "] = " + (String)transitiontime[i]);
    }

  }

}

//********************************//