#ifndef FRAMES_H
#define FRAMES_H

#include <vector>
#include <map>
#include "icons.h"
#include <FastLED_NeoMatrix.h>
#include "MatrixDisplayUi.h"
#include "Globals.h"
#include "Functions.h"
#include "MenuManager.h"
#include "PeripheryManager.h"
#include "DisplayManager.h"
#include "LittleFS.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <Ticker.h>

Ticker downloader;

tm timeInfo;
uint16_t nativeAppsCount;
uint16_t customPagesCount;

int WEATHER_CODE;
String WEATHER_TEMP;
String WEATHER_HUM;

struct CustomFrame
{
    int16_t scrollposition = 0;
    int16_t scrollDelay = 0;
    String text;
    uint16_t color;
    File icon;
    bool isGif;
    bool rainbow;
    bool soundPlayed;
    String sound;
    int16_t repeat = 0;
    int16_t currentRepeat = 0;
    String name;
    byte pushIcon = 0;
    int16_t iconPosition = 0;
    bool iconWasPushed = false;
};

String currentCustomFrame;
std::map<String, CustomFrame> customFrames;

struct Notification
{
    int16_t scrollposition = 34;
    int16_t scrollDelay = 0;
    String text;
    uint16_t color;
    File icon;
    bool rainbow;
    bool isGif;
    bool flag = false;
    unsigned long startime = 0;
    unsigned long duration = 0;
    int16_t repeat = -1;
    bool hold = false;
    byte pushIcon = 0;
    int16_t iconPosition = 0;
    bool iconWasPushed = false;
};

Notification notify;

std::vector<std::pair<String, AppCallback>> Apps;

CustomFrame *getCustomFrameById(String name)
{
    return customFrames.count(name) ? &customFrames[name] : nullptr;
}

String getFrameNameByFunction(AppCallback frameFunction)
{
    for (const auto &appPair : Apps)
    {
        if (appPair.second == frameFunction)
        {
            return appPair.first;
        }
    }

    return ""; // Gibt einen leeren String zurück, wenn die Frame-Funktion nicht gefunden wurde
}

int findAppIndexByName(const String &name)
{
    auto it = std::find_if(Apps.begin(), Apps.end(), [&name](const std::pair<String, AppCallback> &appPair)
                           { return appPair.first == name; });
    if (it != Apps.end())
    {
        return std::distance(Apps.begin(), it);
    }
    return -1;
}

void TimeFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Time";
    DisplayManager.getInstance().resetTextColor();
    time_t now1 = time(nullptr);
    struct tm *timeInfo;
    timeInfo = localtime(&now1);
    char t[14];
    if (SHOW_SECONDS)
    {
        sprintf_P(t, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
        matrix->setCursor(2 + x, 6 + y);
    }
    else
    {
        sprintf_P(t, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
        matrix->setCursor(7 + x, 6 + y);
    }

    matrix->println(t);

    if (!SHOW_WEEKDAY)
        return;
    for (int i = 0; i <= 6; i++)
    {
        if (i == (timeInfo->tm_wday + 6) % 7)
        {
            matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200));
        }
        else
        {
            matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100));
        }
    }
}

void DateFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Date";
    DisplayManager.getInstance().resetTextColor();
    time_t now1 = time(nullptr);
    struct tm *timeInfo;
    timeInfo = localtime(&now1);
    char d[11];
    sprintf_P(d, PSTR("%02d.%02d.%02d"), timeInfo->tm_mday, timeInfo->tm_mon + 1, timeInfo->tm_year % 100);
    matrix->setCursor(2 + x, 6 + y);
    matrix->println(d);
    if (!SHOW_WEEKDAY)
        return;
    for (int i = 0; i <= 6; i++)
    {
        if (i == (timeInfo->tm_wday + 6) % 7)
        {
            matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200));
        }
        else
        {
            matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100));
        }
    }
}

void TempFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Temperature";
    DisplayManager.getInstance().resetTextColor();
    matrix->drawRGBBitmap(x, y, get_icon(234), 8, 8);
    matrix->setCursor(14 + x, 6 + y);
    matrix->print((int)CURRENT_TEMP); // Ausgabe der Temperatur
    matrix->print(utf8ascii("°"));
}

void HumFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Humidity";
    DisplayManager.getInstance().resetTextColor();
    matrix->drawRGBBitmap(x, y + 1, get_icon(2075), 8, 8);
    matrix->setCursor(14 + x, 6 + y);
    int humidity = CURRENT_HUM; // Temperatur ohne Nachkommastellen
    matrix->print(humidity);    // Ausgabe der Temperatur
    matrix->print("%");
}

void BatFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Battery";
    DisplayManager.getInstance().resetTextColor();
    matrix->drawRGBBitmap(x, y, get_icon(1486), 8, 8);
    matrix->setCursor(14 + x, 6 + y);
    matrix->print(BATTERY_PERCENT); // Ausgabe des Ladezustands
    matrix->print("%");
}

void MenuFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
{
    if (!MenuManager.inMenu)
        return;
    matrix->fillScreen(0);
    DisplayManager.printText(0, 6, MenuManager.menutext().c_str(), true, true);
}

void AlarmFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
{
    if (ALARM_ACTIVE)

    {
        matrix->fillScreen(matrix->Color(255, 0, 0));
        CURRENT_APP = "Alarm";
        uint16_t textWidth = getTextWidth("ALARM",false);
        int16_t textX = ((32 - textWidth) / 2);
        matrix->setTextColor(0);
        matrix->setCursor(textX, 6);
        matrix->print("ALARM");
        if (ALARM_SOUND != "")
        {
            if (!PeripheryManager.isPlaying())
                PeripheryManager.playFromFile("/MELODIES/" + ALARM_SOUND + ".txt");
        }
    }
}

void TimerFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
{
    if (TIMER_ACTIVE)
    {
        matrix->fillScreen(matrix->Color(0, 255, 0));
        CURRENT_APP = "Timer";
        String menuText = "TIMER";
        uint16_t textWidth = getTextWidth(menuText.c_str(),false);
        int16_t textX = ((32 - textWidth) / 2);
        matrix->setTextColor(0);
        matrix->setCursor(textX, 6);
        matrix->print(menuText);
        if (TIMER_SOUND != "")
        {
            if (!PeripheryManager.isPlaying())
                PeripheryManager.playFromFile("/MELODIES/" + TIMER_SOUND + ".txt");
        }
    }
}

void ShowCustomFrame(String name, FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    // Abort if notify.flag is set
    if (notify.flag)
    {
        return;
    }

    // Get custom frame by ID
    CustomFrame *cf = getCustomFrameById(name);

    // Abort if custom frame not found
    if (cf == nullptr)
    {
        return;
    }

    // reset custom frame properties if last frame
    if (lastFrame)
    {
        cf->iconWasPushed = false;
        cf->scrollposition = 9;
        cf->iconPosition = 0;
        cf->scrollDelay = 0;
    }

    CURRENT_APP = cf->name;
    currentCustomFrame = name;

    bool hasIcon = cf->icon;
    uint16_t availableWidth = (hasIcon) ? 24 : 32;

    bool noScrolling = getTextWidth(cf->text.c_str(),false) <= availableWidth;

    if ((cf->repeat > 0) && (getTextWidth(cf->text.c_str(),false) > availableWidth) && (state->frameState == FIXED))
    {
        DisplayManager.setAutoTransition(false);
    }
    else
    {
        DisplayManager.setAutoTransition(true);
    }

    if (getTextWidth(cf->text.c_str(),false) > availableWidth && !(state->frameState == IN_TRANSITION))
    {
        if (cf->scrollposition <= -getTextWidth(cf->text.c_str(),false))
        {
            cf->scrollDelay = 0;
            cf->scrollposition = 9;
            if (cf->iconWasPushed && cf->pushIcon == 2)
            {
                cf->iconWasPushed = false;
            }
            if ((cf->currentRepeat + 1 >= cf->repeat) && (cf->repeat > 0))
            {
                DisplayManager.setAutoTransition(true);
                cf->currentRepeat = 0;
                DisplayManager.nextApp();
                return;
            }
            else if (cf->repeat > 0)
            {
                ++cf->currentRepeat;
            }
        }
    }

    if (!noScrolling)
    {
        if ((cf->scrollDelay > MATRIX_FPS * 1.2))
        {
            --cf->scrollposition;
        }
        else
        {
            ++cf->scrollDelay;
            if (hasIcon)
            {
                if (cf->iconWasPushed && cf->pushIcon == 1)
                {
                    cf->scrollposition = 0;
                }
                else
                {
                    cf->scrollposition = 9;
                }
            }
            else
            {
                cf->scrollposition = 0;
            }
        }
    }
    int16_t textX = (hasIcon) ? ((24 - getTextWidth(cf->text.c_str(),false)) / 2) + 9 : ((32 - getTextWidth(cf->text.c_str(),false)) / 2);
    matrix->setTextColor(cf->color);
    if (noScrolling)
    {
        cf->repeat = -1; // Disable repeat if text is too short for scrolling
        // Display text with rainbow effect if enabled
        if (cf->rainbow)
        {
            DisplayManager.getInstance().HSVtext(x + textX, 6 + y, cf->text.c_str(), false);
        }
        else
        {
            // Display text
            DisplayManager.printText(x + textX, y + 6, cf->text.c_str(), false, false);
        }
    }
    else
    {
        // Display scrolling text with rainbow effect if enabled
        if (cf->rainbow)
        {
            DisplayManager.getInstance().HSVtext(x + cf->scrollposition, 6 + y, cf->text.c_str(), false);
        }
        else
        {
            DisplayManager.printText(x + cf->scrollposition, 6 + y, cf->text.c_str(), false, false);
        }
    } // Display icon if present and not pushed
    if (hasIcon)
    {
        // Push icon if enabled and text is scrolling
        if (cf->pushIcon > 0 && !noScrolling)
        {
            if (cf->iconPosition < 0 && cf->iconWasPushed == false && cf->scrollposition > 8)
            {
                ++cf->iconPosition;
            }

            if (cf->scrollposition < 8 && !cf->iconWasPushed)
            {
                cf->iconPosition = cf->scrollposition - 8;

                if (cf->iconPosition <= -9)
                {
                    cf->iconWasPushed = true;
                }
            }
        }

        // Display animated GIF if enabled and frame is fixed, since we have only one gifplayer instance, it looks weird when 2 apps want to draw a different gif
        if (cf->isGif)
        {
            if (state->frameState == FIXED)
            {
                DisplayManager.drawGIF(x + cf->iconPosition, y, cf->icon);
            }
        }
        else
        {
            // Display JPEG image
            DisplayManager.drawJPG(x + cf->iconPosition, y, cf->icon);
        }

        // Draw vertical line if text is scrolling
        if (!noScrolling)
        {
            // matrix->drawLine(8 + x + cf->iconPosition, 0 + y, 8 + x + cf->iconPosition, 7 + y, 0);
        }
    }
    // Reset text color
    DisplayManager.getInstance().resetTextColor();
}

void NotifyFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
{
    // Check if notification flag is set
    if (!notify.flag)
    {
        return; // Exit function if flag is not set
    }

    // Set current app name
    CURRENT_APP = "Notification";
    // Get current time
    unsigned long currentTime = millis();

    // Check if notification duration has expired or if repeat count is 0 and hold is not enabled
    if ((((currentTime - notify.startime >= notify.duration) && notify.repeat == -1) || notify.repeat == 0) && !notify.hold)
    {
        // Reset notification flags and exit function
        notify.flag = false;
        notify.duration = 0;
        notify.startime = 0;
        notify.scrollposition = 34;
        notify.iconWasPushed = false;
        notify.iconPosition = 0;
        notify.scrollDelay = 0;
        return;
    }

    // Check if notification has an icon
    bool hasIcon = notify.icon;

    // Clear the matrix display
    matrix->fillRect(0, 0, 32, 8, 0);

    // Calculate text and available width
    uint16_t textWidth = getTextWidth(notify.text.c_str(),false);
    uint16_t availableWidth = hasIcon ? 24 : 32;

    // Check if text is scrolling
    bool noScrolling = textWidth <= availableWidth;

    // Check if text needs to be scrolled
    if (textWidth > availableWidth && notify.scrollposition <= -textWidth)
    {
        // Reset scroll position and icon position if needed
        notify.scrollDelay = 0;
        notify.scrollposition = 9;

        if (notify.pushIcon == 2)
        {
            notify.iconWasPushed = false;
        }

        if (notify.repeat > 0)
        {
            --notify.repeat;
            if (notify.repeat == 0)
                return;
        }
    }

    if (!noScrolling)
    {
        if ((notify.scrollDelay > MATRIX_FPS * 1.2))
        {
            --notify.scrollposition;
        }
        else
        {
            ++notify.scrollDelay;
            if (hasIcon)
            {
                if (notify.iconWasPushed && notify.pushIcon == 1)
                {
                    notify.scrollposition = 0;
                }
                else
                {
                    notify.scrollposition = 9;
                }
            }
            else
            {
                notify.scrollposition = 0;
            }
        }
    }

    // Calculate text X position based on icon presence
    int16_t textX = hasIcon ? ((24 - textWidth) / 2) + 9 : ((32 - textWidth) / 2);

    // Set text color
    matrix->setTextColor(notify.color);

    if (noScrolling)
    {
        // Disable repeat if text is not scrolling
        notify.repeat = -1;

        if (notify.rainbow)
        {
            // Display text in rainbow color if enabled
            DisplayManager.getInstance().HSVtext(textX, 6, notify.text.c_str(), false);
        }
        else
        {
            // Display text in solid color
            DisplayManager.printText(textX, 6, notify.text.c_str(), false, false);
        }
    }
    else
    {
        if (notify.rainbow)
        {
            // Display scrolling text in rainbow color if enabled
            DisplayManager.getInstance().HSVtext(notify.scrollposition, 6, notify.text.c_str(), false);
        }
        else
        {
            // Display scrolling text in solid color
            DisplayManager.printText(notify.scrollposition, 6, notify.text.c_str(), false, false);
        }
    }

    // Display icon if present and not pushed
    if (hasIcon)
    {
        // Push icon if enabled and text is scrolling
        if (notify.pushIcon > 0 && !noScrolling)
        {
            if (notify.iconPosition < 0 && notify.iconWasPushed == false && notify.scrollposition > 8)
            {
                ++notify.iconPosition;
            }

            if (notify.scrollposition < 8 && !notify.iconWasPushed)
            {
                notify.iconPosition = notify.scrollposition - 9;

                if (notify.iconPosition <= -9)
                {
                    notify.iconWasPushed = true;
                }
            }
        }

        // Display animated GIF if
        if (notify.isGif)
        {
            // Display GIF if present
            DisplayManager.drawGIF(notify.iconPosition, 0, notify.icon);
        }
        else
        {
            // Display JPG image if present
            DisplayManager.drawJPG(notify.iconPosition, 0, notify.icon);
        }

        // Display icon divider line if text is scrolling
        if (!noScrolling)
        {
            // matrix->drawLine(8 + notify.iconPosition, 0, 8 + notify.iconPosition, 7, 0);
        }
    }

    // Reset text color after displaying notification
    DisplayManager.getInstance().resetTextColor();
}

void CFrame1(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame1);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame2(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame2);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame3(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame3);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame4(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame4);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame5(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame5);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame6(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame6);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame7(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame7);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame8(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame8);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame9(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame9);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

void CFrame10(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    String name = getFrameNameByFunction(CFrame10);
    ShowCustomFrame(name, matrix, state, x, y, firstFrame, lastFrame);
}

const uint16_t *getWeatherIcon(int code)
{
    switch (code)
    {
    case 1:
        return icon_475;
        break;

    default:
        return icon_475;
        break;
    }
}

void WeatherFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
{
    if (notify.flag)
        return;
    CURRENT_APP = "Weather";
    DisplayManager.getInstance().resetTextColor();
    matrix->drawRGBBitmap(x, y, getWeatherIcon(WEATHER_CODE), 8, 8);
    String text = WEATHER_TEMP + "°" + WEATHER_HUM + "%";
    uint16_t textWidth = getTextWidth(text.c_str(), false);
    int16_t textX = ((23 - textWidth) / 2);
    matrix->setCursor(textX + 11, 6 + y);
    matrix->print(utf8ascii(text));
}

void getWeatherData()
{
    Serial.println("UPDATE");
    String weatherUrl = "https://wttr.in/" + CITY + "?format=p1";
    if ((WiFi.status() == WL_CONNECTED))
    {
        HTTPClient http;
        http.begin(weatherUrl);
        http.setTimeout(5000);
        int httpCode = http.GET();

        if (httpCode > 0)
        {
            String payload = http.getString();
            int temperatureIndex = payload.indexOf("temperature_celsius{forecast=\"current\"}");
            int humIndex = payload.indexOf("humidity_percentage{forecast=\"current\"}");
            int weatherCodeIndex = payload.indexOf("weather_code{forecast=\"current\"}");

            if (temperatureIndex >= 0 && weatherCodeIndex >= 0)
            {
                int tempEndIndex = payload.indexOf('\n', temperatureIndex);
                int codeEndIndex = payload.indexOf('\n', weatherCodeIndex);
                int humEndIndex = payload.indexOf('\n', humIndex);
                String temperatureValue = payload.substring(temperatureIndex + 40, tempEndIndex);
                String humValue = payload.substring(humIndex + 40, humEndIndex);
                String weatherCodeValue = payload.substring(weatherCodeIndex + 33, codeEndIndex);

                WEATHER_TEMP = temperatureValue;
                WEATHER_HUM = humValue;
                WEATHER_CODE = weatherCodeValue.toInt();
            }
        }
        http.end();
    }
}

void StartAppUpdater()
{
    // downloader.attach(60,getWeatherData);
    // getWeatherData();
}

OverlayCallback overlays[] = {MenuFrame, NotifyFrame, AlarmFrame, TimerFrame};
#endif