#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <SoftwareSerial.h>
#include <DS3231.h>

#define SOIL_MOISTURE_PIN A0  // YL-69 аналоговый выход (A0 -> Arduino A0)
#define MHZ19_TX_PIN 3        // MH-Z19 TX пин
#define MHZ19_RX_PIN 2        // MH-Z19 RX пин
#define RELAY_1_PIN 8         // Реле канал 1 (Лампа)
#define RELAY_2_PIN 10        // Реле канал 2 (Шторы)
#define RELAY_3_PIN 11        // Реле канал 3 (Насос)
#define RELAY_4_PIN 12        // Реле канал 4 (Вентилятор)

// Определения для RGB светодиода
#define LED_COM 7
#define LED_R 9
#define LED_G 6
#define LED_B 5

// Определения для реле
#define LED_RELAY RELAY_1_PIN
#define CURTAINS_RELAY RELAY_2_PIN
#define PUMP_RELAY RELAY_3_PIN
#define FAN_RELAY RELAY_4_PIN

// Настройка датчиков
#define BME280_I2C_ADDR 0x76  // Адрес BME280 (может быть 0x76 или 0x77)
#define LCD_I2C_ADDR 0x27     // Адрес LCD дисплея (обычно 0x27 или 0x3F)
#define DISPLAY_INTERVAL 5000
#define TIME_UPDATE_INTERVAL 1000

// Инициализация объектов
SoftwareSerial mhz19Serial(MHZ19_TX_PIN, MHZ19_RX_PIN); // TX, RX
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, 20, 4);  // 20x4 LCD дисплей
Adafruit_BME280 bme;  // BME280 датчик
DS3231 rtc;  // DS3231 часы реального времени

enum DisplayMode {
  TIME_DATE,
  SOIL_MOISTURE,
  AIR_DATA,
  CO2_LEVEL,
  DEVICES_STATE
};

DisplayMode currentDisplayMode = SOIL_MOISTURE;

// Целевые значения
int targetTemperature = 25;
int tempTolerance = 3;
int targetSoilMoisture = 50;
int soilTolerance = 5;
int targetCO2Level = 800;
int co2Tolerance = 200;

// Настройки времени
int curtainsOpenHour = 8;
int curtainsOpenMinute = 0;
int curtainsCloseHour = 20;
int curtainsCloseMinute = 0;
int lampOnHour = 8;
int lampOnMinute = 30;
int lampOffHour = 21;
int lampOffMinute = 0;

// Флаги состояния
bool isTimeSet = false;
bool ledState = false;
bool curtainsState = false;
bool pumpState = false;
bool fanState = false;

// Калибровка датчика почвы
int drySoilValue = 1000;
int wetSoilValue = 200;

// Временные переменные
unsigned long lastDisplayChange = 0;
unsigned long lastTimeUpdate = 0;
unsigned long lastCO2Reading = 0;
unsigned long co2ReadInterval = 5000;  // Интервал чтения CO2 (5 секунд)

// Буфер для CO2
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
unsigned char response[9];
int co2Level = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  
  // Инициализация LCD дисплея
  lcd.init();
  lcd.backlight();
  
  // Инициализация BME280
  if (!bme.begin(BME280_I2C_ADDR)) {
    Serial.println(F("BME280 not found!"));
    lcd.setCursor(0, 0);
    lcd.print(F("BME280 not found!"));
  }
  
  // Инициализация MH-Z19
  mhz19Serial.begin(9600);
  
  // Настройка пинов
  pinMode(RELAY_1_PIN, OUTPUT);
  pinMode(RELAY_2_PIN, OUTPUT);
  pinMode(RELAY_3_PIN, OUTPUT);
  pinMode(RELAY_4_PIN, OUTPUT);
  
  // Настройка пинов RGB светодиода
  pinMode(LED_COM, OUTPUT);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  
  // Установка реле в выключенное состояние (HIGH для выключения)
  digitalWrite(RELAY_1_PIN, HIGH);
  digitalWrite(RELAY_2_PIN, HIGH);
  digitalWrite(RELAY_3_PIN, HIGH);
  digitalWrite(RELAY_4_PIN, HIGH);
  
  // Установка RGB светодиода
  digitalWrite(LED_COM, HIGH); // Общий анод
  digitalWrite(LED_R, HIGH);   // Выключен (для общего анода HIGH = выкл)
  digitalWrite(LED_G, HIGH);
  digitalWrite(LED_B, HIGH);
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Fitodomik System"));
  lcd.setCursor(0, 1);
  lcd.print(F("Initializing..."));
  delay(1000);
  
  // Запрос времени с компьютера
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Waiting for"));
  lcd.setCursor(0, 1);
  lcd.print(F("setup..."));
  
  Serial.println(F("READY"));
  displaySoilMoistureData();
}

void loop() {
  processSerialCommands();
  updateTime();
  readCO2();
  controlDevices();
  updateCO2LED();
  
  // Автоматическое переключение экранов
  if (millis() - lastDisplayChange >= DISPLAY_INTERVAL) {
    lastDisplayChange = millis();
    switchToNextDisplay();
  }
}

void updateTime() {
  if (isTimeSet) {
    if (millis() - lastTimeUpdate >= TIME_UPDATE_INTERVAL) {
      lastTimeUpdate = millis();
      
      if (currentDisplayMode == TIME_DATE) {
        displayTimeDate();
      }
    }
  }
}

void processSerialCommands() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    command.toUpperCase();
    
    // Команды для настройки времени
    if (command.startsWith("TIME:")) {
      setTimeFromString(command.substring(5));
      isTimeSet = true;
      Serial.println(F("TIME_OK"));
    } 
    // Команды для настройки параметров
    else if (command.startsWith("SET:TEMP:")) {
      targetTemperature = command.substring(9).toInt();
      Serial.println(F("TEMP_OK"));
    } 
    else if (command.startsWith("SET:TEMP_TOL:")) {
      tempTolerance = command.substring(13).toInt();
      Serial.println(F("TEMP_TOL_OK"));
    } 
    else if (command.startsWith("SET:SOIL:")) {
      targetSoilMoisture = command.substring(9).toInt();
      Serial.println(F("SOIL_OK"));
    } 
    else if (command.startsWith("SET:SOIL_TOL:")) {
      soilTolerance = command.substring(13).toInt();
      Serial.println(F("SOIL_TOL_OK"));
    } 
    else if (command.startsWith("SET:CO2:")) {
      targetCO2Level = command.substring(8).toInt();
      Serial.println(F("CO2_OK"));
    } 
    else if (command.startsWith("SET:CO2_TOL:")) {
      co2Tolerance = command.substring(12).toInt();
      Serial.println(F("CO2_TOL_OK"));
    } 
    // Команды для настройки времени устройств
    else if (command.startsWith("SET:CURT_OPEN:")) {
      int h = command.substring(14, 16).toInt();
      int m = command.substring(17, 19).toInt();
      curtainsOpenHour = h; curtainsOpenMinute = m;
      Serial.println(F("CURT_OPEN_OK"));
    } 
    else if (command.startsWith("SET:CURT_CLOSE:")) {
      int h = command.substring(15, 17).toInt();
      int m = command.substring(18, 20).toInt();
      curtainsCloseHour = h; curtainsCloseMinute = m;
      Serial.println(F("CURT_CLOSE_OK"));
    } 
    else if (command.startsWith("SET:LAMP_ON:")) {
      int h = command.substring(12, 14).toInt();
      int m = command.substring(15, 17).toInt();
      lampOnHour = h; lampOnMinute = m;
      Serial.println(F("LAMP_ON_OK"));
    } 
    else if (command.startsWith("SET:LAMP_OFF:")) {
      int h = command.substring(13, 15).toInt();
      int m = command.substring(16, 18).toInt();
      lampOffHour = h; lampOffMinute = m;
      Serial.println(F("LAMP_OFF_OK"));
    }
    // Команды для прямого управления устройствами
    else if (command.startsWith("LED:")) {
      int state = command.substring(4).toInt();
      ledState = state == 1;
      digitalWrite(LED_RELAY, ledState ? LOW : HIGH);
      Serial.print(F("LED:"));
      Serial.println(ledState ? F("1") : F("0"));
    }
    else if (command.startsWith("CURTAINS:")) {
      int state = command.substring(9).toInt();
      curtainsState = state == 1;
      digitalWrite(CURTAINS_RELAY, curtainsState ? LOW : HIGH);
      Serial.print(F("CURTAINS:"));
      Serial.println(curtainsState ? F("1") : F("0"));
    }
    else if (command.startsWith("RELAY3:")) {
      int state = command.substring(7).toInt();
      pumpState = state == 1;
      digitalWrite(PUMP_RELAY, pumpState ? LOW : HIGH);
      Serial.print(F("PUMP:"));
      Serial.println(pumpState ? F("1") : F("0"));
    }
    else if (command.startsWith("RELAY4:")) {
      int state = command.substring(7).toInt();
      fanState = state == 1;
      digitalWrite(FAN_RELAY, fanState ? LOW : HIGH);
      Serial.print(F("FAN:"));
      Serial.println(fanState ? F("1") : F("0"));
    }
    // Команды для запроса данных
    else if (command == "GET_SENSORS" || command == "SENSORS" || command == "READ") {
      sendSensorData();
    }
    else if (command == "GET_DEVICES") {
      sendDeviceStates();
    }
  }
}

void setTimeFromString(String timeData) {
  int yearVal = timeData.substring(0, 4).toInt();
  int monthVal = timeData.substring(5, 7).toInt();
  int dayVal = timeData.substring(8, 10).toInt();
  int hourVal = timeData.substring(11, 13).toInt();
  int minuteVal = timeData.substring(14, 16).toInt();
  int secondVal = timeData.substring(17, 19).toInt();
  
  rtc.setYear(yearVal - 2000);  // DS3231 ожидает год в формате 0-99
  rtc.setMonth(monthVal);
  rtc.setDate(dayVal);
  rtc.setHour(hourVal);
  rtc.setMinute(minuteVal);
  rtc.setSecond(secondVal);
  
  isTimeSet = true;
  
  Serial.print(F("Time set to: "));
  Serial.println(timeData);
  
  if (currentDisplayMode == TIME_DATE) {
    displayTimeDate();
  }
}

void switchToNextDisplay() {
  switch (currentDisplayMode) {
    case TIME_DATE:
      currentDisplayMode = SOIL_MOISTURE;
      displaySoilMoistureData();
      break;
    case SOIL_MOISTURE:
      currentDisplayMode = AIR_DATA;
      displayBMEData();
      break;
    case AIR_DATA:
      currentDisplayMode = CO2_LEVEL;
      displayCO2Data();
      break;
    case CO2_LEVEL:
      currentDisplayMode = DEVICES_STATE;
      displayDevicesState();
      break;
    case DEVICES_STATE:
      currentDisplayMode = SOIL_MOISTURE;
      displaySoilMoistureData();
      break;
  }
}

void displayTimeDate() {
  if (!isTimeSet) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(F("Waiting for time"));
    lcd.setCursor(0, 1);
    lcd.print(F("synchronization"));
    return;
  }
  
  int yearVal = 2000 + rtc.getYear();
  bool century;
  int monthVal = rtc.getMonth(century);
  int dayVal = rtc.getDate();
  bool h12, PM_time;
  int hourVal = rtc.getHour(h12, PM_time);
  int minuteVal = rtc.getMinute();
  int secondVal = rtc.getSecond();
  
  lcd.clear();
  lcd.setCursor(4, 0);
  if (hourVal < 10) lcd.print("0");
  lcd.print(hourVal);
  lcd.print(":");
  if (minuteVal < 10) lcd.print("0");
  lcd.print(minuteVal);
  lcd.print(":");
  if (secondVal < 10) lcd.print("0");
  lcd.print(secondVal);
  
  lcd.setCursor(2, 1);
  lcd.print(yearVal);
  lcd.print("-");
  if (monthVal < 10) lcd.print("0");
  lcd.print(monthVal);
  lcd.print("-");
  if (dayVal < 10) lcd.print("0");
  lcd.print(dayVal);
}

void displaySoilMoistureData() {
  int soilMoistureRaw = analogRead(SOIL_MOISTURE_PIN);
  int soilMoisturePercent = map(soilMoistureRaw, drySoilValue, wetSoilValue, 0, 100);
  soilMoisturePercent = constrain(soilMoisturePercent, 0, 100);
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Soil moisture:"));
  lcd.setCursor(0, 1);
  lcd.print(soilMoisturePercent);
  lcd.print("%");
  
  lcd.setCursor(0, 2);
  lcd.print(F("Target: "));
  lcd.print(targetSoilMoisture);
  lcd.print(F("% \xB1"));
  lcd.print(soilTolerance);
  lcd.print(F("%"));
}

void displayBMEData() {
  float temperature = bme.readTemperature();
  float humidity = bme.readHumidity();
  float pressure = bme.readPressure() / 100.0F; // гПа
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Temp: "));
  lcd.print(temperature, 1);
  lcd.print("\xDF""C");
  
  lcd.setCursor(0, 1);
  lcd.print(F("Humidity: "));
  lcd.print(humidity, 1);
  lcd.print("%");
  
  lcd.setCursor(0, 2);
  lcd.print(F("Pressure: "));
  lcd.print(pressure, 1);
  lcd.print(F("hPa"));
}

// Функция для управления RGB светодиодом в зависимости от уровня CO2
void updateCO2LED() {
  // Выключаем все цвета
  digitalWrite(LED_R, HIGH);
  digitalWrite(LED_G, HIGH);
  digitalWrite(LED_B, HIGH);
  
  if (co2Level < 100) {
    // Датчик не отвечает или ошибка - мигаем синим
    static unsigned long lastToggle = 0;
    static bool blinkState = false;
    
    if (millis() - lastToggle >= 500) { // Мигаем каждые 500 мс
      lastToggle = millis();
      blinkState = !blinkState;
      digitalWrite(LED_B, blinkState ? LOW : HIGH); // Для общего анода LOW = вкл
    }
  } else if (co2Level <= 800) {
    // Нормальный уровень CO2 (100-800 ppm) - зеленый
    digitalWrite(LED_G, LOW); // Включаем зеленый
  } else if (co2Level <= 1200) {
    // Повышенный уровень CO2 (800-1200 ppm) - желтый (зеленый + красный)
    digitalWrite(LED_R, LOW);
    digitalWrite(LED_G, LOW);
  } else {
    // Высокий уровень CO2 (>1200 ppm) - красный
    digitalWrite(LED_R, LOW);
  }
}

void readCO2() {
  if (millis() - lastCO2Reading >= co2ReadInterval) {
    lastCO2Reading = millis();
    
    mhz19Serial.write(cmd, 9);
    
    // Ждем ответа от датчика
    memset(response, 0, 9);
    int i = 0;
    unsigned long startTime = millis();
    while (mhz19Serial.available() == 0 && millis() - startTime < 1000) {
      delay(10);
    }
    
    i = 0;
    startTime = millis();
    while (mhz19Serial.available() && i < 9 && millis() - startTime < 1000) {
      response[i++] = mhz19Serial.read();
    }
    
    // Проверяем формат ответа
    if (i > 2 && response[0] == 0xFF && response[1] == 0x86) {
      co2Level = 256 * (int)response[2] + (int)response[3];
      
      // Проверка на разумные значения CO2
      if (co2Level < 100 || co2Level > 5000) {
        co2Level = 0; // Значение при ошибке - будет мигать синим
      }
      
      // Обновляем RGB светодиод
      updateCO2LED();
      
      if (currentDisplayMode == CO2_LEVEL) {
        displayCO2Data();
      }
    } else {
      // Если нет ответа от датчика
      co2Level = 0;
      updateCO2LED();
    }
  }
}

void displayCO2Data() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("CO2 level:"));
  lcd.setCursor(0, 1);
  lcd.print(co2Level);
  lcd.print(F(" ppm"));
  
  // Отображение целевого значения
  lcd.setCursor(0, 2);
  lcd.print(F("Target: "));
  lcd.print(targetCO2Level);
  lcd.print(F(" \xB1"));
  lcd.print(co2Tolerance);
  
  // Отображение статуса CO2
  lcd.setCursor(0, 3);
  lcd.print(F("Status: "));
  if (co2Level < 100) {
    lcd.print(F("ERROR"));
  } else if (co2Level <= 800) {
    lcd.print(F("NORMAL"));
  } else if (co2Level <= 1200) {
    lcd.print(F("ELEVATED"));
  } else {
    lcd.print(F("HIGH"));
  }
}

void displayDevicesState() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Lamp: "));
  lcd.print(ledState ? F("ON") : F("OFF"));
  
  lcd.setCursor(0, 1);
  lcd.print(F("Curtains: "));
  lcd.print(curtainsState ? F("OPEN") : F("CLOSED"));
  
  lcd.setCursor(0, 2);
  lcd.print(F("Pump: "));
  lcd.print(pumpState ? F("ON") : F("OFF"));
  
  lcd.setCursor(0, 3);
  lcd.print(F("Fan: "));
  lcd.print(fanState ? F("ON") : F("OFF"));
  
  // Отображение состояния CO2 LED
  lcd.setCursor(10, 3);
  lcd.print(F("CO2:"));
  if (co2Level < 100) {
    lcd.print(F("ERROR"));
  } else if (co2Level <= 800) {
    lcd.print(F("NORM"));
  } else if (co2Level <= 1200) {
    lcd.print(F("ELEV"));
  } else {
    lcd.print(F("HIGH"));
  }
}

void controlDevices() {
  // Контроль влажности почвы и насоса
  int soilMoistureRaw = analogRead(SOIL_MOISTURE_PIN);
  int soilMoisturePercent = map(soilMoistureRaw, drySoilValue, wetSoilValue, 0, 100);
  soilMoisturePercent = constrain(soilMoisturePercent, 0, 100);
  
  if (soilMoisturePercent < (targetSoilMoisture - soilTolerance)) {
    if (!pumpState) {
      pumpState = true;
      digitalWrite(PUMP_RELAY, LOW); // LOW активирует реле
    }
  } else if (soilMoisturePercent >= targetSoilMoisture) {
    if (pumpState) {
      pumpState = false;
      digitalWrite(PUMP_RELAY, HIGH); // HIGH деактивирует реле
    }
  }
  
  // Контроль температуры и вентилятора
  float temperature = bme.readTemperature();
  
  if (!isnan(temperature)) {
    if (temperature > (targetTemperature + tempTolerance)) {
      if (!fanState) {
        fanState = true;
        digitalWrite(FAN_RELAY, LOW); // LOW активирует реле
      }
    } else if (temperature <= targetTemperature) {
      if (fanState) {
        fanState = false;
        digitalWrite(FAN_RELAY, HIGH); // HIGH деактивирует реле
      }
    }
  }
  
  // Управление шторами и освещением по времени
  if (isTimeSet) {
    bool h12, PM_time;
    int currentHour = rtc.getHour(h12, PM_time);
    int currentMinute = rtc.getMinute();
    int currentTimeInMinutes = currentHour * 60 + currentMinute;
    
    // Управление шторами
    int curtainsOpenTimeInMinutes = curtainsOpenHour * 60 + curtainsOpenMinute;
    int curtainsCloseTimeInMinutes = curtainsCloseHour * 60 + curtainsCloseMinute;
    bool shouldCurtainsBeOpen;
    
    if (curtainsOpenTimeInMinutes < curtainsCloseTimeInMinutes) {
      shouldCurtainsBeOpen = (currentTimeInMinutes >= curtainsOpenTimeInMinutes && 
                             currentTimeInMinutes < curtainsCloseTimeInMinutes);
    } else {
      shouldCurtainsBeOpen = (currentTimeInMinutes >= curtainsOpenTimeInMinutes || 
                             currentTimeInMinutes < curtainsCloseTimeInMinutes);
    }
    
    if (shouldCurtainsBeOpen != curtainsState) {
      curtainsState = shouldCurtainsBeOpen;
      digitalWrite(CURTAINS_RELAY, curtainsState ? LOW : HIGH); // LOW активирует реле
    }
    
    // Управление лампой
    int lampOnTimeInMinutes = lampOnHour * 60 + lampOnMinute;
    int lampOffTimeInMinutes = lampOffHour * 60 + lampOffMinute;
    bool shouldLampBeOn;
    
    if (lampOnTimeInMinutes < lampOffTimeInMinutes) {
      shouldLampBeOn = (currentTimeInMinutes >= lampOnTimeInMinutes && 
                       currentTimeInMinutes < lampOffTimeInMinutes);
    } else {
      shouldLampBeOn = (currentTimeInMinutes >= lampOnTimeInMinutes || 
                       currentTimeInMinutes < lampOffTimeInMinutes);
    }
    
    if (shouldLampBeOn != ledState) {
      ledState = shouldLampBeOn;
      digitalWrite(LED_RELAY, ledState ? LOW : HIGH); // LOW активирует реле
    }
  }
}

// Отправка данных с датчиков в формате, понятном для приложения
void sendSensorData() {
  float temperature = bme.readTemperature();
  float humidity = bme.readHumidity();
  float pressure = bme.readPressure() / 100.0F; // гПа
  
  int soilMoistureRaw = analogRead(SOIL_MOISTURE_PIN);
  int soilMoisturePercent = map(soilMoistureRaw, drySoilValue, wetSoilValue, 0, 100);
  soilMoisturePercent = constrain(soilMoisturePercent, 0, 100);
  
  // Формат: SENSORS:температура:влажность:влажность_почвы:освещенность:co2:давление
  Serial.print(F("SENSORS:"));
  Serial.print(temperature);
  Serial.print(F(":"));
  Serial.print(humidity);
  Serial.print(F(":"));
  Serial.print(soilMoisturePercent);
  Serial.print(F(":"));
  Serial.print(F("500")); // Заглушка для освещенности, так как нет датчика света
  Serial.print(F(":"));
  Serial.print(co2Level);
  Serial.print(F(":"));
  Serial.println(pressure);
}

// Отправка состояния устройств
void sendDeviceStates() {
  // Формат: DEVICES:лампа:шторы:насос:вентилятор
  Serial.print(F("DEVICES:"));
  Serial.print(ledState ? F("1") : F("0"));
  Serial.print(F(":"));
  Serial.print(curtainsState ? F("1") : F("0"));
  Serial.print(F(":"));
  Serial.print(pumpState ? F("1") : F("0"));
  Serial.print(F(":"));
  Serial.println(fanState ? F("1") : F("0"));
}