/** отображение времени через массив сегментов
 * 
 */
 
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <WiFiUdp.h>
#include <TM1637Display.h>

//*************************Изменить для себя *******************************************************
#define HourNightStart  22  //Время включения ночного режима
#define HourNightEnd  7     //Времы выключения ночного режима

// Module connection pins (Digital Pins)
#define CLK D7
#define DIO D6
// soft AP
const char* ssid = "test";
const char* passphrase = "test";

//
#define Key_Pr    5 //    ;Задержка на подавление дребезга короткого нажатия кнопок
#define Key_Long  50  //  ;Задержка на длительное нажатие (в циклах прерывания)

// рудимент от оригинала
//#define SH_CP  16
//#define ST_CP  5
//#define DS     4
//#define LED    14
// #define HV  12
//**********************************************************************************

TM1637Display display(CLK, DIO);
ESP8266WebServer server(80);

bool testWifi(void);
void setupAP(void);
void launchWeb(int webtype);
void createWebServer(int webtype);
bool GetNTP(void);
void DisplayTime(unsigned long epoch);
unsigned long sendNTPpacket(IPAddress& address);
void button_press(void);
void set_Mode(void);
int chk_dst(unsigned int year, unsigned int month, unsigned char day, unsigned char weekday, unsigned char hours);

char TIMEZONE;            // Часовой пояс

struct flag {
  unsigned char dst_enable: 1;  // вкл. перехода на летнее время
  unsigned char leadingzero:1;
};
  
union {
  unsigned char eflag;
  struct flag   fflag;
} test;

unsigned int  localPort = 2390;      // local port to listen for UDP packets
unsigned long ntp_time = 0;
long  t_correct        = 0;
unsigned long cur_ms   = 0;
unsigned long ms1      = 0;
unsigned long ms2      = 10000000UL;
unsigned long ms3      = 0;
unsigned long t_cur    = 0;
bool          points   = true;
// byte points = 1;
unsigned int err_count = 0;
uint16_t     my_s = 0;
uint16_t     my_m = 0;
uint16_t     my_h = 0;
String timestring;

uint8_t data[] = {0x0, 0x0, 0x0, 0x0};  // массив сегментов для отображения на индикторе

typedef enum { showtime, showdate } dispmode_t;
dispmode_t DisplayMode = showtime;

uint8_t     temp = 0;

uint16_t     ADCkey = 0;
uint8_t     Key_Timer;
uint8_t     Key_1_Count;
uint8_t     Key_2_Count;
uint8_t     Key_3_Count;

boolean   Key_1_Pr  = false;  //Кнопка 1 нажата (с учетом дребезга контактов)
boolean   Key_2_Pr  = false;  //Кнопка 2 нажата (с учетом дребезга контактов)
boolean   Key_3_Pr  = false;  //Кнопка 3 нажата (с учетом дребезга контактов)
boolean   Key_1_LP  = false;  //Кнопка 1 длительное удержание
boolean   Key_2_LP  = false;  //Кнопка 2 длительное удержание
boolean   Key_3_LP  = false;  //Кнопка 3 длительное удержание
boolean   Key_1_PrR = false;  //Отпускание кнопки 1 после короткого нажатия
boolean   Key_1_LPR = false;  //Отпускание кнопки 1 после длинного нажатия

String st;
String content;
int statusCode;

bool      LedON   = false;
bool      LedON_temp   = false;
bool      NtpNoConn   = false;

unsigned long Next_ms   = 3600000;

IPAddress timeServerIP;
const char* ntpServerName = "time.nist.gov";

const int NTP_PACKET_SIZE = 48;
byte packetBuffer[ NTP_PACKET_SIZE];
WiFiUDP udp;

void setup() {
  display.setBrightness(0x0f);  // 
 
  Serial.begin(115200);
  EEPROM.begin(512);
  delay(10);
  Serial.println("");
  Serial.println("");
  Serial.println("Startup");
  
  //read eeprom ssid and pass
  Serial.println("Reading EEPROM SSID");
  String esid = "XXXX";
//  for (int i=0; i<32; ++i){
//    esid += char(EEPROM.read(i));
//  }
  Serial.print("SSID: ");
  Serial.println(esid);
  String AP_SSID = esid;

  Serial.println("Reading EEPROM pass");
  String epass = "XXXX";
//  for (int i=32; i<96; ++i) {
//    epass += char(EEPROM.read(i));
//  }
  Serial.print("PASS: ");
  Serial.println(epass);
  String AP_PASS = epass;

  String etimezone = "3";
 // for (int i = 96; i<99; ++i){
 //   etimezone += char(EEPROM.read(i));
 // }
  Serial.print("Timezone: ");
  Serial.println(etimezone);
  TIMEZONE = etimezone.toInt();
  Serial.println(TIMEZONE);

  test.eflag = char(EEPROM.read(100));
  if (test.fflag.dst_enable) Serial.println("DST enabled");
  if (test.fflag.leadingzero) Serial.println("Leading Zero enabled");
  
  ADCkey = analogRead(A0); // проверяем нажатие кнопок по включению.
  Serial.print("ADC: ");
  Serial.println(ADCkey);
  if (ADCkey < 700) { //Если нажата хоть одна из кнопок
    setupAP();
  }

  if ( esid.length() > 1 ) {      //Поднимаем соединение вайфай и проверяем его
    WiFi.begin(esid.c_str(), epass.c_str());
    if (testWifi()) {
      launchWeb(0);
      //return;
    }
  }
 
  Serial.print("Free Memory: ");
  // Соединение с WiFi
  Serial.println(ESP.getFreeHeap());
  delay(1000);

  // Инициализация UDP соединения с NTP сервером
  Serial.println("Starting UDP");
  udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(udp.localPort());

//  digitalWrite(HV, HIGH);
}

bool testWifi(void) {
  int c = 0;
  Serial.println("Waiting for Wifi to connect");
  while ( c < 20 ) {
    if (WiFi.status() == WL_CONNECTED) {
      return true;
    }
    delay(500);
    Serial.print(WiFi.status());
    c++;
  }
  Serial.println("");
  Serial.println("Connect timed out");
  Serial.println("Reset ESP8266 ...");
  ESP.reset();
  return false;
}

void launchWeb(int webtype) {
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("Local IP: ");
  Serial.println(WiFi.localIP());
  Serial.print("SoftAP IP: ");
  Serial.println(WiFi.softAPIP());
  createWebServer(webtype);
  // Start the server
  server.begin();
  Serial.println("Server started");
}

void setupAP(void) {      //Функция поднятия точки доступа и настройки
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  int n = WiFi.scanNetworks();      //Сканируем видимые точки
  Serial.println("scan done");
  if (n == 0)
    Serial.println("no networks found");
  else
  {
    Serial.print(n);
    Serial.println(" networks found");
    for (int i = 0; i < n; ++i)         //Выводим найденные в терминал
    {
      // Print SSID and RSSI for each network found
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i));
      Serial.print(" (");
      Serial.print(WiFi.RSSI(i));
      Serial.print(")");
      Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");
      delay(10);
    }
  }
  Serial.println("");
  st = "<ol>";
  for (int i = 0; i < n; ++i)           //Выводим найденные в веб
  {
    // Print SSID and RSSI for each network found
    st += "<li>";
    st += WiFi.SSID(i);
    st += " (";
    st += WiFi.RSSI(i);
    st += ")";
    st += (WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*";
    st += "</li>";
  }
  st += "</ol>";
  delay(100);
  WiFi.softAP(ssid, passphrase, 6);   //Поднимаем точку доступа, не срабатывает создание точки со соими ссид и паролем
  Serial.println("softap");
  launchWeb(1);                       //Запускаем веб сервер
  Serial.println("over");
  while (1) {
    server.handleClient();
  }
}

void createWebServer(int webtype) {
  if ( webtype == 1 ) {
    server.on("/", []() {
      IPAddress ip = WiFi.softAPIP();
      String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
      content = "<!DOCTYPE HTML><html><HEAD><TITLE>NTP clock configuration page</TITLE><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></HEAD>";
      content += "<body>Hello from ESP8266 at ";
      content += ipStr;
      content += "<p>";
      content += st;
      content += "</p><form method='get' action='setting'><table><tr><td>SSID:  <input name='ssid' size=32>   Password: <input name='pass' size=64>";
      content += "<tr><td>TimeZone: <input name='timezone' size=3 value='2'><tr><td>Enable use DST <input name='dst' TYPE=checkbox VALUE=ON>";
      content += "<tr><td>Enable leading zero  <input name='leadingzero' type=checkbox value=ON></table> <input type='submit'></form>";
      content += "</body></html>";
      server.send(200, "text/html", content);
    });
    server.on("/setting", []() {
      String qsid = server.arg("ssid");
      String qpass = server.arg("pass");
      String qtimezone = server.arg("timezone");
      String qdst = server.arg("dst");
      String qleadingzero = server.arg("leadingzero");
      if (qsid.length() > 0 && qpass.length() > 0 && qtimezone.length() > 0) {
        Serial.println("clearing eeprom");
        for (int i = 0; i < 99; ++i) {
          EEPROM.write(i, 0);
        }
        Serial.println(qsid);
        Serial.println("");
        Serial.println(qpass);
        Serial.println("");

        Serial.println("writing eeprom ssid:");
        for (int i = 0; i < qsid.length(); ++i)
        {
          EEPROM.write(i, qsid[i]);
          Serial.print("Wrote: ");
          Serial.println(qsid[i]);
        }
        Serial.println("writing eeprom pass:");
        for (int i = 0; i < qpass.length(); ++i)
        {
          EEPROM.write(32 + i, qpass[i]);
          Serial.print("Wrote: ");
          Serial.println(qpass[i]);
        }
        for (int i = 0; i < qtimezone.length(); ++i)
        {
          EEPROM.write(96 + i, qtimezone[i]);
          Serial.print("Wrote: ");
          Serial.println(qtimezone[i]);
        }

        if (qdst == "ON") test.fflag.dst_enable = 1; else test.fflag.dst_enable = 0;
        if (qleadingzero = "ON") test.fflag.leadingzero = 1; else test.fflag.leadingzero = 0;
        if (test.fflag.dst_enable) Serial.println("DST enabled");
        if (test.fflag.leadingzero) Serial.println("Leading Zero enabled");

        EEPROM.write(100, test.eflag);
        
        EEPROM.commit();
        content = "{\"Success\":\"saved to eeprom... reset to boot into new wifi\"}";
        statusCode = 200;
      } else {
        content = "{\"Error\":\"404 not found\"}";
        statusCode = 404;
        Serial.println("Sending 404");
      }
      server.send(statusCode, "application/json", content);

    });
  } else if (webtype == 0) {
    server.on("/", []() {
      IPAddress ip = WiFi.localIP();
      String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
      //server.send(200, "application/json", "{\"IP\":\"" + ipStr + "\"}");
      content = "<!DOCTYPE HTML><html><HEAD><TITLE>NTP clock configuration page</TITLE><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></HEAD><body>";
      content += "NTP Nixie Clock on ";
      content += ipStr;
      content += "<br>";
      content += timestring;
      content += "</body></html>";
      server.send(200, "text/html", content);
    });
    server.on("/cleareeprom", []() {
      content = "<!DOCTYPE HTML>\r\n<html>";
      content += "<p>Clearing the EEPROM</p></html>";
      server.send(200, "text/html", content);
      Serial.println("clearing eeprom");
      for (int i = 0; i < 96; ++i) {
        EEPROM.write(i, 0);
      }
      EEPROM.commit();
    });
  }
}




void loop() {
  server.handleClient();
  cur_ms       = millis();
  t_cur        = cur_ms / 1000;
  // Каждые 24 часа считываем время в интернете
  if ( cur_ms < ms2 || (cur_ms - ms2) > Next_ms ) {
    err_count++;
    // Делаем три  попытки синхронизации с интернетом
    NtpNoConn = false;
    if ( GetNTP() ) {
      ms2       = cur_ms;
      err_count = 0;
      t_correct = ntp_time - t_cur;
      NtpNoConn = true;
      Next_ms   = 86400000;
    }
    else {
      Next_ms = 60000; //соединяемся через минуту
    }
  }

  // Каждые  секунду выдаем время
  if ( cur_ms < ms1 || (cur_ms - ms1) > 1000 ) {
    ms1 = cur_ms;
    ntp_time    = t_cur + t_correct;
    points = !points; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    Serial.println(points);
    DisplayTime(ntp_time);

  }

  //опрос кнопок
  if ( cur_ms < ms3 || (cur_ms - ms3) > 5 ) {
    ms3 = cur_ms;
    ADCkey = analogRead(A0);
    button_press();
    set_Mode();
  }


  // Если нет соединения с интернетом, перезагружаемся
  if ( err_count > 10 ) {
    Serial.println("NTP connect false");
    Serial.println("Reset ESP8266 ...");
    ESP.reset();

  }
  delay(100);
}



/**
 * Выдача текущего времени на индикатор
 */
void DisplayTime(unsigned long epoch) {
  
  epoch = epoch + TIMEZONE*3600;  
  
// my_s = epoch % 60;
  my_m = ( epoch / 60 ) % 60;
  my_h = ( epoch / 3600 ) % 24;

/**
  *   изменение яркости дисплея
  */

  if ( my_h > HourNightStart or my_h < HourNightEnd )
    {display.setBrightness(0x08);}  // меньше 8 индикатор не работает
  else
    {display.setBrightness(0x0f);}
  
/*   display.showNumberDec(my_h/10,true,1,0); //крайне левое знакоместо
   display.showNumberDec(my_h%10,true,1,1);
   display.showNumberDec(my_m/10,true,1,2);
   display.showNumberDec(my_m%10,true,1,3); //крайне правое знакоместо
*/

  data[0]= display.encodeDigit(my_h/10);     //крайне левое знакоместо
  
  if ( points = true )
    {data[1]= display.encodeDigit(my_h%10); //единицы часов + точка
    data[1]= data[1] | 0x80;
 //   points = false; 
 }
  else 
  //if (points = 1 )
    {data[1]= display.encodeDigit(my_h%10);
 // points = true; 
 }

    Serial.print(points);
    
  data[2]= display.encodeDigit(my_m/10);
  data[3]= display.encodeDigit(my_m%10);

display.setSegments(data);

}


/**
 * Посылаем и парсим запрос к NTP серверу
 */
bool GetNTP(void) {
  WiFi.hostByName(ntpServerName, timeServerIP);
  sendNTPpacket(timeServerIP);
  delay(1000);

  int cb = udp.parsePacket();
  if (!cb) {
    Serial.println("No packet yet");
    return false;
  }
  else {
    Serial.print("packet received, length=");
    Serial.println(cb);
    // Читаем пакет в буфер
    udp.read(packetBuffer, NTP_PACKET_SIZE);
    // 4 байта начиная с 40-го сождержат таймстамп времени - число секунд
    // от 01.01.1900
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // Конвертируем два слова в переменную long
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    // Конвертируем в UNIX-таймстамп (число секунд от 01.01.1970
    const unsigned long seventyYears = 2208988800UL;
    ntp_time = secsSince1900 - seventyYears;

    // Делаем поправку на местную тайм-зону
//    ntp_time = epoch + (timezone + dst) * 3600;

    
    Serial.print("Unix time = ");
    Serial.println(ntp_time);
  }
  return true;
}



/**
 * Посылаем запрос NTP серверу на заданный адрес
 */
unsigned long sendNTPpacket(IPAddress& address)
{
  Serial.println("sending NTP packet...");
  // Очистка буфера в 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Формируем строку зыпроса NTP сервера
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // Посылаем запрос на NTP сервер (123 порт)
  udp.beginPacket(address, 123);
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}


void set_Mode(void)
{

  if (Key_1_Pr) { // нажатие 1й кнопки вкл/выкл подсветки
    if (DisplayMode == showdate) DisplayMode = showtime;
    else DisplayMode = showdate;
    Key_1_Pr = 0;
  }

  // Реакция на кнопки

  if (Key_2_Pr) { //нажатин на вторую кнопку - запрос времени
    NtpNoConn = false;
    if ( GetNTP() ) {
      ms2       = cur_ms;
      t_correct = ntp_time - t_cur;
      NtpNoConn = true;
      Next_ms   = 86400000;
      Serial.print("t_correct = ");
      Serial.println(t_correct);
    }

    Key_2_Pr = 0;
  }

  if (Key_1_LP == 1) {
    Key_1_LP = 0;
  }



}

// Опрос кнопок процедура візівается 200 раз в секунду
void button_press(void)
{

  if (ADCkey < 500)  {
    Key_Timer = 0;
    if (Key_1_Count == 255)  {
      return;
    } else {
      Key_1_Count++;
      if (Key_1_Count == Key_Pr) Key_1_Pr = 1;
      if (Key_1_Count == Key_Long) Key_1_LP = 1;
      return;
    }
  } else {
    Key_1_Count = 0;
    if (Key_1_Pr)   Key_1_PrR = 1;
    if (!Key_1_LP) {
      Key_1_LPR = 1;
      Key_1_PrR = 0;
    } else {
      Key_1_Pr = 0;
      Key_1_LP = 0;
    }
    //  return;

  }


  if (ADCkey < 600) {
    Key_Timer = 0;
    if (Key_2_Count == 255) {
      return;
    } else {
      Key_2_Count++;
      if (Key_2_Count == Key_Pr) Key_2_Pr = 1;
      if (Key_2_Count == Key_Long) Key_2_LP = 1;
      return;
    }

  } else {
    Key_2_Count = 0;
    Key_2_Pr = 0;
    Key_2_LP = 0;
    //  return;

  }
  if (ADCkey < 700) {
    Key_Timer = 0;
    if (Key_3_Count == 255) {
      return;
    } else {
      Key_3_Count++;
      if (Key_3_Count == Key_Pr) Key_3_Pr = 1;
      if (Key_3_Count == Key_Long) Key_3_LP = 1;
      return;
    }


  } else {
    Key_3_Count = 0;
    Key_3_Pr = 0;
    Key_3_LP = 0;
    //  return;
  }
}

int chk_dst(unsigned int year, unsigned int month, unsigned char day, unsigned char weekday, unsigned char hours) {
  int set_dst = 0;
  if (test.fflag.dst_enable)  {

    switch (month) {
      case 4:
      case 5:
      case 6:
      case 7:
      case 8:
      case 9:
        set_dst = 1;
        break;

      case 1:
      case 2:
      case 11:
      case 12:
        set_dst = 0;
        break;

      case 3:
        if ((day - weekday) > 24) {
          if ((weekday) != 0) {
            set_dst = 1;
          } else {
            if ((hours) < 1) set_dst = 0;
            else set_dst = 1;
          }
        } else {
          set_dst = 0;
        }
        break;

      case 10:
        if ((day - weekday) > 24) {
          if ((weekday) != 0) {
            set_dst = 0;
          } else {
            if ((hours) < 1) set_dst = 1;
            else set_dst = 0;
          }
        } else {
          set_dst = 1;
        }
        break;
    }
  }
  return set_dst;
}

