#include "fancontrol.h"
#include "tmp451.h"

//Fan curve table
TemperaturePoint defaultTable[] =
{
    { .temperature_c = 25.0, .fanLevel_f = 0.00 },
    { .temperature_c = 30.0, .fanLevel_f = 0.00 },
    { .temperature_c = 35.0, .fanLevel_f = 0.00 },
    { .temperature_c = 40.0, .fanLevel_f = 0.00 },
    { .temperature_c = 45.0, .fanLevel_f = 0.30 },
    { .temperature_c = 50.0, .fanLevel_f = 0.50 },
    { .temperature_c = 55.0, .fanLevel_f = 0.60 },
    { .temperature_c = 60.0, .fanLevel_f = 0.85 },
    { .temperature_c = 65.0, .fanLevel_f = 0.95 },
    { .temperature_c = 70.0, .fanLevel_f = 1.00 }
};


TemperaturePoint *fanControllerTable;

//Fan
Thread FanControllerThread;
bool fanControllerThreadExit = false;

//Log
char logPath[PATH_MAX];

//Power management
Event powerStateChangeEvent;
bool isPowerStateInitialized = false;

void CreateDir(char *dir)
{
    char dirPath[PATH_MAX];

    for(int i = 0; i < PATH_MAX; i++)
    {
        if(*(dir + i) == '/' && access(dirPath, F_OK) == -1)
        {
            mkdir(dirPath, 0777);
        }
        dirPath[i] = *(dir + i);
    }
}

void InitLog()
{
    if(access(LOG_DIR, F_OK) == -1)
        CreateDir(LOG_DIR);

    if(access(LOG_FILE, F_OK) != -1)
        remove(LOG_FILE);
}

void WriteLog(char *buffer)
{
    FILE *log = fopen(LOG_FILE, "a");
    if(log != NULL)
    {
        fprintf(log, "%s\n", buffer);  
    }
    fclose(log);
}

void WriteConfigFile(TemperaturePoint *table)
{
    if(table == NULL)
    {
        table = malloc(sizeof(defaultTable));
        memcpy(table, defaultTable, sizeof(defaultTable));
    }

    if(access(CONFIG_DIR, F_OK) == -1)
        CreateDir(CONFIG_DIR);

    FILE *config = fopen(CONFIG_FILE, "w");
    fwrite(table, TABLE_SIZE, 1, config);
    fclose(config);
}



void ReadConfigFile(TemperaturePoint **table_out)
{
    InitLog();

    *table_out = malloc(sizeof(defaultTable));
    memcpy(*table_out, defaultTable, sizeof(defaultTable));

    if(access(CONFIG_DIR, F_OK) == -1)
    {
        CreateDir(CONFIG_DIR);
        WriteConfigFile(NULL);
    }
    else
    {
        if(access(CONFIG_FILE, F_OK) == -1)
        {
            WriteConfigFile(NULL);
        }
        else
        {
            FILE *config = fopen(CONFIG_FILE, "r");
            fread(*table_out, TABLE_SIZE, 1, config);
            fclose(config);
        }
    }    
}

bool IsSystemAwake()
{
    // Check if system is in sleep mode by checking applet state
    // appletGetOperationMode returns the current operation mode
    AppletOperationMode opMode = appletGetOperationMode();
    
    // If in handheld or console mode, system is awake
    // If in any other mode (like sleep), we consider it asleep
    return (opMode == AppletOperationMode_Handheld || opMode == AppletOperationMode_Console);
}

void InitFanController(TemperaturePoint *table)
{
    fanControllerTable = table;

    if(R_FAILED(threadCreate(&FanControllerThread, FanControllerThreadFunction, NULL, NULL, 0x4000, 0x3F, -2)))
    {
        WriteLog("Error creating FanControllerThread");
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
    }
}

void FanControllerThreadFunction(void*)
{
    FanController fc;
    float fanLevelSet_f = 0;
    float temperatureC_f = 0;
    u64 awakeSleepTime = 1000000000ULL; // 1 second when awake
    u64 sleepSleepTime = 10000000000ULL; // 10 seconds when in sleep
    int sleepCheckCounter = 0;

    Result rs = fanOpenController(&fc, 0x3D000001);
    if(R_FAILED(rs))
    {
        WriteLog("Error opening fanController");
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
    }

    while(!fanControllerThreadExit)
    {
        // Check if system is awake every 40 iterations (~10 seconds) to reduce overhead
        sleepCheckCounter++;
        if(sleepCheckCounter >= 40)
        {
            bool isAwake = IsSystemAwake();
            sleepCheckCounter = 0;
            
            // If system is asleep, use longer sleep interval
            if(!isAwake)
            {
                svcSleepThread(sleepSleepTime);
                continue;
            }
        }

        rs = Tmp451GetSocTemp(&temperatureC_f);
        if(R_FAILED(rs))
        {
            WriteLog("tsSessionGetTemperature error");
            diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
        }

        if(temperatureC_f >= 0 && temperatureC_f <= fanControllerTable->temperature_c)
        {
            float m = 0;
            float q = 0;

            m = fanControllerTable->fanLevel_f / fanControllerTable->temperature_c;
            q = 0 - m;

            fanLevelSet_f = (m * temperatureC_f) + q;

        }else if(temperatureC_f >= (fanControllerTable + 9)->temperature_c)
        {
            fanLevelSet_f = (fanControllerTable + 9)->fanLevel_f;
        }else
        {
            for(int i = 0; i < (TABLE_SIZE/sizeof(TemperaturePoint)) - 1; i++)
            {
                if(temperatureC_f >= (fanControllerTable + i)->temperature_c && temperatureC_f <= (fanControllerTable + i + 1)->temperature_c)
                {
                    float m = 0;
                    float q = 0;

                    m = ((fanControllerTable + i + 1)->fanLevel_f - (fanControllerTable + i)->fanLevel_f ) / ((fanControllerTable + i + 1)->temperature_c - (fanControllerTable + i)->temperature_c);
                    q = (fanControllerTable + i)->fanLevel_f - (m * (fanControllerTable + i)->temperature_c);

                    fanLevelSet_f = (m * temperatureC_f) + q;
                    break;
                }
            }
        }

        // Always update fan speed for immediate response
        rs = fanControllerSetRotationSpeedLevel(&fc, fanLevelSet_f);
        if(R_FAILED(rs))
        {
            WriteLog("fanControllerSetRotationSpeedLevel error");
            diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
        }
        
        // Use responsive sleep time when awake (250ms)
        svcSleepThread(awakeSleepTime);
    }

    fanControllerClose(&fc);
}

void StartFanControllerThread()
{
    if(R_FAILED(threadStart(&FanControllerThread)))
    {
        WriteLog("Error starting FanControllerThread");
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
    }
}

void CloseFanControllerThread()
{   
    Result rs;
    fanControllerThreadExit = true;
    rs = threadWaitForExit(&FanControllerThread);
    if(R_FAILED(rs))
    {
        WriteLog("Error waiting fanControllerThread");
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
    }
    threadClose(&FanControllerThread);
    fanControllerThreadExit = false;
    free(fanControllerTable);
}

void WaitFanController()
{
    if(R_FAILED(threadWaitForExit(&FanControllerThread)))
    {
        WriteLog("Error waiting fanControllerThread");
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
    }
}