#include "self_diagnosis.h"
#include "../common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syslog.h>
#include <time.h>
#include "define.h"
#include "deviceLayer.h"
#include "../log_module/log_module.h"
#include "jsonct.h"
#include "../multilanguage.h"

static void *self_diagnosis_task(void *arg);

static inline bool meter_is_master(char* no)
{
    return no == NULL || strlen(no) == 0 || strcmp(no, "LC") == 0;
}

static inline bool check_point_exist(char* no, const char* point)
{
    
    char nod[POINT_NAME_LEN * 2] = {0};
    snprintf(nod, sizeof(nod), "%s.%s", no, point);
    if (check_nod_exist(nod))
    {
        return true;
    }
    return false;
}

static int dev_online_diagnosis(self_diagnosis_it* this_p, const char* dev_no, const char* info)
{
    if (0 != dev_get_dev_tag_int(dev_no, "Online"))
    {
        this_p->status = self_diagnosis_it_pass;
    }
    else
    {
        this_p->status  = self_diagnosis_it_no_pass;
        this_p->message = strdup(info);
    }
    return 0;
}



static int dev_alarm_diagnosis(self_diagnosis_it *this_p, const char *dev_no, const char *info)
{
    if (0 == dev_get_dev_tag_int(dev_no, "alarmCnt"))
    {
        this_p->status = self_diagnosis_it_pass;
    }
    else
    {
        this_p->status  = self_diagnosis_it_no_pass;
        this_p->message = strdup(info);
    }
    return 0;
}

static inline void merge_point_name(char *dst, const char *no, const char *point)
{
    if (no == NULL || strlen(no) == 0)
    {
        sprintf(dst, "%s", point);
    }
    else
    {
        sprintf(dst, "%s.%s", no, point);
    }
}

static int bms_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_BMS);
    return dev_online_diagnosis(this_p, tmp, _("Please check the BMS connection."));
}

static int pcs_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_PCS);
    return dev_online_diagnosis(this_p, tmp, _("Please check the PCS connection."));
}

static int pcs_meter_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    if (meter_is_master(no))
    {
        return dev_online_diagnosis(this_p, DEV_NO_PCS_MT, _("Please check the connection of the PCS-meter."));
    }

    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_PCS_MT);
    if (check_point_exist(no, "Online"))
    {
        this_p->status = self_diagnosis_it_pass;
        this_p->message = strdup(_("PCS-meter is not configured"));
        return 0;
    }
    return dev_online_diagnosis(this_p, tmp, _("Please check the connection of the PCS-meter."));
}

static int grid_meter_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    if (g_usercfg_variant.EnProtectReverse || (g_usercfg_variant.EnProtectTransf > 0))
        return dev_online_diagnosis(this_p, DEV_NO_GRID_MT, _("Please check the connection of the grid-meter."));
    else
    {
        this_p->status = self_diagnosis_it_pass;
        this_p->message = strdup(_("Grid-meter is not configured"));
    }
    return 0;
}

static int dio_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_DIDO);
    return dev_online_diagnosis(this_p, tmp, _("Please check if the DIO software module is installed."));
}

static int airc_status_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_AC);
    return dev_online_diagnosis(this_p, tmp, _("Please check if the air conditioner is connected."));
}

static int bms_alarm_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_BMS);
    return dev_alarm_diagnosis(this_p, tmp, _("BMS has alarms, please check."));
}

static int pcs_alarm_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_PCS);
    return dev_alarm_diagnosis(this_p, tmp, _("PCS has alarms, please check."));
}

static int ems_net_diagnosis(char* no, self_diagnosis_it* this_p)
{
    char *ping_ip[] = {"frps.lnxall.com", "mqtt.lnxall.com", "223.5.5.5", "114.114.114.114", "8.8.8.8"};
    if (meter_is_master(no))
    {
        this_p->status  = self_diagnosis_it_processing;
        for (int i = 0; i < sizeof(ping_ip) / sizeof(ping_ip[0]); i++)
        {
            if (0 == Ping_Test_IP(ping_ip[i], 5))
            {
                this_p->status = self_diagnosis_it_pass;
                break;
            }
        }
        if (this_p->status  != self_diagnosis_it_pass)
        {
            this_p->status  = self_diagnosis_it_no_pass;
            this_p->message = strdup(_("EMS connection to server failed, please check if SIM card or Ethernet cable is inserted."));
        }
    }
    else
    {
        this_p->status = self_diagnosis_it_pass;
        this_p->message = strdup(_("LC devices do not require network configuration."));
    }


    return 0;
}

static int di_input_diagnosis(char* no, self_diagnosis_it* this_p)
{
    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status = self_diagnosis_it_processing;
    //
    char buff[16] = {0};
    for (int i = 0; i < 8; i++)
    {
        snprintf(buff, sizeof(buff), "DI%d", i);

        char tmp[POINT_NAME_LEN] = {0};
        merge_point_name(tmp, no, "DIO");
        if (0 == dev_get_dev_tag_int(tmp, buff))
        {
            res = self_diagnosis_it_pass;
        }
        else
        {
            res = self_diagnosis_it_no_pass;
            this_p->message = strdup(_("DI has a point with input 1."));
            break;
        }
    }
    //
    this_p->status = res;

    return 0;
}

static int bms_soc_diagnosis(char* no, self_diagnosis_it* this_p)
{
    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status               = self_diagnosis_it_processing;
    
    char tmp[POINT_NAME_LEN]     = {0};
    merge_point_name(tmp, no, DEV_NO_BMS);
    double soc = dev_get_dev_tag_float(tmp, CURRENT_SOC);
    if ((soc > 5) && (soc < 100))
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Abnormal SOC value"));
    }
    this_p->status = res;

    return 0;
}

static int pcs_dc_diagnosis(char* no, self_diagnosis_it* this_p)
{

    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status               = self_diagnosis_it_processing;
    
    char tmp[POINT_NAME_LEN]     = {0};
    merge_point_name(tmp, no, "PCS");

    double dc = dev_get_dev_tag_float(tmp, "DcVoltage");
    double dc2 = dev_get_dev_tag_float(tmp, "BatteryVol");
    if (((dc > 500) && (dc < 1200))||((dc2 > 500) && (dc2 < 1200)))
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Abnormal total DC voltage"));
    }

    //
    this_p->status = res;

    return 0;
}
#if 0
static int pcs_ac_diagnosis(self_diagnosis_it *this_p)
{
    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status = self_diagnosis_it_processing;
    //
    const char *tag_names[] = {"AphaseVoltage", "BphaseVoltage", "CphaseVoltage"};
    for (int i = 0; i < sizeof(tag_names) / sizeof(tag_names[0]); i++)
    {
        double ac = dev_get_dev_tag_float("PCS", tag_names[i]);
        if ((ac > 200) && (ac < 410))
        {
            res = self_diagnosis_it_pass;
        }
        else
        {
            res = self_diagnosis_it_no_pass;
            this_p->message = strdup("PCS交流电压数值异常");
            break;
        }
    }
    //
    this_p->status = res;

    return 0;
}
#endif
static int grid_meter_power_dir_diagnosis(char* no, self_diagnosis_it* this_p)
{

    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status = self_diagnosis_it_processing;
    double power = dev_get_dev_tag_float(DEV_NO_GRID_MT, METER_POWER);
    if (power > 0)
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Power direction error"));
    }
    //
    this_p->status = res;

    return 0;
}

static int pcs_meter_power_dir_diagnosis(char* no, self_diagnosis_it* this_p)
{

    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status               = self_diagnosis_it_processing;

    double power = 0.0;
    if (meter_is_master(no))
    {
        power = dev_get_dev_tag_float(DEV_NO_PCS_MT, METER_POWER);
    }
    else
    {
        char tmp[POINT_NAME_LEN] = {0};
        merge_point_name(tmp, no, DEV_NO_PCS_MT);
        // 如果从机配置了PCS_METER,则使用从机配置的PCS_METER,否则使用主机的PCS_METER
        if (check_point_exist(tmp, METER_POWER))
        {
            power = dev_get_dev_tag_float(tmp, METER_POWER);
        }
        else
        {
            power = dev_get_dev_tag_float(DEV_NO_PCS_MT, METER_POWER);
        }
    }
    
    if (power > 0)
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Power direction error"));
    }
    //
    this_p->status = res;

    return 0;
}

static int pcs_meter_energy_dir_diagnosis(char* no, self_diagnosis_it* this_p)
{

    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status               = self_diagnosis_it_processing;
    //
    double power1 = dev_get_dev_tag_float(DEV_NO_PCS_MT, MT_FORWARD_ENERGY);
    sleep(60);
    double power2 = dev_get_dev_tag_float(DEV_NO_PCS_MT, MT_FORWARD_ENERGY);
    sleep(60);
    double power3 = dev_get_dev_tag_float(DEV_NO_PCS_MT, MT_FORWARD_ENERGY);

    if ((power1 < power2) && (power2 < power3))
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Electrical energy direction error"));
    }
    //
    this_p->status = res;

    return 0;
}

static int bms_soc_dir_diagnosis(char* no, self_diagnosis_it* this_p)
{

    self_diagnosis_it_status res = self_diagnosis_it_no_pass;
    this_p->status               = self_diagnosis_it_processing;

    char tmp[POINT_NAME_LEN] = {0};
    merge_point_name(tmp, no, DEV_NO_BMS);
    //
    double soc1 = dev_get_dev_tag_float(tmp, CURRENT_SOC);
    sleep(3*60);
    double soc2 = dev_get_dev_tag_float(tmp, CURRENT_SOC);
    sleep(3*60);
    double soc3 = dev_get_dev_tag_float(tmp, CURRENT_SOC);
    if ((soc1 < soc2) && (soc2 < soc3))
    {
        res = self_diagnosis_it_pass;
    }
    else
    {
        res = self_diagnosis_it_no_pass;
        this_p->message = strdup(_("Charging SOC direction error"));
    }

    //
    this_p->status = res;

    return 0;
}

self_diagnosis_it factory_it[] =
    {
        {.name = "BMS Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = bms_status_diagnosis},
        {.name = "PCS Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_status_diagnosis},
        {.name = "PCS-meter Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_meter_status_diagnosis},
        {.name = "DIO Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = dio_status_diagnosis},
        {.name = "AC Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = airc_status_diagnosis},
        {.name = "BMS Alarm", .message = NULL, .status = self_diagnosis_it_no_start, .fun = bms_alarm_diagnosis},
        {.name = "PCS Alarm", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_alarm_diagnosis},
        {.name = "Networking", .message = NULL, .status = self_diagnosis_it_no_start, .fun = ems_net_diagnosis},
        {.name = "DI Status", .message = NULL, .status = self_diagnosis_it_no_start, .fun = di_input_diagnosis},
        {.name = "SOC", .message = NULL, .status = self_diagnosis_it_no_start, .fun = bms_soc_diagnosis},
        {.name = "PCS DC voltage", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_dc_diagnosis},
        // {.name = "PCS交流电压", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_ac_diagnosis},
};
int factory_it_count = sizeof(factory_it) / sizeof(factory_it[0]);

self_diagnosis_it charge_it[] =
    {
        {.name = "BMS Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = bms_status_diagnosis},
        {.name = "PCS Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_status_diagnosis},
        {.name = "PCS-meter Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_meter_status_diagnosis},
        {.name = "Grid-meter Online", .message = NULL, .status = self_diagnosis_it_no_start, .fun = grid_meter_status_diagnosis},
        {.name = "Power direction of Grid-meter", .message = NULL, .status = self_diagnosis_it_no_start, .fun = grid_meter_power_dir_diagnosis},
        {.name = "Power direction of PCS-meter", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_meter_power_dir_diagnosis},
        {.name = "Energy direction of PCS-meter", .message = NULL, .status = self_diagnosis_it_no_start, .fun = pcs_meter_energy_dir_diagnosis},
        {.name = "Charging direction of SOC", .message = NULL, .status = self_diagnosis_it_no_start, .fun = bms_soc_dir_diagnosis},
};
int charge_it_count = sizeof(charge_it) / sizeof(charge_it[0]);

self_diagnosis *factory_diagnosis_new(char* no)
{
    return self_diagnosis_new(no, factory_it, sizeof(factory_it) / sizeof(factory_it[0]));
}

self_diagnosis *charge_diagnosis_new(char* no)
{
    return self_diagnosis_new(no, charge_it, sizeof(charge_it) / sizeof(charge_it[0]));
}



static int self_diagnosis_it_init(self_diagnosis_it *it)
{
    it->status = self_diagnosis_it_no_start;
    if (it->message != NULL)
    {
        free(it->message);
        it->message = NULL;
    }
    return 0;
}

static int self_diagnosis_init(char* no, self_diagnosis *this_p, self_diagnosis_it *it, int it_size)
{
    for (int i = 0; i < it_size; i++)
    {
        self_diagnosis_it_init(&it[i]);
    }
    if(no)
        local_strlcpy(this_p->no, no, sizeof(this_p->no));
    else
        this_p->no[0] = '\0';

    this_p->cur_it = it;
    this_p->it = it;
    this_p->it_size = it_size;
    this_p->status = self_diagnosis_no_start;
    this_p->thread = PTHREAD_NULL;
    return 0;
}

static int self_diagnosis_reinit(self_diagnosis *this_p)
{
    self_diagnosis_it *it = this_p->it;
    int it_size = this_p->it_size;
    for (int i = 0; i < it_size; i++)
    {
        self_diagnosis_it_init(&it[i]);
    }

    this_p->status = self_diagnosis_no_start;
    this_p->thread = PTHREAD_NULL;
    return 0;
}

static cJSON *self_diagnosis_it_to_json(self_diagnosis_it *it)
{
    cJSON *json = cJSON_CreateObject();
    if (json != NULL)
    {
        cJSON_AddItemToObject(json, "name", cJSON_CreateString(it->name));
        cJSON_AddItemToObject(json, "status", cJSON_CreateNumber(it->status));
        if (it->message != NULL)
        {
            cJSON_AddItemToObject(json, "message", cJSON_CreateString(it->message));
        }
        else
        {
            cJSON_AddItemToObject(json, "message", cJSON_CreateString(""));
        }
    }

    return json;
}

self_diagnosis *self_diagnosis_new(char* no,self_diagnosis_it *it, int it_size)
{
    self_diagnosis *dia = calloc(1, sizeof(self_diagnosis));
    if (dia != NULL)
    {
        self_diagnosis_init(no ,dia, it, it_size);
    }
    return dia;
}

int self_diagnosis_deinit(self_diagnosis *this_p)
{
    self_diagnosis_it *it = this_p->it;
    int it_size = this_p->it_size;
    for (int i = 0; i < it_size; i++)
    {
        self_diagnosis_it_init(&it[i]);
    }
    return 0;
}

int self_diagnosis_stop_handle(self_diagnosis *this_p)
{
    self_diagnosis_it *it = this_p->it;
    int it_size = this_p->it_size;
    for(int i = 0; i < it_size; i++)
    {
        if(it[i].status == self_diagnosis_it_processing)
        {
            it[i].status = self_diagnosis_it_no_start;
        }
    }
    return 0;
}

int self_diagnosis_start(self_diagnosis *this_p)
{
    if (this_p->thread != PTHREAD_NULL)
    {
        return -1;
    }
    //
    self_diagnosis_reinit(this_p);
    //
    pthread_attr_t attr;
    if (0 != pthread_attr_init(&attr))
    {
        return -3;
    }

    if (0 != pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
    {
        pthread_attr_destroy(&attr);
        return -4;
    }
    int ret = pthread_create(&this_p->thread, &attr, self_diagnosis_task, this_p);
    pthread_attr_destroy(&attr);
    if (ret != 0)
    {
        ems_syslog(LOG_ERR, "failed to created pthread for self diagnosis %s", strerror(errno));
        return -2;
    }
    else
    {
        pthread_setname_np(this_p->thread, "self_diagnosis");
    }
    return 0;
}
int self_diagnosis_stop(self_diagnosis *this_p)
{
    int ret = 0;
    if (this_p == NULL)
    {
        return -2;
    }
    if ((this_p->status == self_diagnosis_processing) && (this_p->thread != PTHREAD_NULL))
    {
        ret = pthread_cancel(this_p->thread);
        if (ret == 0)
        {
            this_p->status = self_diagnosis_stoped;
            this_p->thread = PTHREAD_NULL;
        }
    }
    return ret;
}

cJSON *self_diagnosis_get_status(self_diagnosis *this_p)
{
    self_diagnosis_it *it = this_p->it;
    int it_size = this_p->it_size;
    cJSON *main_json = cJSON_CreateObject();
    cJSON *it_arr = cJSON_CreateArray();
    cJSON *tmp_json = NULL;
    cJSON_AddItemToObject(main_json, "status", cJSON_CreateNumber(this_p->status));
    for (int i = 0; i < it_size; i++)
    {
        tmp_json = self_diagnosis_it_to_json(it + i);
        if (tmp_json != NULL)
        {
            cJSON_AddItemToArray(it_arr, tmp_json);
        }
        else
        {
            ems_syslog(LOG_ERR, "%s diagnosis to json fail", it->name);
        }
    }
    cJSON_AddItemToObject(main_json, "it", it_arr);
    return main_json;
}

//

//
static void *self_diagnosis_task(void *arg)
{
    self_diagnosis *diag = (self_diagnosis *)arg;
    self_diagnosis_it *it = diag->it;
    int it_size = diag->it_size;
    int pass_num = 0,nopass_num=0;
    diag->status = self_diagnosis_processing;
    char log_add_tmp[512] = {0};
    snprintf(log_add_tmp,sizeof(log_add_tmp),"%d",it_size);
    server_add_new_log(LOG_CODE_DIAGNOSIS,"EMS",LOG_MODE_TRIG,1,log_add_tmp);
    for (int i = 0; i < it_size; i++)
    {
        sleep(1);
        ems_syslog(LOG_INFO, "diagnosis %s", it[i].name);
        diag->cur_it = &it[i];
        it[i].fun(diag->no, &it[i]);
        snprintf(log_add_tmp,sizeof(log_add_tmp),"%d,%d,%s",i+1,it_size,it[i].name);
        if(it[i].status==self_diagnosis_it_pass)
        {
            server_add_new_log(LOG_CODE_DIAGNOSIS,"EMS",LOG_MODE_TRIG,2,log_add_tmp);
            pass_num++;
        }
        else
        {
            server_add_new_log(LOG_CODE_DIAGNOSIS,"EMS",LOG_MODE_TRIG,3,log_add_tmp);
            nopass_num++;
        }
    }
    sleep(1);
    diag->thread = PTHREAD_NULL;
    diag->status = self_diagnosis_completed;
    sleep(1);
    snprintf(log_add_tmp,sizeof(log_add_tmp),"%d,%d",pass_num,nopass_num);
    server_add_new_log(LOG_CODE_DIAGNOSIS,"EMS",LOG_MODE_TRIG,4,log_add_tmp);
    return NULL;
}
