#include "ocpp16_client.h"
#include "ocpp16_encode.h"
#include "ocpp16_decode.h"
#include "cJSON.h"
#include "ocpp16.h"
#include "mlogger.h"
#include "ocpp16_configkey.h"
#include "ocpp16_authorization.h"
#include "ocpp16_OfflineChargingRecords.h"
#include "ocpp16_ChargingProfiles.h"
#include "ccu_config.h"
#include "ccu_port.h"
#include "secc_port.h"

static int generate_random_i32(void)
{
    srand((int32_t)time(0));
    return  rand();
}

static void ocpp16_format_MeterValues(void *secc, MeterValue_t* info, uint32_t measurand)
{
    secc_port_t* dev = (secc_port_t*)secc;
    ccu_port_t *ccu = (ccu_port_t *)dev->ccu;
    time_t now = time(NULL);
    info->count=0;
    info->items[info->count].sampledvalue.count=0;
    sprintf_UTCTime(info->items[info->count].timestamp, now);
    if (measurand & (1<<Measurand_Energy_Active_Import_Register))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_kWh;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Energy_Active_Import_Register;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", dev->EMeters);
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_Voltage))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_V;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Voltage;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", dev->VMeters);
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_Current_Import))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_A;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Current_Import;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", dev->IMeters);
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_SoC))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_Percent;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_SoC;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%d", dev->SOC);
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_Temperature))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_Celsius;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Temperature;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", dev->GUNTemp1);
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].location = Location_Cable;
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_Temperature))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_Celsius;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Temperature;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", dev->GUNTemp2);
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].location = Location_Cable;
        info->items[info->count].sampledvalue.count++;
    }
    if (measurand & (1<<Measurand_Temperature))
    {
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].unit = UnitOfMeasure_Celsius;
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].measurand = Measurand_Temperature;
        sprintf(info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].value, "%f", ccu->BoxTemp);
        info->items[info->count].sampledvalue.items[info->items[info->count].sampledvalue.count].location = Location_Body;
        info->items[info->count].sampledvalue.count++;
    }
    info->count++;
    printi("info->count:%d, info->items[info->count].sampledvalue.count:%d", info->count, info->items[info->count-1].sampledvalue.count);
}

void ocpp16_report_MeterValues(void* ocpp, void* secc)
{
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    secc_port_t *gun = (secc_port_t*)secc;
    {
        MeterValuesReq_t req;
        memset(&req, 0, sizeof(req));
        req.connectorId = gun->config->connectorId;
        req.transactionId = gun->transactionId;
        ocpp16_format_MeterValues(gun, &req.meterValue, ocpp16_configkeys.coreProfile.MeterValuesAlignedData);
        ocpp16_MeterValues_request(dev, &req);
    }
}

void ocpp16_report_MeterValues_Interval(void* ocpp, int connectorId)
{
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    ocpp16_config_t *config = (ocpp16_config_t*)dev->config;
    ccu_port_t *ccu = (ccu_port_t *)dev->ccu;
    secc_port_t *gun = NULL;
    if (connectorId == 1) gun = (secc_port_t*)ccu->seccA; 
    if (connectorId == 2) gun = (secc_port_t*)ccu->seccB; 
    if (gun == NULL) return ;

    if (config->enable == 1)
    {
        if (dev->boot_notification_readay == 0) return;

        if (ocpp16_configkeys.coreProfile.ClockAlignedDataInterval != 0)
        {
            time_t now = time(NULL);
            if ((now - gun->ClockAlignedDataTime) >= ocpp16_configkeys.coreProfile.ClockAlignedDataInterval)
            {
                gun->ClockAlignedDataTime = now;
                ocpp16_report_MeterValues(dev, gun);
            }
        }
        if (ocpp16_configkeys.coreProfile.MeterValueSampleInterval != 0)
        {
            time_t now = time(NULL);
            if ((now - gun->MeterValueSampleTime) >= ocpp16_configkeys.coreProfile.MeterValueSampleInterval)
            {
                gun->MeterValueSampleTime = now;
                ocpp16_report_MeterValues(dev, gun);
            }
        }
    }else{
        ocpp16_OfflineChargingRecords_metervalues(gun);
    }
}

void ocpp16_report_StatusNotification(void* ocpp, int connectorId, int GUN_Error, int GUN_State)
{
    // printd("ocpp=%p\n", ocpp);
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    // printd("ocpp=%p, dev=%p\n", ocpp, dev);
    ocpp16_config_t *config = (ocpp16_config_t*)dev->config;
    // printd("ocpp=%p, dev=%p, config:%p\n", ocpp, dev, config);
    ccu_port_t *ccu = (ccu_port_t *)dev->ccu;
    // printd("ocpp=%p, dev=%p, config:%p, ccu=%p\n", ocpp, dev, config, ccu);
    secc_port_t *seccA = (secc_port_t*)ccu->seccA;
    secc_port_t *seccB = (secc_port_t*)ccu->seccB;

    // printd("config->enable=%d, dev->boot_notification_readay=%d\n", config->enable, dev->boot_notification_readay);

    if (config->enable && dev->boot_notification_readay == 1)
    {
        StatusNotificationReq_t req;
        memset(&req, 0, sizeof(req));
        req.connectorId = connectorId;
        sprintf_UTCTime(req.timestamp, time(NULL));
        switch(GUN_State)
        {
            case GUN_NORMAL:
            case GUN_PLUGOUT: req.status = ChargePointStatus_Available; break;
            case GUN_PLUGIN:
            case GUN_STARTUP: 
            case GUN_ELOCKED: 
            case GUN_AUTHORIZED: 
            case GUN_PARAMETERS:
            case GUN_INSULATION:
            case GUN_PRECHARGE:   req.status = ChargePointStatus_Preparing; break;
            case GUN_CHARGING: 
            case GUN_SUSPEND:req.status = ChargePointStatus_Charging; break;
            case GUN_STOPPING: req.status = ChargePointStatus_Finishing; break;
            default: req.status = ChargePointStatus_Faulted; break;
        }

        if (connectorId == 1 && seccA->reservationId != 0) req.status = ChargePointStatus_Reserved;
        if (connectorId == 2 && seccB->reservationId != 0) req.status = ChargePointStatus_Reserved;

        if (connectorId == 1 && seccA->config->GUN_Available != true) req.status = ChargePointStatus_Unavailable;
        if (connectorId == 2 && seccB->config->GUN_Available != true) req.status = ChargePointStatus_Unavailable;

        sprintf(req.vendorErrorCode, "%08x", GUN_Error);

        if (GUN_Error == 0) 
            req.errorCode = ChargePointErrorCode_NoError;
        else
            req.errorCode = ChargePointErrorCode_OtherError;
        
        if (GUN_Error == GUN_CONTACTOR_FAULT ) { req.errorCode = ChargePointErrorCode_ConnectorLockFailure;}
        if (GUN_Error == CCSX_HLC_OFFLINE ) { req.errorCode = ChargePointErrorCode_EVCommunicationError;}
        if (GUN_Error == GBDC_CAN_OFFLINE ) { req.errorCode = ChargePointErrorCode_EVCommunicationError;}
        if (GUN_Error == GUN_INSULATION_FAULT ) { req.errorCode = ChargePointErrorCode_GroundFailure; }
        if (GUN_Error == CCU_BOX_OVERTEMP ) { req.errorCode = ChargePointErrorCode_HighTemperature;}
        if (GUN_Error == GUN_LINE_OVERTEMP ) { req.errorCode = ChargePointErrorCode_HighTemperature;}
        if (GUN_Error == BMS_CURRENT_MISTACH ) { req.errorCode = ChargePointErrorCode_OverCurrentFailure;}
        if (GUN_Error == BMS_VOLTAGE_MISTACH ) { req.errorCode = ChargePointErrorCode_OverVoltage;}
        if (GUN_Error == BMS_VOLTAGE_MISTACH ) { req.errorCode = ChargePointErrorCode_OverVoltage;}
        
        ocpp16_StatusNotification_request(dev, &req);
    }
}

int ocpp16_request_AuthorizateEMAID(void* ocpp, const char* emaid)
{
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    ocpp16_config_t *config = (ocpp16_config_t*)dev->config;

    AuthorizationDataNFC_t * itemA = copp16_AuthorizationCache_find(emaid);
    AuthorizationDataNFC_t * itemB = copp16_LocalAuthorizationList_find(emaid);

    if (config->enable == 1)
    {
        if (ocpp16_configkeys.coreProfile.LocalPreAuthorize == 1)
        {
            if (itemA) return itemA->status;
            if (itemB) return itemB->status;
        }
        
        if (dev->boot_notification_readay == 1)
        {
            AuthorizeReq_t req;
            AuthorizeConf_t conf;
            sprintf(req.idTag, "%s", emaid);
            if (ocpp16_Authorize_request(dev, &req, &conf) == 0)
            {
                if (ocpp16_configkeys.coreProfile.AuthorizationCacheEnabled == 1)
                {
                    ocpp16_AuthorizationCache_append(req.idTag, conf.idTagInfo.parentIdTag, conf.idTagInfo.expiryDate, conf.idTagInfo.status);   
                }
                return conf.idTagInfo.status;
            }
        }
    }else{
        if (ocpp16_configkeys.coreProfile.AllowOfflineTxForUnknownId == 1)
        {
            if (itemA) return itemA->status;
            if (itemB) return itemB->status;
            return AuthorizationStatus_Accepted;
        }
        if (ocpp16_configkeys.coreProfile.LocalAuthorizeOffline == 1)
        {
            if (itemB) return itemB->status;
        }
    }
    return AuthorizationStatus_Invalid;
}

int ocpp16_request_TransactionStart(void* ocpp, int connectorId)
{
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    ocpp16_config_t *config = (ocpp16_config_t*)dev->config;
    ccu_port_t *ccu = (ccu_port_t *)dev->ccu;
    secc_port_t *gun = NULL;
    if (connectorId == 1) gun = (secc_port_t*)ccu->seccA; 
    if (connectorId == 2) gun = (secc_port_t*)ccu->seccB; 
    if (gun == NULL) return -1;

    printd("step, connectorId:%d, config->enable:%d\n", connectorId, config->enable);

    if (config->enable == 1)
    {
        printd("step, boot_notification_readay:%d\n", dev->boot_notification_readay);

        if (dev->boot_notification_readay == 0)  return -1;

        printd("step, AuthorizeRemoteTxRequests:%d\n", ocpp16_configkeys.coreProfile.AuthorizeRemoteTxRequests );

        if (ocpp16_configkeys.coreProfile.AuthorizeRemoteTxRequests == 1)
        {
            ocpp16_request_AuthorizateEMAID(dev, gun->emaid);
        }
        {
            StartTransactionReq_t req;
            StartTransactionConf_t conf;
            req.connectorId = connectorId;
            sprintf(req.idTag, "%s", gun->emaid);
            req.reservationId = 0;
            req.timestamp = time(NULL);
            req.meterStart = (int)(gun->EMeters*1000);
            if (ocpp16_StartTransaction_request(dev, &req, &conf) == AuthorizationStatus_Accepted)
            {
                gun->transactionId = conf.transactionId;
                printi("connectorId:%d, transactionId:%d\n", connectorId, gun->transactionId);
                return gun->transactionId;
            }else{
                printe("connectorId:%d failed\n", connectorId);
            }
        }
        return -1;
    }else{
        if (ocpp16_configkeys.coreProfile.AllowOfflineTxForUnknownId == 1)
        {
            gun->transactionId = generate_random_i32();
            ocpp16_OfflineChargingRecords_startup(gun);
            printi("connectorId:%d, transactionId:%d\n", connectorId, gun->transactionId);
        }else{
            if (ocpp16_request_AuthorizateEMAID(dev, gun->emaid) == AuthorizationStatus_Accepted)
            {
                gun->transactionId = generate_random_i32();
                ocpp16_OfflineChargingRecords_startup(gun);
                printi("connectorId:%d, transactionId:%d\n", connectorId, gun->transactionId);
            }
        }
    }
    return -1;
}

int ocpp16_request_TransactionStop(void* ocpp, int connectorId, int transactionId)
{
    // printd("ocpp=%p\n", ocpp);
    ocpp_client_t *dev = (ocpp_client_t *)ocpp;
    ocpp16_config_t *config = (ocpp16_config_t*)dev->config;
    // printd("ocpp=%p\n", ocpp);
    ccu_port_t *ccu = (ccu_port_t *)dev->ccu;
    // printd("ocpp=%p\n", ocpp);
    secc_port_t *gun = NULL;
    if (connectorId == 1) gun = (secc_port_t*)ccu->seccA; 
    if (connectorId == 2) gun = (secc_port_t*)ccu->seccB; 
    if (gun == NULL) return -1;

    // printd("ocpp=%p\n", ocpp);
    if (config->enable == 1)
    {
        if (dev->boot_notification_readay == 1)
        {
            if (ocpp16_configkeys.coreProfile.AuthorizeRemoteTxRequests == 1)
            {
                ocpp16_request_AuthorizateEMAID(dev, gun->emaid);
            }
            {
                StopTransactionReq_t req;
                StopTransactionConf_t conf;
                req.transactionId = transactionId;
                sprintf(req.idTag, "%s", gun->emaid);
                req.meterStop  = (int)(gun->EMeters*1000);
                req.reason = 0;
                req.timestamp = time(NULL);
                req.transactiondata_used = 0;
                ocpp16_format_MeterValues(dev, &req.transactiondata, ocpp16_configkeys.coreProfile.StopTxnAlignedData);
                if (ocpp16_StopTransaction_request(dev, &req, &conf) == AuthorizationStatus_Accepted)
                {
                    printi("connectorId:%d, transactionId:%d\n", connectorId, transactionId);
                    return 0;
                }else{
                    printe("connectorId:%d failed\n", connectorId);
                }
            }
            if (ocpp16_configkeys.coreProfile.StopTransactionOnInvalidId == 1)
            {
                return 0;
            }
        }else{
            return 0;
        }
    }else{
        // if (ocpp16_configkeys.coreProfile.AllowOfflineTxForUnknownId == 1)
        {
            ocpp16_OfflineChargingRecords_shutdown(gun);
            printi("connectorId:%d, transactionId:%d\n", connectorId, transactionId);
            return 0;
        }
    }
    return -1;
}