#include "global.h"
#include <WiFi.h>
#include <FS.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "JsonConstrain.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char* tag = "global";
const char* appName;

SYS_TASK_HANDLES TaskList;
enum  COMM_MODE commMode = COMM_WIFI_AP_BLE;
CHIP_INFO chipInfo;
Version localVersion = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};

void get_chip_mac(char* macStr, size_t size) {
    uint64_t chipMAC = ESP.getEfuseMac();
    uint8_t macByte[6];

    // Extract the MAC address bytes
    for (int i = 0; i < 6; ++i) {
        macByte[i] = (chipMAC >> (8 * (5 - i))) & 0xFF;
    }

    // Format the MAC address string
    snprintf(macStr, size, "%02X:%02X:%02X:%02X:%02X:%02X", 
             macByte[0], macByte[1], macByte[2], 
             macByte[3], macByte[4], macByte[5]);
}

void print_ram_info(void) {
    char s[64];
    snprintf(s, sizeof(s), "Total RAM: %u, Free: %u, Used: %u", 
             ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getHeapSize() - ESP.getFreeHeap());
    ESP_LOGD(tag, "RAM: %s", s);
}

void print_chip_info(void) {
    char macStr[64];
    get_chip_mac(macStr, sizeof(macStr));
    ESP_LOGD(tag, "Chip MAC: %s", macStr);

    print_ram_info();
}

void printTaskInfo(TaskStatus_t taskStatus) {
    uint32_t ulTotalRunTime = taskStatus.ulRunTimeCounter;
    uint32_t ulStatsAsPercentage = (ulTotalRunTime * 100) / ulTotalRunTime; // Total runtime equals 100%

    ESP_LOGI(tag, "TaskInfo: %s\t%u\t%u\t%u\t%u%%",
             taskStatus.pcTaskName,
             taskStatus.uxCurrentPriority,
             taskStatus.xCoreID,
             configMINIMAL_STACK_SIZE - taskStatus.usStackHighWaterMark,
             ulStatsAsPercentage);
}

void printTaskCPUUsage(TaskHandle_t xTask) {
    char buffer[512];
    vTaskGetRunTimeStats(buffer);

    // Parse the buffer to find the information for xTask
    char* taskInfo = strstr(buffer, pcTaskGetName(xTask));
    if (taskInfo != NULL) {
        unsigned long execTime, cpuUsage;
        int parsedItems = sscanf(taskInfo, "%*s %*s %*s %*s %*s %*s %*s %lu %*s %lu", &execTime, &cpuUsage);
        if (parsedItems == 2) {
            ESP_LOGI(tag, "Task CPU Usage: %lu%%", cpuUsage);
        } else {
            ESP_LOGE(tag, "Failed to parse task statistics.");
        }
    } else {
        ESP_LOGE(tag, "Task not found in statistics.");
    }
}

void addTaskHandleToList(SYS_TASK_HANDLES &list, TaskHandle_t handle){
  list.handle[list.count] = handle;
  list.count++;
}

void printTaskStackWatermark(TaskHandle_t xTask) {
    UBaseType_t watermark = uxTaskGetStackHighWaterMark(xTask);
    ESP_LOGI(tag, "Task Stack High Watermark: %u", watermark);
}

// Stack, Heap and CPU Reporting
void report_system_stats()
{
  #if FREERTOS_DIAGNOSTICS == 1
  /*
    // Get the number of tasks
    UBaseType_t taskCount = uxTaskGetNumberOfTasks();
    UBaseType_t highWaterMark = uxTaskGetStackHighWaterMark(NULL);
    // Allocate memory for task status array
    TaskStatus_t *taskStatusArray = (TaskStatus_t*) malloc(sizeof(TaskStatus_t) * taskCount);

    // Get the system status and total CPU time
    uint32_t totalCpuTime = 0;
    uxTaskGetSystemState(taskStatusArray, taskCount, &totalCpuTime);
    // Print task stats header
    Serial.println("Task Name\tStack High Water Mark\tCPU Usage");

    // Print task stats for each task
    for (int i = 0; i < taskCount; i++) {
        // Get the task status
        TaskStatus_t taskStatus = taskStatusArray[i];

        // Calculate task stack high water mark in bytes
        uint32_t stackHighWaterMark = taskStatus.usStackHighWaterMark * sizeof(StackType_t);

        // Calculate task CPU usage as a percentage
        uint32_t cpuUsage = (taskStatus.ulRunTimeCounter * 100) / totalCpuTime;

        // Print task stats
        Serial.printf("%s\t\t%u bytes\t\t%u%%\n", taskStatus.pcTaskName, stackHighWaterMark, cpuUsage);
    }

    // Free task status array memory
    free(taskStatusArray);
  */
  #endif
}

String macToStr(uint8_t* mac){
  String macStr;

  for (int i = 0; i < 6; ++i) {
    // Convert each byte to a hexadecimal string
    // and append it to the String object
    String section = String(mac[i], HEX);
    if (section.length() == 1) {
      section = "0" + section;
    }
    macStr += section;

    // Add colon between sections, but not after the last one
    if (i < 5) {
      macStr += ":";
    }
  }

  // Convert the String to uppercase
  macStr.toUpperCase();

  return macStr;
}

String getMacAddress(void) {
  // Initialize an array to hold the MAC address
  uint8_t macAddr[6];

  // Get the MAC address for the Wi-Fi interface
  WiFi.macAddress(macAddr);

  return macToStr(macAddr);
}

bool updateJsonDocument(JsonDocument &doc, const char* filePath) {
    // Check for available space
    //if (LittleFS.totalBytes() - LittleFS.usedBytes() < doc.memoryUsage()) {
    //    ESP_LOGE(tag, "Not enough space to update file");
    //    return false;
    //}

    String tempFilePath = String(filePath) + ".tmp";
    String backupFilePath = String(filePath) + ".bak";

    File tempFile = LittleFS.open(tempFilePath, "w");
    if (!tempFile) {
        ESP_LOGE(tag, "Failed to create temporary file");
        return false;
    }

    if (serializeJsonPretty(doc, tempFile) == 0) {
        ESP_LOGE(tag, "Failed to write to temporary file");
        tempFile.close();
        LittleFS.remove(tempFilePath);
        return false;
    }
    tempFile.close();

    // Rename original file to backup (instead of deleting it)
    if (LittleFS.exists(filePath)) {
        if (!LittleFS.rename(filePath, backupFilePath)) {
            ESP_LOGE(tag, "Failed to rename original file to backup");
            LittleFS.remove(tempFilePath);
            return false;
        }
    }

    // Rename temporary file to original file name
    if (!LittleFS.rename(tempFilePath, filePath)) {
        ESP_LOGE(tag, "Failed to rename temporary file to original");
        LittleFS.rename(backupFilePath, filePath); // Attempt to restore from backup
        return false;
    }

    // Remove backup file
    LittleFS.remove(backupFilePath);

    ESP_LOGD(tag, "File successfully updated");
    return true;
}


#include "soc/ledc_struct.h"
#define LEDC_CHANNEL_COUNT 8 // ESP32 (one speed mode) exposes 8 channels (0..7) per mode

// NOTE:
// The previous implementation attempted to detect a free channel by checking a raw
// register (conf0.val == 0). This is unreliable because once a channel is configured
// the register won't return to zero automatically, and depending on initialization
// order / speed mode the heuristic can yield false positives or negatives.
// Maintaining our own allocation bitmap is deterministic and thread-safe when
// guarded by a critical section.

static uint32_t s_ledcAllocMask = 0; // bit set => channel allocated
static portMUX_TYPE s_ledcMux = portMUX_INITIALIZER_UNLOCKED;

static inline bool ledcIsAllocated(int ch){
    return (ch >= 0 && ch < LEDC_CHANNEL_COUNT) && ((s_ledcAllocMask >> ch) & 0x1);
}

int findUnusedLedcChannel() { // allocates & returns a free channel, or -1
    portENTER_CRITICAL(&s_ledcMux);
    for(int ch=0; ch<LEDC_CHANNEL_COUNT; ++ch){
        if(((s_ledcAllocMask >> ch) & 0x1) == 0){
            s_ledcAllocMask |= (1u << ch);
            portEXIT_CRITICAL(&s_ledcMux);
            return ch;
        }
    }
    portEXIT_CRITICAL(&s_ledcMux);
    return -1;
}

void releaseLedcChannel(int ch){
    if(ch < 0 || ch >= LEDC_CHANNEL_COUNT) return;
    portENTER_CRITICAL(&s_ledcMux);
    s_ledcAllocMask &= ~(1u << ch);
    portEXIT_CRITICAL(&s_ledcMux);
}

bool markLedcChannelUsed(int ch){ // use if channel configured elsewhere before allocator
    if(ch < 0 || ch >= LEDC_CHANNEL_COUNT) return false;
    portENTER_CRITICAL(&s_ledcMux);
    if( (s_ledcAllocMask >> ch) & 0x1 ){ // already allocated
        portEXIT_CRITICAL(&s_ledcMux);
        return false;
    }
    s_ledcAllocMask |= (1u << ch);
    portEXIT_CRITICAL(&s_ledcMux);
    return true;
}




#include "esp_timer.h"
#include "freertos/task.h"
void Log_CPU_Load(void) {
    static const char* tag = "CPU_Load";
    
    // Get run time stats
    char *stats = (char*)malloc(2048);
    if (!stats) {
        ESP_LOGE(tag, "Failed to allocate memory for stats");
        return;
    }

    vTaskGetRunTimeStats(stats);
    
    // Calculate percentage load for each core
    uint32_t total_runtime = 0;
    uint32_t idle_0 = 0;
    uint32_t idle_1 = 0;
    
    TaskStatus_t *task_array;
    UBaseType_t task_count;
    
    // Get task array
    task_count = uxTaskGetNumberOfTasks();
    task_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * task_count);
    
    if (!task_array) {
        free(stats);
        ESP_LOGE(tag, "Failed to allocate memory for task array");
        return;
    }
    
    // Get task stats
    task_count = uxTaskGetSystemState(task_array, task_count, &total_runtime);
    if (total_runtime == 0) {
        ESP_LOGW(tag, "Runtime stats not ready yet (total_runtime==0)");
        free(stats);
        free(task_array);
        return;
    }
    
    // Find IDLE tasks
    for (UBaseType_t i = 0; i < task_count; i++) {
        if (strcmp(task_array[i].pcTaskName, "IDLE0") == 0) {
            idle_0 = task_array[i].ulRunTimeCounter;
        }
        else if (strcmp(task_array[i].pcTaskName, "IDLE1") == 0) {
            idle_1 = task_array[i].ulRunTimeCounter;
        }
    }
    
    // Calculate loads
    float core0_load = 100.0f - ((float)idle_0 * 100.0f / (float)total_runtime);
    float core1_load = 100.0f - ((float)idle_1 * 100.0f / (float)total_runtime);
    
    // Log results
    ESP_LOGI(tag, "CPU Load - Core 0: %.1f%%, Core 1: %.1f%%", core0_load, core1_load);
    
    // Cleanup
    free(stats);
    free(task_array);
}

/*
void print_task_watermarks(void) {
    static const char* tag = "TaskWatermarks";
    UBaseType_t taskCount = uxTaskGetNumberOfTasks();
    TaskStatus_t* taskStatusArray = (TaskStatus_t*)pvPortMalloc(taskCount * sizeof(TaskStatus_t));
    
    if (taskStatusArray != NULL) {
        UBaseType_t actualTaskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
        
        ESP_LOGI(tag, "Task Stack Watermarks:");
        ESP_LOGI(tag, "%-20s %-10s %-10s %-10s", "Task Name", "Priority", "Stack", "Watermark");
        
        for (UBaseType_t i = 0; i < actualTaskCount; i++) {
            UBaseType_t watermark = taskStatusArray[i].usStackHighWaterMark;
            const char* warning = "";
            
            // Warning levels
            if (watermark < 50) {
                warning = " !!! CRITICAL !!!";
            } else if (watermark < 100) {
                warning = " ! WARNING !";
            }
            
            ESP_LOGI(tag, "%-20s %-10d %-10d %-10d%s",
                    taskStatusArray[i].pcTaskName,
                    (int)taskStatusArray[i].uxCurrentPriority,
                    (int)taskStatusArray[i].usStackHighWaterMark * sizeof(StackType_t),
                    (int)watermark,
                    warning);
        }
        
        vPortFree(taskStatusArray);
    } else {
        ESP_LOGE(tag, "Failed to allocate memory for task status array");
    }
}
*/

/*
void print_task_watermarks(void) {
    static const char* tag = "TaskWatermarks";
    char* buf = (char*)malloc(2048);
    
    if (buf) {
        vTaskList(buf);
        ESP_LOGI(tag, "\nTask Name\tStatus\tPrio\tHWM\tTask#\n%s", buf);
        free(buf);
    } else {
        ESP_LOGE(tag, "Failed to allocate buffer for task list");
    }
}
*/

void print_task_watermarks(void) {
    static const char* tag = "TaskWatermarks";
    
    // Get current task handle
    TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
    
    if (currentTask != NULL) {
        // Get task info
        const char* name = pcTaskGetName(currentTask);
        UBaseType_t priority = uxTaskPriorityGet(currentTask);
        eTaskState state = eTaskGetState(currentTask);
        UBaseType_t watermark = uxTaskGetStackHighWaterMark(currentTask);
        
        // Print header
        ESP_LOGI(tag, "\nTask Statistics");
        ESP_LOGI(tag, "%-16s %-8s %-8s %-8s %s", "Name", "State", "Prio", "HWM", "Status");
        ESP_LOGI(tag, "---------------------------------------------------");
        
        // Set warning level
        const char* warning = "";
        if (watermark < 50) warning = " !CRITICAL!";
        else if (watermark < 100) warning = " !WARNING!";
        
        // Print task info
        ESP_LOGI(tag, "%-16s %-8d %-8d %-8d%s", 
                 name,
                 state,
                 priority,
                 watermark,
                 warning);
    } else {
        ESP_LOGE(tag, "Failed to get current task handle");
    }
}