#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <BH1750.h>
#include <LiquidCrystal_I2C.h>
#include "config.h"

Adafruit_BME280 bme280;
BH1750 bh1750(0x23);
LiquidCrystal_I2C lcd(0x27, 16, 2);
SensorReadings lastReadings = {0};
unsigned long lastSensorsUpdateTime = 0;
int8_t currentModeNumber = 0;

SensorReadings readAllSensors()
{
    SensorReadings re = {0};

    // Temperature
    re.temperature = bme280.readTemperature();

    // Humidity
    re.humidity = bme280.readHumidity();

    // Pressure
    re.pressure = bme280.readPressure() / 100.0f;

    // Light intensity
    while (!bh1750.measurementReady()) {}
    re.lightIntensity = bh1750.readLightLevel();

    // Soil moisture
    re.soilMoisture = 100 - map(analogRead(SOIL_MOISTURE_SENSOR_PIN), 0, 1023, 0, 100);

    // Water level
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(5);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    long duration = pulseIn(ECHO_PIN, HIGH);
    int distance = duration * 0.034 / 2;
    re.waterLevel = 100 - map(distance, 0, WATER_LEVEL_0_PCT_DISTANCE, 0, 100);
    re.waterLevel = constrain(re.waterLevel, 0, 100);

    return re;
}

void handleSensors()
{
    lastReadings = readAllSensors();
    GreenhouseModeSettings settings = greenhouseModeSettings[currentModeNumber];

    // Temperature
    if (lastReadings.temperature <= (settings.temperature * (1.0 - HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_HEATER_PIN, HIGH);
    }
    else if (lastReadings.temperature >= (settings.temperature * (1.0 + HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_HEATER_PIN, LOW);
    }

    // Humidity
    if (lastReadings.humidity <= (settings.humidity * (1.0 - HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_AIR_HUMIDIFIER_PIN, HIGH);
        RELAY_SET(RELAY_DEHUMIDIFIER_PIN, LOW);
    }
    else if (lastReadings.humidity >= (settings.humidity * (1.0 + HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_AIR_HUMIDIFIER_PIN, LOW);
        RELAY_SET(RELAY_DEHUMIDIFIER_PIN, HIGH);
    }
    else
    {
        RELAY_SET(RELAY_AIR_HUMIDIFIER_PIN, LOW);
        RELAY_SET(RELAY_DEHUMIDIFIER_PIN, LOW);
    }

    // Light intensity
    if (lastReadings.lightIntensity <= (settings.lightIntensity * (1.0 - HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_LIGHT_PIN, HIGH);
    }
    else if (lastReadings.lightIntensity >= (settings.lightIntensity * (1.0 + HYSTERESIS_PERCENT)))
    {
        RELAY_SET(RELAY_LIGHT_PIN, LOW);
    }

    // Soil moisture
    if (lastReadings.waterLevel > 10)
    {
        if (lastReadings.soilMoisture <= (settings.soilMoisture * (1.0 - HYSTERESIS_PERCENT)))
        {
            RELAY_SET(RELAY_WATER_PUMP_PIN, HIGH);
        }
        else if (lastReadings.soilMoisture >= (settings.soilMoisture * (1.0 + HYSTERESIS_PERCENT)))
        {
            RELAY_SET(RELAY_WATER_PUMP_PIN, LOW);
        }
    }
    else
    {
        RELAY_SET(RELAY_WATER_PUMP_PIN, LOW);
    }
}

void sendSensorsReadings()
{
    Serial.print("{");
    Serial.print("\"temperature\":");
    Serial.print(lastReadings.temperature);
    Serial.print(", \"humidity\":");
    Serial.print(lastReadings.humidity);
    Serial.print(", \"lightIntensity\":");
    Serial.print(lastReadings.lightIntensity);
    Serial.print(", \"soilMoisture\":");
    Serial.print(lastReadings.soilMoisture);
    Serial.print(", \"pressure\":");
    Serial.print(lastReadings.pressure);
    Serial.print(", \"waterLevel\":");
    Serial.print(lastReadings.waterLevel);
    Serial.println("}");
}

void showOnDisplay()
{
    static uint8_t currentSensor = 0;
    static unsigned long lastDisplayUpdate = 0;

    if (millis() - lastDisplayUpdate < DIPLAY_UPDATE_INTERVAL)
    {
        return;
    }

    GreenhouseModeSettings settings = greenhouseModeSettings[currentModeNumber];
    lcd.clear();

    if (currentSensor == 0)
    {
        lcd.print("Temperature [C]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.temperature, 2) + " (" + String(settings.temperature) + ")");
    }
    else if (currentSensor == 1)
    {
        lcd.print("Humidity [%]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.humidity, 2) + " (" + String(settings.humidity) + ")");
    }
    else if (currentSensor == 2)
    {
        lcd.print("Light [lx]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.lightIntensity, 2) + " (" + String(settings.lightIntensity) + ")");
    }
    else if (currentSensor == 3)
    {
        lcd.print("Soil moist. [%]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.soilMoisture, 2) + " (" + String(settings.soilMoisture) + ")");
    }
    else if (currentSensor == 4)
    {
        lcd.print("Pressure [hPa]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.pressure, 2));
    }
    else if (currentSensor == 5)
    {
        lcd.print("Water level [%]");
        lcd.setCursor(0, 1);
        lcd.print(String(lastReadings.waterLevel, 0));
    }

    currentSensor = (currentSensor + 1) % 6;
    lastDisplayUpdate = millis();
}

void setMode()
{
    bool updateDisplay = true;

    if (!digitalRead(OK_BUTTON_PIN))
    {
        while (true)
        {
            if (updateDisplay)
            {
                lcd.clear();
                lcd.print("Mode:");
                lcd.setCursor(0, 1);
                lcd.print(greenhouseModeSettings[currentModeNumber].name);
                updateDisplay = false;

                delay(50);
                while (!digitalRead(OK_BUTTON_PIN) || !digitalRead(UP_BUTTON_PIN) || !digitalRead(DOWN_BUTTON_PIN)) {};
                delay(50);
            }

            if (!digitalRead(UP_BUTTON_PIN))
            {
                currentModeNumber = (currentModeNumber + 1) % NUM_OF_MODES;
                updateDisplay = true;
            }
            else if (!digitalRead(DOWN_BUTTON_PIN))
            {
                currentModeNumber = (currentModeNumber == 0 ? (NUM_OF_MODES - 1) : currentModeNumber - 1);
                updateDisplay = true;
            }
            else if (!digitalRead(OK_BUTTON_PIN))
            {
                delay(50);
                while (!digitalRead(OK_BUTTON_PIN) || !digitalRead(UP_BUTTON_PIN) || !digitalRead(DOWN_BUTTON_PIN)) {};
                delay(50);
                return;
            }
        }
    }
}

void setup()
{
    Serial.begin(9600);

    pinMode(SOIL_MOISTURE_SENSOR_PIN, INPUT);
    pinMode(RELAY_HEATER_PIN, OUTPUT);
    pinMode(RELAY_WATER_PUMP_PIN, OUTPUT);
    pinMode(RELAY_AIR_HUMIDIFIER_PIN, OUTPUT);
    pinMode(RELAY_DEHUMIDIFIER_PIN, OUTPUT);
    pinMode(RELAY_LIGHT_PIN, OUTPUT);
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    pinMode(UP_BUTTON_PIN, INPUT_PULLUP);
    pinMode(OK_BUTTON_PIN, INPUT_PULLUP);
    pinMode(DOWN_BUTTON_PIN, INPUT_PULLUP);

    RELAY_SET(RELAY_HEATER_PIN, LOW);
    RELAY_SET(RELAY_WATER_PUMP_PIN, LOW);
    RELAY_SET(RELAY_AIR_HUMIDIFIER_PIN, LOW);
    RELAY_SET(RELAY_DEHUMIDIFIER_PIN, LOW);
    RELAY_SET(RELAY_LIGHT_PIN, LOW);

    while (!bme280.begin())
    {
        Serial.println("BME280 Connection error");
        delay(1000);
    }

    while (!bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE))
    {
        Serial.println("BH1750 Connection error");
        delay(1000);
    }

    lcd.init();
    lcd.backlight();

    handleSensors();
    sendSensorsReadings();
    lastSensorsUpdateTime = millis();
}

void loop()
{
    if (millis() - lastSensorsUpdateTime >= SENSORS_UPDATE_INTERVAL)
    {
        handleSensors();
        sendSensorsReadings();
        lastSensorsUpdateTime = millis();
    }

    setMode();
    showOnDisplay();
}
BME280Breakout
BH1750Breakout