#include <stdio.h>      /*標準輸入輸出定義*/
#include <stdlib.h>     /*標準函數庫定義*/
#include <string.h>
#include <stdint.h>

#include <sys/types.h>
#include <dirent.h>

#include "../Config.h"
#include "../Log/log.h"
#include "../Define/define.h"

#include "../ShareMemory/shmMem.h"
#include "../CSU/main.h"

struct SysConfigData *pSysConfig = NULL;
struct SysInfoData *pSysInfo = NULL;
struct OCPP16Data *ShmOCPP16Data = NULL;
struct ChargingInfoData *pAcChargingInfo = NULL;
static struct ChargingInfoData *pDcChargingInfo = NULL;
#define MODELNAME_FAIL                          (0)
#define UPGRADE_FAN                             (0x02)
#define UPGRADE_RB                              (0x09) //0x09 for DD360 dispenser
#define UPGRADE_PRI                             (0x04)
#define UPGRADE_AC                              (0x05)
#define UPGRADE_LED                             (0x06)

//------------------------------------------------------------------------------
static char *_priPortName = "/dev/ttyS1";
static char *_485PortName = "/dev/ttyS5";

//------------------------------------------------------------------------------

void KillAllTask(void)
{
    pSysInfo->PageIndex = _LCM_MAINTAIN;

    system("killall Module_EventLogging");
    system("killall Module_PrimaryComm");
    system("killall Module_EvComm");
    system("killall Module_LcmControl");
    system("killall Module_InternalComm");
    system("killall Module_DoComm");
    return ;
}

void KillTask(void)
{
    pSysInfo->PageIndex = _LCM_MAINTAIN;

    system("killall Module_EventLogging");
    system("killall Module_PrimaryComm");
    system("killall Module_EvComm");
    system("killall Module_LcmControl");
    system("killall Module_InternalComm");
    //system("killall Module_DoComm");
    return ;

}
void setChargerMode(uint8_t gunIndex, uint8_t mode)
{
    pDcChargingInfo = (struct ChargingInfoData *)GetDcChargingInfoData(gunIndex);

    pDcChargingInfo->SystemStatus = mode;

    log_info(" ====== System Status:%d ======",mode);
}
void TryCloseWatchdog()
{
    system("echo V > /dev/watchdog");
}
static int InitComPort(uint8_t target)
{
    int fd;
    struct termios tios;

    if (target == UPGRADE_PRI) {
        fd = open(_priPortName, O_RDWR);
    } else if (target == UPGRADE_FAN ||
               target == UPGRADE_RB ||
               target == UPGRADE_AC ||
               target == UPGRADE_LED
              ) {
        fd = open(_485PortName, O_RDWR);
    }

    if (fd <= 0) {
        log_error("open 407 Communication port NG ");
        return -1;
    }

    ioctl (fd, TCGETS, &tios);
    tios.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
    tios.c_lflag = 0;
    tios.c_iflag = 0;
    tios.c_oflag = 0;
    tios.c_cc[VMIN] = 0;
    tios.c_cc[VTIME] = (uint8_t)1;
    tios.c_lflag = 0;
    tcflush(fd, TCIFLUSH);
    ioctl (fd, TCSETS, &tios);

    return fd;
}
bool IsConnectorWholeIdle()
{
    bool result = true;

    for (uint8_t count = 0; count < pSysConfig->TotalConnectorCount; count++) {
        pDcChargingInfo = (struct ChargingInfoData *)GetDcChargingInfoData(count);

        if (pDcChargingInfo->SystemStatus != S_IDLE &&
                pDcChargingInfo->SystemStatus != S_RESERVATION &&
                pDcChargingInfo->SystemStatus != S_MAINTAIN) {
            result = false;
            break;
        }
    }
/*
    for (uint8_t count = 0; count < pSysConfig->AcConnectorCount; count++) {
        pAcChargingInfo = (struct ChargingInfoData *)GetAcChargingInfoData(count);

        if (pAcChargingInfo->SystemStatus != S_IDLE &&
                pAcChargingInfo->IsErrorOccur == NO) {
            result = false;
            break;
        }
    }
*/
    return result;
}


static int InitCanBus(void)
{
    int fd = -1;
    int nbytes;
    struct timeval tv;
    struct ifreq ifr0;
    struct sockaddr_can addr0;

    system("/sbin/ip link set can0 down");
    system("/sbin/ip link set can0 type can bitrate 500000 restart-ms 100");
    system("/sbin/ip link set can0 up");

    fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);

    tv.tv_sec = 0;
    tv.tv_usec = 10000;
    if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct  timeval)) < 0) {
        log_error("Set SO_RCVTIMEO NG");
    }
    nbytes = 40960;
    if (setsockopt(fd, SOL_SOCKET,  SO_RCVBUF, &nbytes, sizeof(int)) < 0) {
        log_error("Set SO_RCVBUF NG");
    }
    nbytes = 40960;
    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &nbytes, sizeof(int)) < 0) {
        log_error("Set SO_SNDBUF NG");
    }

    strcpy(ifr0.ifr_name, "can0");
    ioctl(fd, SIOCGIFINDEX, &ifr0); /* ifr.ifr_ifindex gets filled with that device's index */
    addr0.can_family = AF_CAN;
    addr0.can_ifindex = ifr0.ifr_ifindex;
    bind(fd, (struct sockaddr *)&addr0, sizeof(addr0));

    return fd;
}

unsigned long long getAvailableMemory()
{
    long pages = sysconf(_SC_AVPHYS_PAGES);
    long page_size = sysconf(_SC_PAGE_SIZE);
    return pages * page_size;
}

static int CheckUpdateProcess(void)
{
    //bool isPass = true;
    uint8_t retSucc = 0;
    uint8_t retFail = 0;
    uint8_t index = 0;
    uint8_t target = 0;
    char Buf[256];
    char *new_str = NULL;
    uint8_t *ptr = NULL;
    int fd = 0;
    int CanFd = 0;
    int uartFd = 0;
    unsigned int Type = 0;
    long int MaxLen = 48 * 1024 * 1024, ImageLen = 0;
    DIR *d;
    struct dirent *dir;
    DcCommonInfo *ShmDcCommonData = (DcCommonInfo *)GetShmDcCommonData();
    struct ChargingInfoData *pDcChargingInfo = NULL;

    if (getAvailableMemory() < (200 * 1024 * 1024))
    {
        log_info("Available memory (%.2f Bytes) less than 200 MBytes, free cache first.\n", getAvailableMemory() / (1024 * 1024.0));
        system("echo 3 > /proc/sys/vm/drop_caches");
    }

    pSysConfig = (struct SysConfigData *)GetShmSysConfigData();
    d = opendir("/mnt/");
    if (d) {
        while ((dir = readdir(d)) != NULL) {
            if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) {
                continue;
            }

            new_str = calloc(strlen("/mnt/") + strlen(dir->d_name) + 1, sizeof(char));
            //new_str[0] = '\0';
            strcat(new_str, "/mnt/");
            strcat(new_str, dir->d_name);
            log_info("%s%s", "/mnt/", dir->d_name);
            
            fd = open(new_str, O_RDONLY);
            if (fd < 0) {
                return FAIL;
            }

            ptr = calloc(MaxLen, sizeof(char)); //-48 is take out the header
            //memset(ptr, 0xFF, MaxLen);  //-48 is take out the header

            //get the image length
            ImageLen = read(fd, ptr, MaxLen);
            for (uint8_t i = 0; i < 16; i++) {
                if (pSysConfig->ModelName[i] != ptr[i]) {
                    return FAIL;
                }
            }

            log_info("model name check pass. ");
            if (ImageLen > 20) {
                Type = (((unsigned int)ptr[16]) << 24 |
                        ((unsigned int)ptr[17]) << 16 |
                        ((unsigned int)ptr[18]) << 8  |
                        ((unsigned int)ptr[19]));
                log_info("Typed...%x ", Type);
                free(ptr);
                switch (Type) {
                case 0x10000001:
                case 0x10000002:
                case 0x10000003:
                case 0x10000004:
                case 0x10000005:
                    if (Upgrade_Flash(Type, new_str, (char *)pSysConfig->ModelName) == PASS) {
                        //return PASS;
                        retSucc++;
                    } else {
                        log_info("Upgrade %x Failed", Type);
                        //return FAIL;
                        retFail++;
                    }
                    break;

                case 0x10000007:
                case 0x10000008:
                case 0x10000009:
                case 0x1000000A:
                    CanFd = InitCanBus();
                    if (CanFd > 0) {
                        for (index = 0; index < pSysConfig->TotalConnectorCount; index++) {
                            pDcChargingInfo = (struct ChargingInfoData *)GetDcChargingInfoData(index);
                            if (pDcChargingInfo->Type == _Type_CCS_2) {
                                uint8_t targetID = pDcChargingInfo->Evboard_id;

                                if (pSysConfig->TotalConnectorCount == 1 &&
                                        ShmDcCommonData->CcsVersion == _CCS_VERSION_CHECK_TAG_V015S0) {
                                    //targetID += 1;
                                }

                                system("echo 3 > /proc/sys/vm/drop_caches");
                                sleep(2);
                                log_info("Upgrade CCS Processing..target id = %d ", targetID);
                                if (Upgrade_CCS(CanFd,
                                                Type,
                                                targetID,
                                                new_str,
                                                (char *)pSysConfig->ModelName) == FAIL) {
                                    log_info("Upgrade CCS Failed ");
                                    retFail++;
                                } else {
                                    retSucc++;
                                }
                            }
                        }
                        close(CanFd);
                    }
                    memset(Buf, 0, sizeof(Buf));
                    sprintf(Buf, "rm -rvf /mnt/%s", new_str);
                    system(Buf);
                    //isPass = true;
#if 0
                    CanFd = InitCanBus();
                    if (CanFd > 0) {
                        for (index = 0; index < pSysConfig->TotalConnectorCount; index++) {
                            //if (!isPass) {
                            //    break;
                            //}
                            if (chargingInfo[index]->Type == _Type_CCS_2) {
                                if (Upgrade_CCS(CanFd, Type, chargingInfo[index]->Evboard_id, new_str, (char *)pSysConfig->ModelName) == FAIL) {
                                    //isPass = false;
                                    log_info("Upgrade %x Failed", Type);
                                    retFail++;
                                }
                            }
                        }
                    } else {
                        log_error("Upgrade CCS open CAN FD fail.");
                        //isPass = false;
                        return FAIL;
                    }

                    if (retFail != 0) {
                        break;
                    } else {
                        retSucc++;
                    }
                    //return isPass;
#endif //0
                    break;

                case 0x10000006:
                case 0x1000000D:
                case 0x1000000E:
                case 0x20000002:
                case 0x10000014:
                    // CSU_PRIMARY_CONTROLLER : 0x10000006
                    target = 0x00;

                    if (Type == 0x10000006) {
                        target = UPGRADE_PRI;
                    } else if (Type == 0x1000000D) {
                        target = UPGRADE_RB;
                    } else if (Type == 0x1000000E) {
                        target = UPGRADE_FAN;
                    } else if (Type == 0x20000002) {
                        target = UPGRADE_AC;
                    } else if (Type == 0x10000014) {
                        target = UPGRADE_LED;
                    }

                    uartFd = InitComPort(target);

                    if (Upgrade_UART(uartFd, Type, target, new_str, (char *)pSysConfig->ModelName) == PASS) {
                        //return PASS;
                        retSucc++;
                    } else {
                        log_info("Upgrade %x Failed", Type);
                        //return FAIL;
                        return FAIL;
                    }

                    if (uartFd > 0) {
                        close(uartFd);
                    }
                    break;

                case 0x1000000B:
                case 0x1000000C:
                    // CHAdeMO_BOARD : 0x1000000B, GBT : 0x1000000C
                    //bool isPass = true;
                    CanFd = InitCanBus();
                    if (CanFd > 0) {
                        for (index = 0; index < pSysConfig->TotalConnectorCount; index++) {
                            pDcChargingInfo = (struct ChargingInfoData *)GetDcChargingInfoData(index);

                            //if (!isPass) {
                            //    break;
                            //}

                            if ((Type == 0x1000000B && pDcChargingInfo->Type == _Type_Chademo) ||
                                    (Type == 0x1000000C && pDcChargingInfo->Type == _Type_GB)) {
                                if (Upgrade_CAN(CanFd, Type, pDcChargingInfo->Evboard_id, new_str, (char *)pSysConfig->ModelName) == PASS) {
                                    //isPass = PASS;
                                    retSucc++;
                                } else {
                                    log_info("Upgrade %x Failed", Type);
                                    //isPass = FAIL;
                                    retFail++;
                                }
                            }
                        }
                    } else {
                        log_info("Upgrad FD fail. ");
                        //isPass = false;
                        return FAIL;
                    }

                    //return isPass;
                    break;
                }
            } else {
                free(ptr);
            }
            free(new_str);
 
        }
    }
    free(dir);
    closedir(d);

    if (retFail != 0) {
        return FAIL;
    }

    return PASS;
}

void CheckFwUpdateFunction(void)
{
    //log_info("pSysInfo->FirmwareUpdate = %d ", pSysInfo->FirmwareUpdate);
    if (pSysInfo->FirmwareUpdate == YES) {
        log_info("ftp : update start. ");
        TryCloseWatchdog();
        pSysInfo->SystemPage = _LCM_MAINTAIN;
        for (uint8_t gun_index = 0; gun_index < pSysConfig->TotalConnectorCount; gun_index++) {
            setChargerMode(gun_index, MODE_UPDATE);
        }
        sleep(1);
        uint8_t updateResult = CheckUpdateProcess();

        if (updateResult == PASS) {
            log_info("ftp : update complete. ");
        } else if (updateResult == MODELNAME_FAIL) {
            log_info("ftp : model name is none match. ");
            KillAllTask();
            pSysInfo->FirmwareUpdate = NO;
            sleep(5);
            system("/usr/bin/run_evse_restart.sh");
            return;
        } else {
            log_info("ftp : update fail. ");
        }

        pSysInfo->FirmwareUpdate = NO;
        sleep(5);
        system("reboot -f");
    } else if (ShmOCPP16Data->MsMsg.bits.UpdateFirmwareReq == YES) {
        ShmOCPP16Data->MsMsg.bits.UpdateFirmwareReq = NO;

        if (strcmp((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "Downloaded") == EQUAL) {
            log_info("Backend : update start. ");
            TryCloseWatchdog();
            strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "");
            strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "Installing");
            ShmOCPP16Data->SpMsg.bits.FirmwareStatusNotificationReq = YES;
            KillTask();

            for (uint8_t _index = 0; _index < pSysConfig->TotalConnectorCount; _index++) {
                setChargerMode(_index, MODE_UPDATE);
            }

            for (uint8_t _index = 0; _index < pSysConfig->AcConnectorCount; _index++) {
                pAcChargingInfo = (struct ChargingInfoData *)GetAcChargingInfoData(_index);

                pAcChargingInfo->SystemStatus = MODE_UPDATE;
            }
            sleep(1);

            uint8_t updateResult = CheckUpdateProcess();

            if (updateResult == PASS) {
                log_info("Backend : update complete. ");
                strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "Installed");
            } else if (updateResult == MODELNAME_FAIL) {
                log_info("Backend : model name is none match. ");
                KillAllTask();
                strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "InstallationFailed");
                ShmOCPP16Data->SpMsg.bits.FirmwareStatusNotificationReq = YES;
                sleep(5);
                system("/usr/bin/run_evse_restart.sh");
                return;
            } else {
                log_info("Backend : update fail. ");
                strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "InstallationFailed");
            }

            strcpy((char *)ShmOCPP16Data->FirmwareStatusNotification.Status, "Installed");
            ShmOCPP16Data->SpMsg.bits.FirmwareStatusNotificationReq = YES;
            sleep(5);
            system("reboot -f");
        }
    }
}

int main(int argc, char *argv[])
{
    if (CreateAllCsuShareMemory() == FAIL) {
        log_error("create share memory error");
        return FAIL;
    }
    
    MappingGunChargingInfo("Upgrade Task");

    pSysConfig = (struct SysConfigData *)GetShmSysConfigData();
    pSysInfo = (struct SysInfoData *)GetShmSysInfoData();
    ShmOCPP16Data = (struct OCPP16Data *)GetShmOCPP16Data();
   
    while (1) {
        if (IsConnectorWholeIdle())
            CheckFwUpdateFunction();
        sleep(3);
    } //while
    return 0;
}