#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <TimeLib.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <ArduinoJson.h>

#define I2C_ADDRESS   0x3C

// Custom weather icons (16x16 pixels)
const uint8_t clearSkyIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b00011111, 0b11111000,
    0b00001111, 0b11110000,
    0b00000011, 0b11000000,
    0b00000000, 0b00000000
};

const uint8_t fewCloudsIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00111111, 0b11111100,
    0b00111100, 0b00111100,
    0b00011000, 0b00011000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000
};

const uint8_t scatteredCloudsIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00111111, 0b11111100,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000
};

const uint8_t rainIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00101010, 0b10101000,
    0b00010101, 0b01010000,
    0b00101010, 0b10101000,
    0b00010101, 0b01010000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000
};

const uint8_t thunderstormIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00111100, 0b00111100,
    0b00011000, 0b00011000,
    0b00110000, 0b00001100,
    0b00011000, 0b00011000,
    0b00001100, 0b00110000,
    0b00000000, 0b00000000
};

const uint8_t snowIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000011, 0b11000000,
    0b00001111, 0b11110000,
    0b00011111, 0b11111000,
    0b00111111, 0b11111100,
    0b00111111, 0b11111100,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00101010, 0b10101000,
    0b00010101, 0b01010000,
    0b00101010, 0b10101000,
    0b00010101, 0b01010000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000
};

const uint8_t mistIcon[] PROGMEM = {
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b01111111, 0b11111110,
    0b01111111, 0b11111110,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000,
    0b00000000, 0b00000000
};

Adafruit_SH1107 display = Adafruit_SH1107(64, 128, &Wire);

const char* ssid     = "Your SSID";
const char* password = "Your SSID password";

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000);

// OpenWeatherMap API settings
const String apiKey = "Your API key"; // Your API key
const String weatherUrl = "http://api.openweathermap.org/data/2.5/weather?lat=57.19316&lon=-2.11010&units=metric&appid=" + apiKey;

float temperature = 0.0; // Variable to store temperature
bool temperatureFetchSuccess = false; // Track if temperature fetch was successful

unsigned long lastTemperatureFetchTime = 0; // Track the last time the temperature was fetched
const unsigned long temperatureFetchInterval = 5 *60 * 1000; // Fetch temperature every 5 minutes (in milliseconds)
const unsigned long retryInterval = 5 * 1000; // Retry after 5 seconds if parsing fails

bool colonVisible = true; // Track whether the colon is visible
unsigned long lastColonToggleTime = 0; // Track the last time the colon was toggled

String weatherIcon = ""; // Variable to store the weather icon code

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    Serial.print("Connecting to Wi-Fi");

    // Wi-Fi connection with timeout
    unsigned long startAttemptTime = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) { // 10-second timeout
        delay(500);
        Serial.print(".");
    }
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("Failed to connect to Wi-Fi!");
    } else {
        Serial.println("Connected!");
    }

    // Display initialization with error handling
    if (!display.begin(I2C_ADDRESS, true)) {
        Serial.println("Display initialization failed!");
        while (1); // Halt execution
    }
    display.setRotation(1); // Rotate display for landscape mode
    display.clearDisplay();

    timeClient.begin();
}

void loop() {
    timeClient.update();
    time_t rawTime = timeClient.getEpochTime();
    setTime(rawTime);

    // Clear the entire display to avoid overlapping text
    display.clearDisplay();

    // Display time in 24-hour format at (2, 30)
    display.setTextColor(SH110X_WHITE);
    display.setFont(&FreeSansBold18pt7b);

    char timeStr[6];
    sprintf(timeStr, "%02d%02d", hour(), minute()); // 24-hour format without colon
    display.setCursor(0, 31); // Adjusted position
    display.print(timeStr[0]); // First digit of hour
    display.print(timeStr[1]); // Second digit of hour

    // Toggle colon visibility every second
    if (millis() - lastColonToggleTime >= 1000) {
        colonVisible = !colonVisible;
        lastColonToggleTime = millis();
    }

    // Display colon if visible
    if (colonVisible) {
        display.setCursor(38, 30); // Position for colon
        display.print(":");
    } else {
        display.fillRect(40, 10, 10, 20, SH110X_BLACK); // Overwrite colon space to prevent shifting
    }

    display.setCursor(48, 30); // Maintain fixed spacing
    display.print(timeStr[2]); // First digit of minute
    display.print(timeStr[3]); // Second digit of minute

    // Display date at (20, 45)
    display.setFont();
    display.setTextSize(1);
    char dateStr[20];
    sprintf(dateStr, "%s %02d/%02d/%04d", dayShortStr(weekday()), day(), month(), year());
    display.setCursor(20, 45); // Adjusted position
    display.print(dateStr);

    // Draw Wi-Fi connection status (3 circles)
    drawWiFiStatus();

    // Fetch and display outdoor temperature at (95, 20)
    unsigned long currentTime = millis();
    if (currentTime - lastTemperatureFetchTime >= temperatureFetchInterval || 
        (!temperatureFetchSuccess && currentTime - lastTemperatureFetchTime >= retryInterval)) {
        fetchTemperature();
        lastTemperatureFetchTime = currentTime; // Update the last fetch time
    }
    displayTemperature();

    // Draw weather icon at (95, 10)
    if (weatherIcon.length() > 0) {
        drawWeatherIcon(weatherIcon);
    }

    // Update the display
    display.display();
    delay(500); // Update every 500ms
}

void drawWiFiStatus() {
    if (WiFi.status() == WL_CONNECTED) {
        // Draw 3 circles for connected status
        display.drawCircle(126, 1, 6, SH110X_WHITE); // Outer circle
        display.drawCircle(127, 0, 3, SH110X_WHITE); // Middle circle
        display.drawCircle(128, 0, 1, SH110X_WHITE); // Inner circle
    } else {
        // Draw an "X" for disconnected status
        display.drawLine(115, 0, 125, 10, SH110X_WHITE); // Diagonal line 1
        display.drawLine(125, 0, 115, 10, SH110X_WHITE); // Diagonal line 2
    }
}

void fetchTemperature() {
    if (WiFi.status() == WL_CONNECTED) {
        WiFiClient client;
        if (client.connect("api.openweathermap.org", 80)) {
            // Send HTTP GET request
            client.print("GET " + weatherUrl + " HTTP/1.1\r\n");
            client.print("Host: api.openweathermap.org\r\n");
            client.print("Connection: close\r\n\r\n");

            // Wait for the response
            while (client.connected() && !client.available()) {
                delay(10);
            }

            // Read the response
            String jsonResponse = "";
            while (client.available()) {
                jsonResponse += client.readString();
            }
            client.stop();

            // Debug: Print the raw JSON response
            Serial.println("Raw JSON Response:");
            Serial.println(jsonResponse);

            // Find the start of the JSON object
            int jsonStart = jsonResponse.indexOf('{');
            if (jsonStart == -1) {
                Serial.println("JSON start not found!");
                temperatureFetchSuccess = false;
                return;
            }

            // Extract the JSON object
            String jsonObject = jsonResponse.substring(jsonStart);

            // Debug: Print the extracted JSON object
            Serial.println("Extracted JSON Object:");
            Serial.println(jsonObject);

            // Parse the JSON object
            DynamicJsonDocument doc(1024);
            DeserializationError error = deserializeJson(doc, jsonObject);
            if (error) {
                Serial.println("Failed to parse JSON!");
                Serial.println(error.c_str());
                temperatureFetchSuccess = false;
                return;
            }

            // Check if the "main" and "temp" fields exist
            if (doc.containsKey("main") && doc["main"].containsKey("temp")) {
                temperature = doc["main"]["temp"].as<float>(); // Extract temperature
                temperatureFetchSuccess = true;
                Serial.println("Temperature fetched successfully: " + String(temperature));
            } else {
                Serial.println("Temperature data not found in JSON!");
                temperatureFetchSuccess = false;
            }

            // Extract weather icon code
            if (doc.containsKey("weather") && doc["weather"].is<JsonArray>()) {
                JsonArray weatherArray = doc["weather"].as<JsonArray>();
                if (weatherArray.size() > 0 && weatherArray[0].containsKey("icon")) {
                    weatherIcon = weatherArray[0]["icon"].as<String>();
                    Serial.println("Weather icon code: " + weatherIcon);
                } else {
                    Serial.println("Weather icon code not found!");
                }
            }
        } else {
            Serial.println("Failed to connect to OpenWeatherMap!");
            temperatureFetchSuccess = false;
        }
    } else {
        Serial.println("Wi-Fi not connected!");
        temperatureFetchSuccess = false;
    }
}

void displayTemperature() {
    display.setFont();
    display.setTextSize(1);
    display.setCursor(92, 25); // Adjusted position
    if (temperatureFetchSuccess) {
        display.print(temperature, 1); // Display temperature with 1 decimal place
        display.print((char)247); // Degrees symbol (ASCII 247)
        display.print("C"); // Celsius symbol
    } else {
        display.print("   "); // Display "N/A" if temperature fetch failed
    }
}


void drawWeatherIcon(const String& iconCode) {
    const uint8_t* icon = nullptr;

    if (iconCode == "01d" || iconCode == "01n") {
        icon = clearSkyIcon; // Clear sky
    } else if (iconCode == "02d" || iconCode == "02n") {
        icon = fewCloudsIcon; // Few clouds
    } else if (iconCode == "03d" || iconCode == "03n") {
        icon = scatteredCloudsIcon; // Scattered clouds
    } else if (iconCode == "04d" || iconCode == "04n") {
        icon = scatteredCloudsIcon; // Broken clouds (same as scattered)
    } else if (iconCode == "09d" || iconCode == "09n") {
        icon = rainIcon; // Shower rain
    } else if (iconCode == "10d" || iconCode == "10n") {
        icon = rainIcon; // Rain (same as shower rain)
    } else if (iconCode == "11d" || iconCode == "11n") {
        icon = thunderstormIcon; // Thunderstorm
    } else if (iconCode == "13d" || iconCode == "13n") {
        icon = snowIcon; // Snow
    } else if (iconCode == "50d" || iconCode == "50n") {
        icon = mistIcon; // Mist
    } else {
        // Unknown weather
        return;
    }

    // Draw the icon at (97, 2)
    display.drawBitmap(97, 2, icon, 16, 16, SH110X_WHITE);
}