#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266WebServer.h>
#include <time.h>

// Настройки для OLED-дисплея
#define SCREEN_WIDTH 128 // Ширина дисплея OLED в пикселях
#define SCREEN_HEIGHT 64 // Высота дисплея OLED в пикселях
#define OLED_RESET -1    // На некоторых модулях контроллер сброса не используется
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Настройки WiFi
const char* ssid = "Beeline_2G_A7F18A"; // Имя сети WiFi
const char* password = "iD2tAXJN3e";    // Пароль от WiFi

// Настройки NTP
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 10800, 60000); // Смещение для Москвы +10800 секунд (3 часа)

// Настройки зуммера
const int buzzerPin = 14; // Пин, к которому подключён зуммер (GPIO14)

// Настройки веб-сервера
ESP8266WebServer server(80);

// Переменные для хранения времени будильника
int alarmHour = -1;
int alarmMinute = -1;

// IP-адрес сервера
String serverIP = "";

// Таймеры для ненавязчивого обновления дисплея и проверки будильника
unsigned long previousDisplayMillis = 0;
const unsigned long displayInterval = 50; // Более частое обновление дисплея

unsigned long previousAlarmCheckMillis = 0;
const unsigned long alarmCheckInterval = 1000; // Проверка будильника каждые 1000 мс

// Флаг для предотвращения многократных срабатываний будильника в одну секунду
bool alarmTriggered = false;

// Структура для капель дождя
struct Raindrop {
  int x;
  int y;
};

// Параметры для дождя
const int maxDrops = 45; // Уменьшили количество капель на 10%
Raindrop drops[maxDrops];

// Функция для форматирования чисел с ведущими нулями
String formatTwoDigits(int digits) {
  if (digits < 10) {
    return "0" + String(digits);
  }
  return String(digits);
}

// HTML и CSS для веб-интерфейса
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP8266 Clock</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
      background-color: #2b2b2b;
      color: #e0e0e0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
      text-align: center;
      padding: 40px;
    }
    .container {
      background-color: #3c3c3c;
      padding: 20px;
      border-radius: 10px;
      display: inline-block;
    }
    h1 {
      color: #f2f2f7;
    }
    .time {
      font-size: 2em;
      margin: 20px 0;
    }
    .date {
      font-size: 1.5em;
      margin-bottom: 30px;
    }
    input[type=number] {
      width: 60px;
      padding: 10px;
      margin: 5px;
      border: 1px solid #555555;
      border-radius: 5px;
      background-color: #4a4a4a;
      color: #e0e0e0;
      text-align: center;
      font-size: 1em;
    }
    input[type=submit] {
      padding: 10px 20px;
      margin-top: 10px;
      border: none;
      border-radius: 5px;
      background-color: #007aff;
      color: white;
      font-size: 1em;
      cursor: pointer;
    }
    input[type=submit]:hover {
      background-color: #0051a8;
    }
    .ip-address {
      margin-top: 20px;
      font-size: 0.9em;
      color: #a0a0a0;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>ESP8266 Clock</h1>
    <div class="time">--:--:--</div>
    <div class="date">--/--/----</div>
    <h2>Set Alarm</h2>
    <form action="/setAlarm" method="get">
      <input type="number" name="hour" min="0" max="23" placeholder="HH" required> :
      <input type="number" name="minute" min="0" max="59" placeholder="MM" required><br><br>
      <input type="submit" value="Set Alarm">
    </form>
  </div>
  <div class="ip-address">Server IP: --.--.--.--</div>
  
  <script>
    function updateTime() {
      fetch('/time')
        .then(response => response.json())
        .then(data => {
          document.querySelector('.time').innerText = data.time;
          document.querySelector('.date').innerText = data.date;
          document.querySelector('.ip-address').innerText = "Server IP: " + data.ip;
        });
    }
    setInterval(updateTime, 1000);
    window.onload = updateTime;
  </script>
</body>
</html>
)rawliteral";

// Функция для проверки аутентификации
bool checkAuthentication() {
  if (!server.authenticate("admin", "35539900")) {
    server.requestAuthentication();
    return false;
  }
  return true;
}

// Обработчик для главной страницы с аутентификацией
void handleRootWithAuth() {
  if (!checkAuthentication()) {
    return;
  }
  server.send_P(200, "text/html", index_html);
}

// Обработчик для получения текущего времени, даты и IP в формате JSON с аутентификацией
void handleTimeWithAuth() {
  if (!checkAuthentication()) {
    return;
  }

  unsigned long epochTime = timeClient.getEpochTime();
  time_t rawTime = epochTime;
  struct tm* ptm = gmtime(&rawTime); // Смещение уже учтено в NTPClient

  String formattedDate = String(ptm->tm_mday) + "/" +
                         String(ptm->tm_mon + 1) + "/" +
                         String(ptm->tm_year + 1900);
  String formattedTime = formatTwoDigits(ptm->tm_hour) + ":" +
                         formatTwoDigits(ptm->tm_min) + ":" +
                         formatTwoDigits(ptm->tm_sec);

  String json = "{\"date\":\"" + formattedDate + "\",\"time\":\"" + formattedTime + "\",\"ip\":\"" + serverIP + "\"}";

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

// Обработчик для установки будильника с аутентификацией
void handleSetAlarmWithAuth() {
  if (!checkAuthentication()) {
    return;
  }

  if (server.hasArg("hour") && server.hasArg("minute")) {
    alarmHour = server.arg("hour").toInt();
    alarmMinute = server.arg("minute").toInt();
    String message = "Alarm set for " + String(formatTwoDigits(alarmHour)) + ":" + String(formatTwoDigits(alarmMinute));
    server.send(200, "text/html", "<html><body><script>alert('" + message + "'); window.location.href='/';</script></body></html>");
    Serial.println(message);
  } else {
    server.send(400, "text/html", "Bad Request");
    Serial.println("Bad Request: Missing hour or minute");
  }
}

// Инициализация дождя
void initializeRain() {
  for (int i = 0; i < maxDrops; i++) {
    drops[i].x = random(0, SCREEN_WIDTH);
    drops[i].y = random(0, SCREEN_HEIGHT);
  }
}

// Обновляем движение дождя
void updateRain() {
  for (int i = 0; i < maxDrops; i++) {
    drops[i].y += 5; // Скорость падения

    // Заново инициализируем капли, если они ушли за экран
    if (drops[i].y >= SCREEN_HEIGHT) {
      drops[i].x = random(0, SCREEN_WIDTH);
      drops[i].y = 0;
    }
  }
}

// Функция для отображения дождя на OLED
void drawRain() {
  for (int i = 0; i < maxDrops; i++) {
    display.drawLine(drops[i].x, drops[i].y, drops[i].x, drops[i].y + 3, SSD1306_WHITE);
  }
}

// Основная функция для отображения сообщения о срабатывании будильника
void displayAlarmTriggered(String currentTime) {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  int16_t x1, y1;
  uint16_t width, height;
  String message = "Alarm Triggered!";
  display.getTextBounds(message, 0, 0, &x1, &y1, &width, &height);
  int centeredX = (SCREEN_WIDTH - width) / 2;
  display.setCursor(centeredX, 0);
  display.println(message);
  message = "Time: " + currentTime;
  display.getTextBounds(message, 0, 0, &x1, &y1, &width, &height);
  centeredX = (SCREEN_WIDTH - width) / 2;
  display.setCursor(centeredX, 16);
  display.println(message);
  display.display();
}

// Функция для отображения сообщения о сбросе будильника
void displayAlarmReset() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  int16_t x1, y1;
  uint16_t width, height;
  String message = "Alarm Reset";
  display.getTextBounds(message, 0, 0, &x1, &y1, &width, &height);
  int centeredX = (SCREEN_WIDTH - width) / 2;
  display.setCursor(centeredX, 0);
  display.println(message);
  message = "No Active Alarm";
  display.getTextBounds(message, 0, 0, &x1, &y1, &width, &height);
  centeredX = (SCREEN_WIDTH - width) / 2;
  display.setCursor(centeredX, 16);
  display.println(message);
  display.display();
}

void setup() {
  Serial.begin(115200);
  delay(10);

  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, LOW);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("Failed to initialize OLED display"));
    while (true);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  String message = "Starting device...";
  int16_t x1, y1;
  uint16_t width, height;
  display.getTextBounds(message, 0, 0, &x1, &y1, &width, &height);
  int centeredX = (SCREEN_WIDTH - width) / 2;
  int centeredY = (SCREEN_HEIGHT - height) / 2;

  display.setCursor(centeredX, centeredY);
  display.println(message);
  display.display();
  delay(1000);

  Serial.println("Starting device...");

  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  int dotCount = 0;
  const int maxDots = 3;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    dotCount = (dotCount + 1) % (maxDots + 1);

    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    String connectingMessage = "Connecting to WiFi";
    display.getTextBounds(connectingMessage, 0, 0, &x1, &y1, &width, &height);
    centeredX = (SCREEN_WIDTH - width) / 2;
    centeredY = (SCREEN_HEIGHT - height) / 2;

    display.setCursor(centeredX, centeredY);
    display.print(connectingMessage);
    for (int i = 0; i < dotCount; i++) {
      display.print(".");
    }
    display.display();
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  serverIP = WiFi.localIP().toString();

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  String wifiConnectedMessage = "WiFi connected";
  display.getTextBounds(wifiConnectedMessage, 0, 0, &x1, &y1, &width, &height);
  centeredX = (SCREEN_WIDTH - width) / 2;
  centeredY = (SCREEN_HEIGHT - height) / 3;

  display.setCursor(centeredX, centeredY);
  display.println(wifiConnectedMessage);
  String ipMessage = "IP: " + serverIP;
  display.getTextBounds(ipMessage, 0, 0, &x1, &y1, &width, &height);
  centeredX = (SCREEN_WIDTH - width) / 2;
  centeredY = 2 * (SCREEN_HEIGHT - height) / 3;

  display.setCursor(centeredX, centeredY);
  display.println(ipMessage);
  display.display();
  delay(2000);

  digitalWrite(buzzerPin, HIGH);
  delay(200);
  digitalWrite(buzzerPin, LOW);
  delay(200);

  timeClient.begin();
  timeClient.update();

  if (timeClient.getEpochTime() > 0) {
    Serial.println("Time successfully obtained");
    digitalWrite(buzzerPin, HIGH);
    delay(200);
    digitalWrite(buzzerPin, LOW);
  } else {
    Serial.println("Failed to obtain time");
  }

  server.on("/", handleRootWithAuth);
  server.on("/time", handleTimeWithAuth);
  server.on("/setAlarm", handleSetAlarmWithAuth);

  server.onNotFound([]() {
    if (!server.authenticate("admin", "35539900")) {
      server.requestAuthentication();
      return;
    }
    server.send(404, "text/plain", "404: Not found");
  });

  server.begin();
  Serial.println("HTTP server started");

  initializeRain();
}

void loop() {
  server.handleClient();
  timeClient.update();

  unsigned long epochTime = timeClient.getEpochTime();
  time_t rawTime = epochTime;
  struct tm* ptm = gmtime(&rawTime);

  String formattedDate = String(ptm->tm_mday) + "/" +
                         String(ptm->tm_mon + 1) + "/" +
                         String(ptm->tm_year + 1900);
  String formattedTime = formatTwoDigits(ptm->tm_hour) + ":" +
                         formatTwoDigits(ptm->tm_min);
  String formattedSeconds = formatTwoDigits(ptm->tm_sec);

  if (millis() - previousDisplayMillis >= displayInterval) {
    previousDisplayMillis = millis();

    display.clearDisplay();

    // Отрисовка часов и минут
    display.setTextSize(3); // Увеличенные часы и минуты
    display.setTextColor(SSD1306_WHITE);
    int16_t x1, y1;
    uint16_t width, height;
    display.getTextBounds(formattedTime, 0, 0, &x1, &y1, &width, &height);
    int centeredX = (SCREEN_WIDTH - width - 24) / 2; // Учет длины секунд
    display.setCursor(centeredX, 0);
    display.print(formattedTime);

    // Отрисовка секунд
    display.setTextSize(1); // Уменьшенные секунды
    display.setCursor(centeredX + width + 5, 12); // Подстройка позиции
    display.print(":" + formattedSeconds);

    // Отрисовка даты
    display.setTextSize(1);
    display.getTextBounds(formattedDate, 0, 0, &x1, &y1, &width, &height);
    centeredX = (SCREEN_WIDTH - width) / 2;
    display.setCursor(centeredX, 36);
    display.println(formattedDate);

    // Обновление и отрисовка дождя
    updateRain();
    drawRain();

    // Отображение
    display.display();
  }

  if (alarmHour != -1 && alarmMinute != -1) {
    if (ptm->tm_hour == alarmHour && ptm->tm_min == alarmMinute && ptm->tm_sec == 0) {
      if (!alarmTriggered) {
        digitalWrite(buzzerPin, HIGH);
        Serial.println("Alarm Triggered!");
        displayAlarmTriggered(formattedTime + ":" + formattedSeconds);
        alarmTriggered = true;
      }
    } else {
      alarmTriggered = false;
    }
  }
} добавь в код эффект который будет при смене часов и минут имитировать эффект того что одна цифра проваливается а другая падает на это же место сверху. эффект должен работать токо с той цифрой которая должна измениться остальной код должен работать в штатном режиме