#include <TinyGPS++.h>
#include <SPIFFS.h>
#include "APRSPacketLib.h"
#include "station_utils.h"
#include "battery_utils.h"
#include "configuration.h"
#include "boards_pinout.h"
#include "power_utils.h"
#include "sleep_utils.h"
#include "lora_utils.h"
#include "ble_utils.h"
#include "wx_utils.h"
#include "display.h"
#include "logger.h"

extern Configuration        Config;
extern Beacon               *currentBeacon;
extern logging::Logger      logger;
extern TinyGPSPlus          gps;
extern uint8_t              myBeaconsIndex;
extern uint8_t              loraIndex;

extern uint32_t             lastTx;
extern uint32_t             lastTxTime;

extern bool                 sendUpdate;

extern double               currentHeading;
extern double               previousHeading;

extern double               lastTxLat;
extern double               lastTxLng;
extern double               lastTxDistance;

extern bool                 miceActive;
extern bool                 smartBeaconActive;
extern bool                 winlinkCommentState;

extern int                  wxModuleType;
extern bool                 wxModuleFound;
extern bool                 gpsIsActive;
extern bool                 gpsShouldSleep;


bool	    sendStandingUpdate      = false;
uint8_t     updateCounter           = 100;


bool        sendStartTelemetry      = true;
uint32_t    lastTelemetryTx         = 0;
uint32_t    telemetryTx             = millis();

uint32_t    lastDeleteListenedTracker;

struct nearTracker {
    String      callsign;
    float       distance;
    int         course;
    uint32_t    lastTime;
};

nearTracker nearTrackers[4];


namespace STATION_Utils {

    void nearTrackerInit() {
        for (int i = 0; i < 4; i++) {
            nearTrackers[i].callsign    = "";
            nearTrackers[i].distance    = 0.0;
            nearTrackers[i].course      = 0;
            nearTrackers[i].lastTime    = 0;
        }
    }

    const String getNearTracker(uint8_t position) {
        if (nearTrackers[position].callsign == "") {
            return "";
        } else {
            return nearTrackers[position].callsign + "> " + String(nearTrackers[position].distance,2) + "km " + String(nearTrackers[position].course);
        }
    }

    void deleteListenedTrackersbyTime() {
        for (int a = 0; a < 4; a++) {                       // clean nearTrackers[] after time
            if (nearTrackers[a].callsign != "" && (millis() - nearTrackers[a].lastTime > Config.rememberStationTime * 60 * 1000)) {
                nearTrackers[a].callsign    = "";
                nearTrackers[a].distance    = 0.0;
                nearTrackers[a].course      = 0;
                nearTrackers[a].lastTime    = 0;
            }
        }

        for (int b = 0; b < 4 - 1; b++) {
            for (int c = 0; c < 4 - b - 1; c++) {
                if (nearTrackers[c].callsign == "") {       // get "" nearTrackers[] at the end
                    nearTracker temp = nearTrackers[c];
                    nearTrackers[c] = nearTrackers[c + 1];
                    nearTrackers[c + 1] = temp;
                }
            }
        }
        lastDeleteListenedTracker = millis();
    }

    void checkListenedTrackersByTimeAndDelete() {
        if (millis() - lastDeleteListenedTracker > Config.rememberStationTime * 60 * 1000) {
            deleteListenedTrackersbyTime();
        }
    }

    void orderListenedTrackersByDistance(const String& callsign, float distance, float course) {   
        bool shouldSortbyDistance = false;
        bool callsignInNearTrackers = false;

        for (int a = 0; a < 4; a++) {                       // check if callsign is in nearTrackers[]
            if (nearTrackers[a].callsign == callsign) {
                callsignInNearTrackers  = true;
                nearTrackers[a].lastTime = millis();        // update listened millis()
                if (nearTrackers[a].distance != distance) { // update distance if needed
                    nearTrackers[a].distance    = distance;
                    shouldSortbyDistance        = true;
                }
                break;           
            }
        }
    
        if (!callsignInNearTrackers) {                      // callsign not in nearTrackers[]
            for (int b = 0; b < 4; b++) {                   // if nearTrackers[] is available
                if (nearTrackers[b].callsign == "") {
                    shouldSortbyDistance        = true;
                    nearTrackers[b].callsign    = callsign;
                    nearTrackers[b].distance    = distance;
                    nearTrackers[b].course      = int(course);
                    nearTrackers[b].lastTime    = millis();
                    break;
                }
            }

            if (!shouldSortbyDistance) {                    // if no more nearTrackers[] available , it compares distances to move and replace
                for (int c = 0; c < 4; c++) {
                    if (nearTrackers[c].distance > distance) {
                        for (int d = 3; d > c; d--) {
                            nearTrackers[d] = nearTrackers[d - 1];
                        }
                        nearTrackers[c].callsign    = callsign;
                        nearTrackers[c].distance    = distance;
                        nearTrackers[c].course      = int(course);
                        nearTrackers[c].lastTime    = millis();
                        break;
                    }
                }
            }
        }

        if (shouldSortbyDistance) {                         // sorts by distance (only nearTrackers[] that are not "")
            for (int f = 0; f < 4 - 1; f++) {
                for (int g = 0; g < 4 - f - 1; g++) {
                    if (nearTrackers[g].callsign != "" && nearTrackers[g + 1].callsign != "") {
                        if (nearTrackers[g].distance > nearTrackers[g + 1].distance) {
                            nearTracker temp = nearTrackers[g];
                            nearTrackers[g] = nearTrackers[g + 1];
                            nearTrackers[g + 1] = temp;
                        }
                    }
                }
            }
        }
    }

    void checkStandingUpdateTime() {
        if (!sendUpdate && lastTx >= Config.standingUpdateTime * 60 * 1000) {
            sendUpdate = true;
            sendStandingUpdate = true;
            if (!gpsIsActive) {
                SLEEP_Utils::gpsWakeUp();
            }
        }
    }

    void sendBeacon(uint8_t type) {
        if (sendStartTelemetry && Config.battery.sendVoltage && Config.battery.voltageAsTelemetry) {                
            String sender = currentBeacon->callsign;
            for (int i = sender.length(); i < 9; i++) {
                sender += ' ';
            }
            String basePacket = currentBeacon->callsign;
            basePacket += ">APLRT1";
            if (Config.path != "") {
                basePacket += ",";
                basePacket += Config.path;
            }
            basePacket += "::";
            basePacket += sender;
            basePacket += ":";

            String tempPacket = basePacket;
            tempPacket += "EQNS.0,0.01,0";
            displayShow("<<< TX >>>", "Telemetry Packet:", "Equation Coefficients",100);
            LoRa_Utils::sendNewPacket(tempPacket);
            delay(3000);

            tempPacket = basePacket;
            tempPacket += "UNIT.VDC";
            displayShow("<<< TX >>>", "Telemetry Packet:", "Unit/Label",100);
            LoRa_Utils::sendNewPacket(tempPacket);
            delay(3000);

            tempPacket = basePacket;
            tempPacket += "PARM.V_Batt";
            displayShow("<<< TX >>>", "Telemetry Packet:", "Parameter Name",100);
            LoRa_Utils::sendNewPacket(tempPacket);
            delay(3000);
            sendStartTelemetry = false;
        }

        String packet;
        if (Config.wxsensor.sendTelemetry && wxModuleFound && type == 1) { // WX
            packet = APRSPacketLib::generateGPSBeaconPacket(currentBeacon->callsign, "APLRT1", Config.path, "/", APRSPacketLib::encodeGPS(gps.location.lat(),gps.location.lng(), gps.course.deg(), 0.0, currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, "Wx"));
            if (wxModuleType != 0) {
                packet += WX_Utils::readDataSensor(0);
            } else {
                packet += ".../...g...t...";
            }            
        } else {
            String path = Config.path;
            if (gps.speed.kmph() > 200 || gps.altitude.meters() > 9000) {   // avoid plane speed and altitude
                path = "";
            }
            if (miceActive) {
                packet = APRSPacketLib::generateMiceGPSBeacon(currentBeacon->micE, currentBeacon->callsign, currentBeacon->symbol, currentBeacon->overlay, path, gps.location.lat(), gps.location.lng(), gps.course.deg(), gps.speed.knots(), gps.altitude.meters());
            } else {
                packet = APRSPacketLib::generateGPSBeaconPacket(currentBeacon->callsign, "APLRT1", path, currentBeacon->overlay, APRSPacketLib::encodeGPS(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, "GPS"));
            }
        }
        String comment;
        int sendCommentAfterXBeacons;
        if (winlinkCommentState || Config.battery.sendVoltageAlways) {
            if (winlinkCommentState) comment = " winlink";
            sendCommentAfterXBeacons = 1;
        } else {
            comment = currentBeacon->comment;
            sendCommentAfterXBeacons = Config.sendCommentAfterXBeacons;
        }
        String batteryVoltage = POWER_Utils::getBatteryInfoVoltage();
        bool shouldSleepLowVoltage = false;
        #if defined(BATTERY_PIN) || defined(HAS_AXP192) || defined(HAS_AXP2101)
            if (Config.battery.monitorVoltage && batteryVoltage.toFloat() < Config.battery.sleepVoltage) {
                shouldSleepLowVoltage   = true;
            }
            //
            //BATTERY_Utils::checkLowVoltageAndSleep(batteryVoltage.toFloat());
            //
        #endif
        if (Config.battery.sendVoltage && !Config.battery.voltageAsTelemetry) {
            String batteryChargeCurrent = POWER_Utils::getBatteryInfoCurrent();
            #if defined(HAS_AXP192)
                comment += " Bat=";
                comment += batteryVoltage;
                comment += "V (";
                comment += batteryChargeCurrent;
                comment += "mA)";
            #elif defined(HAS_AXP2101)
                comment += " Bat=";
                comment += String(batteryVoltage.toFloat(),2);
                comment += "V (";
                comment += batteryChargeCurrent;
                comment += "%)";
            #elif defined(BATTERY_PIN) && !defined(HAS_AXP192) && !defined(HAS_AXP2101)
                comment += " Bat=";
                comment += String(batteryVoltage.toFloat(),2);
                comment += "V";
                comment += BATTERY_Utils::getPercentVoltageBattery(batteryVoltage.toFloat());
                comment += "%";
            #endif
        }
        if (shouldSleepLowVoltage) {
            packet += " **LowVoltagePowerOff**";
        } else {
            if (comment != "" || (Config.battery.sendVoltage && Config.battery.voltageAsTelemetry)) {
                updateCounter++;
                if (updateCounter >= sendCommentAfterXBeacons) {
                    if (comment != "") packet += comment;
                    if (Config.battery.sendVoltage && Config.battery.voltageAsTelemetry) packet += BATTERY_Utils::generateEncodedTelemetry(batteryVoltage.toFloat());
                    updateCounter = 0;
                }
            }
        }
        #ifdef HAS_TFT
            cleanTFT();
        #endif
        displayShow("<<< TX >>>", "", packet,100);
        LoRa_Utils::sendNewPacket(packet);

        if (Config.bluetooth.type == 0 || Config.bluetooth.type == 2) {     // send Tx packets to Phone too
            BLE_Utils::sendToPhone(packet);
        }

        if (shouldSleepLowVoltage) {
            delay(3000);
            POWER_Utils::shutdown();
        }
        
        if (smartBeaconActive) {
            lastTxLat       = gps.location.lat();
            lastTxLng       = gps.location.lng();
            previousHeading = currentHeading;
            lastTxDistance  = 0.0;
        }
        lastTxTime  = millis();
        sendUpdate  = false;
        #ifdef HAS_TFT
            cleanTFT(); 
        #endif
        if (currentBeacon->gpsEcoMode) {
            gpsShouldSleep = true;
        }
    }

    void checkTelemetryTx() {
        if (Config.wxsensor.active && Config.wxsensor.sendTelemetry && sendStandingUpdate) {
            lastTx = millis() - lastTxTime;
            telemetryTx = millis() - lastTelemetryTx;
            if ((lastTelemetryTx == 0 || telemetryTx > 10 * 60 * 1000) && lastTx > 10 * 1000) {
                sendBeacon(1);
                lastTelemetryTx = millis();
            }
        }
    }

    void saveIndex(uint8_t type, uint8_t index) {
        String filePath;
        if (type == 0) {
            filePath = "/callsignIndex.txt";
        } else {
            filePath = "/freqIndex.txt";
        }
        File fileIndex = SPIFFS.open(filePath, "w");
        if(!fileIndex) {
            return;
        }
        String dataToSave = String(index);
        if (fileIndex.println(dataToSave)) {
            if (type == 0) {
                logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "New Callsign Index saved to SPIFFS");
            } else {
                logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "New Frequency Index saved to SPIFFS");
            }
        } 
        fileIndex.close();
    }

    void loadIndex(uint8_t type) {
        String filePath;
        if (type == 0) {
            filePath = "/callsignIndex.txt";
        } else {
            filePath = "/freqIndex.txt";
        }
        File fileIndex = SPIFFS.open(filePath);
        if(!fileIndex) {
            return;
        } else {
            while (fileIndex.available()) {
                String firstLine = fileIndex.readStringUntil('\n');
                if (type == 0) {
                    myBeaconsIndex = firstLine.toInt();
                    logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "Main", "Callsign Index: %s", firstLine);
                } else {
                    loraIndex = firstLine.toInt();
                    logger.log(logging::LoggerLevel::LOGGER_LEVEL_DEBUG, "LoRa", "LoRa Freq Index: %s", firstLine);
                }
            }
            fileIndex.close();
        }
    }

}