#include "CanDataHandler.h"
#include "cmsis_os2.h"
#include "FreeRTOS.h"
#include "CLSAddress.h"
#include "fatfs.h"
#include "FirmwareUpdate.h"
#include "UsbDataHandler.h"

// Memory for the task
StaticTask_t CLS_FW_Task_cb;
uint32_t CLS_FW_Task_stk[512];
// Attributes for the task
osThreadId_t CLS_FW_Task_id;
const osThreadAttr_t CLS_FW_Task_attr = {
  .name = "CLS_FW_Task",
  .attr_bits = 0U,
  .cb_mem = &CLS_FW_Task_cb,
  .cb_size = sizeof(CLS_FW_Task_cb),
  .stack_mem = CLS_FW_Task_stk,
  .stack_size = sizeof(CLS_FW_Task_stk),
  .priority = osPriorityNormal,
  .reserved = 0U
};

osMutexId_t CLS_FW_Task_FileLock = NULL;
static FIL fw_file;
static FwFrame frame = {0};



// wait_for_start_callback to get notified when the slave is ready
void wait_for_start_callback(uint16_t canid, uint8_t * data, uint8_t size) {
    osThreadFlagsSet(CLS_FW_Task_id,  TASK_FLAG_WAIT);
}

void wait_for_start(FirmwareUpdateArgs * configuration) {

    // setup wait_for_start_callback to get notified when the slave is ready
    uint16_t ready_canid = GENERATE_CLS_ADDRESS(CLS_CODE_FIMWARE, configuration->device, CLS_CH_FW_MISO);
    // wait_for_start_callback // MessageCode::Firmware(FirmwareChannel::SlaveOutMasterIn);
    CanData_regEventMsg(ready_canid, wait_for_start_callback);

    // setup the command to send the slave into booloader mode
    uint16_t target_canid = GENERATE_CLS_ADDRESS(CLS_CODE_FIMWARE, configuration->device, CLS_CH_FW_BOOTCALL);
    uint8_t bootloader_call[8] = {0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab};


    // wait for the slave to be ready to update:
    uint32_t counter = 0;
    do {
        // send bootloader command
        CLS_BSP_TxHeaderType header = CREATE_BSP_CAN_HEADER(target_canid,CLS_BSP_DLC_BYTES_8);
        CLS_BSP_CAN_AddMessageToSend(&header,bootloader_call); 
        counter++;

        // after some time abort if we dont get a connection
        if(counter > BOOTLOADER_CALL_MAX_TRIES) {
            // TODO: Logging
            CanData_removeEvent(ready_canid);
            f_close(&fw_file);
            osThreadExit();
        }

    // wait for the slave
    } while(osThreadFlagsWait(TASK_FLAG_WAIT,osFlagsWaitAny,BOOTLOADER_CALL_RETRY_TIME) == (uint32_t)osErrorTimeout);
    
    //remove the wait_for_start_callback again
    CanData_removeEvent(ready_canid);

    // reset the fw_file
    f_lseek(&fw_file,0);
    frame.counter = 0;
}



void running_callback_error(uint16_t canid, uint8_t * data, uint8_t size) {
    osMutexAcquire(CLS_FW_Task_FileLock, osWaitForever);
    f_lseek(&fw_file,0);
    frame.counter = 0;
    osMutexRelease(CLS_FW_Task_FileLock);
    osThreadFlagsSet(CLS_FW_Task_id,  TASK_FLAG_RUNNING);
}

void running_callback_ack(uint16_t canid, uint8_t * data, uint8_t size) { 
    osThreadFlagsSet(CLS_FW_Task_id,  TASK_FLAG_RUNNING);
}


void running(FirmwareUpdateArgs * configuration) {
    uint16_t error_canid = GENERATE_CLS_ADDRESS(CLS_CODE_FIMWARE, configuration->device, CLS_CH_FW_MISO);
    uint16_t ack_canid = GENERATE_CLS_ADDRESS(CLS_CODE_FIMWARE, configuration->device, CLS_CH_FW_SLAVE_FEEDBACK);
    uint16_t target_canid = GENERATE_CLS_ADDRESS(CLS_CODE_FIMWARE, configuration->device, CLS_CH_FW_MOSI);
    CLS_BSP_TxHeaderType header = CREATE_BSP_CAN_HEADER(target_canid,CLS_BSP_DLC_BYTES_8);

    CanData_regEventMsg(error_canid,running_callback_error);
    CanData_regEventMsg(ack_canid, running_callback_ack);

    unsigned int read_bytes = 0;

    while (true)
    {
        
        // read fw file and and send data
        osMutexAcquire(CLS_FW_Task_FileLock, osWaitForever);
        f_read(&fw_file, &frame.data, sizeof(frame.data), &read_bytes);
        CLS_BSP_CAN_AddMessageToSend(&header, (uint8_t*)&frame);
        frame.counter++;
        osMutexRelease(CLS_FW_Task_FileLock);

        // transport window response
        if(frame.counter % UPDATE_ACK_WINDOW == 0) {
            osThreadFlagsWait(TASK_FLAG_RUNNING, osFlagsWaitAny, osWaitForever);
        }

        // end of file
        if(read_bytes != sizeof(frame.data)) { 
            break;
        }

    }


    uint64_t done = UPDATE_DONE_MSG;
    CLS_BSP_CAN_AddMessageToSend(&header,(uint8_t*)&done); 

    CanData_removeEvent(error_canid);
    CanData_removeEvent(ack_canid);
}


#define MAX_FW_UPDATES 16
osMessageQueueId_t fwUpdateQueue;

void CLSFirmwareUpdateTask_func(void *argument);

void FirmwareUpdateTask_start(FirmwareUpdateArgs args) {
  // Task functionality here
    if(CLS_FW_Task_FileLock == NULL) {
        CLS_FW_Task_FileLock = osMutexNew(NULL);
    }

        // Initialize the queue if it hasn't been initialized yet
    if(fwUpdateQueue == NULL) {
        fwUpdateQueue = osMessageQueueNew(MAX_FW_UPDATES, sizeof(FirmwareUpdateArgs), NULL);
    }

    if(CLS_FW_Task_FileLock == NULL) { 
        // error cant create mutex
        return;
    }   

    osMessageQueuePut(fwUpdateQueue, &args, 0, 0);


  // check CLS_FW_Task_id is null or stopped
  // only one Task can runn at once
   if(CLS_FW_Task_id == NULL || osThreadGetState(CLS_FW_Task_id) == osThreadTerminated) {
    CLS_FW_Task_id = osThreadNew(CLSFirmwareUpdateTask_func, NULL, &CLS_FW_Task_attr);
   }
}

UsbDataPacket buffer;
void CLSFirmwareUpdateTask_func(void *argument) {
    FirmwareUpdateArgs args;
    osStatus_t status;

    while(1) {
        // Wait for a firmware update request
        status = osMessageQueueGet(fwUpdateQueue, &args, NULL, 100);

        // If the queue is empty, terminate the task
        if (status != osOK) {
            osThreadExit();
        }

        char * filename = args.name;
        if(f_open(&fw_file, filename, FA_READ) != FR_OK) {
            // TODO: Logging
            continue;
        }
    
        wait_for_start(&args);

        running(&args);

        //FirmwareUpdateDone done = {
        //    .device_id = args.device,
        //};
        //UsbDataPacketSendMessage(UsbPackageType_FIRMWAREUPDATEDONE, &buffer,FirmwareUpdateDone_fields,&done);
        
        f_close(&fw_file);
    }
}