#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "cloud_conn_common.h"
#include "dy_utils/cJSON.h"
#include "dy_utils/dy_common.h"
#include "mqtt_session.h"
#include "dy_utils/protocol.h"

extern host_cmd_t g_host_cmd[];

static int cloud_conn_subscribe_all(cloud_conn_var_t *var)
{
    mqtt_session_t *mqtt_session = var->session_cloud;
    char topic[128];

    snprintf(topic, sizeof(topic), "M/%s/#", var->sn_str);
    mqtt_session_subscribe(mqtt_session, topic);

    return 0;
}

static int load_server_default(cloud_conn_var_t *var)
{
#if defined(DEF_MQTT_SERVER_IP) && defined(DEF_MQTT_SERVER_PORT) && defined(DEF_MQTT_SERVER_USER) && defined(DEF_MQTT_SERVER_PASS)
    mqtt_session_set_address(var->session_cloud, DEF_MQTT_SERVER_IP, DEF_MQTT_SERVER_PORT, DEF_MQTT_SERVER_USER, DEF_MQTT_SERVER_PASS);
#endif
    return 0;
}

static int cloud_conn_state_change(void *obj, int state)
{
    cloud_conn_var_t *var = (cloud_conn_var_t *)obj;

    switch (state)
    {
    case MQTT_CONNECTING:
        post_net_led(LED_TWINKLE);
        var->comm_status = COMM_STATUS_CONNECTING;
        break;
    case MQTT_CONNECTED:
        post_net_led(LED_LIGHT_ON);
        var->comm_status = COMM_STATUS_CONNECTED;
        if (!var->status_timer)
        {
            //开启心跳超时检查定时器
            var->status_timer = my_timer_create();
            if (var->status_timer <= 0)
            {
                dy_syslog(LOG_INFO, "[ERR] create status check timer fail, %d", var->status_timer);
            }

            if (var->status_timer > 0)
            {
                my_timer_set(var->status_timer, 2, 2000);
            }
        }
        break;
    case MQTT_DISCONNECTED:
        post_net_led(LED_LIGHT_OFF);
        var->comm_status = COMM_STATUS_IDLE;
        break;
    default:
        dy_syslog(LOG_ERR, "wrong mqtt state:%d", state);
    }
    return 0;
}

static char *login_print(login_info_t *info)
{
    char version[32] = {0};
    char exec_cmd[256] = {0};
    char *buff = NULL;
    cJSON *root = NULL;
    long uptime = 0;
    struct sysinfo s_info;

    if (0 == sysinfo(&s_info))
    {
        uptime = s_info.uptime;
    }

    sprintf(exec_cmd, "%s/%s status", NET_BASH_PATH, "fetch_arch_info.sh");
    root = cJSON_CreateObject();
    if (root == NULL)
    {
        return NULL;
    }

    system_reply(exec_cmd, &buff);
    get_hardware_ver(version);
    cJSON_AddStringToObject(root, "hard_ver", version);
    get_software_ver(version);
    cJSON_AddStringToObject(root, "soft_ver", version);
    cJSON_AddNumberToObject(root, "mi", info->serial_num);
    cJSON_AddNumberToObject(root, "timestamp", info->timestamp);
    cJSON_AddNumberToObject(root, "run_mode", info->run_mode);
    cJSON_AddStringToObject(root, "product", info->product_name);
    cJSON_AddNumberToObject(root, "uptime", uptime);
    if (buff)
    {
        cJSON_AddStringToObject(root, "sys_arch", buff);
        free(buff);
    }
    char *str = (cJSON_Print(root));
    cJSON_Delete(root);
    return str;
}

static char *heartbeat_print(int mi)
{
    cJSON *root = cJSON_CreateObject();
    if (root == NULL)
    {
        return NULL;
    }
    cJSON_AddNumberToObject(root, "mi", mi);
    char *str = (cJSON_Print(root));
    cJSON_Delete(root);
    return str;
}

void heart_beat_msg(cloud_conn_var_t *var)
{
    char *str = NULL;
    int mi = var->serial_number++;
    char topic[TOPIC_MAX_LEN];

    str = heartbeat_print(mi);

    snprintf(topic, TOPIC_MAX_LEN, "G/%s/%s", var->sn_str, TOPIC_EVT_HEARTBEAT);
    mqtt_session_publish(var->session_cloud, topic, str, strlen(str));
    free(str);
}

void refuse_master_server(cloud_conn_var_t *var)
{
    mqtt_session_disconn(var->session_cloud);
    load_server_default(var);
    mqtt_session_conn(var->session_cloud);
    cloud_conn_subscribe_all(var);
}

void timer_heart_beat(cloud_conn_var_t *var)
{
    heart_beat_msg(var);
    if (var->heart_beat_lost_cnt >= 5)
    {
        var->heart_beat_lost_cnt = 0;
        refuse_master_server(var);
    }
    else
    {
        var->heart_beat_lost_cnt++;
    }
}

void gateway_login_msg(cloud_conn_var_t *var)
{
    login_info_t info;
    char topic[TOPIC_MAX_LEN];
    char *str = NULL;

    var->serial_number++;
    info.timestamp = time(NULL);
    info.serial_num = var->serial_number;
    info.run_mode = get_gateway_run_mode();
    strcpy(info.product_name, var->product_name);
    str = login_print(&info);

    sleep(10);  // Delay login by 10 seconds. Waiting for other services startup.
    snprintf(topic, TOPIC_MAX_LEN, "G/%s/%s", var->sn_str, TOPIC_LOGIN);
    mqtt_session_publish(var->session_cloud, topic, str, strlen(str));
    free(str);
}

void request_timestamp_msg(cloud_conn_var_t *var)
{
    p_general_cmd_info_t reply;
    char topic[TOPIC_MAX_LEN];

    reply.mi = 0;
    reply.utc = (unsigned int)time(0);
    reply.data = NULL;
    reply.dataLen = 0;

    snprintf(topic, TOPIC_MAX_LEN, "G/%s/%s", var->sn_str, TOPIC_GET_RTC);
    mqtt_session_publish(var->session_cloud, topic, (char *)&reply, sizeof(reply));
}

int comm_status_poll(cloud_conn_var_t *var)
{
    TIMER_CONFIRM(var->status_timer);
    time_t now = time(NULL);
    static time_t last_check = 0;
    static time_t request_time = 0;
    static time_t heartbeat_time = 0;
    static time_t login_time = 0;

    if (now > last_check + 1)
    {
        last_check = now;
        if (var->comm_status == COMM_STATUS_CONNECTED)
        {
            if (now >= login_time + var->login_interval)
            {
                login_time = now;
                gateway_login_msg(var);
            }
        }
        else if (var->comm_status == COMM_STATUS_LOGIN)
        {
            if (now >= heartbeat_time + var->heartbeat_interval)
            {
                heartbeat_time = now;
                timer_heart_beat(var);
            }
        }
    }

    if (now >= request_time + 600)
    {
        request_time = now;
        request_timestamp_msg(var);
    }

    return 0;
}

static int load_interval_cfg(cloud_conn_var_t *var, char *cfg_file)
{
    char *data = NULL;
    cJSON *root = NULL;
    int ret = -1;
    int lens;

    if (cfg_file == NULL)
    {
        dy_syslog(LOG_ERR, "cfg_file:%s not exist, use defaut MQTT server", cfg_file);
        goto out;
    }

    data = read_file_data_and_lens(cfg_file, &lens);
    if (data == NULL)
    {
        dy_syslog(LOG_ERR, "read cfg file %s error, load default", cfg_file);
        goto out;
    }

    root = cJSON_Parse(data);
    if (!root)
    {

        char *new = malloc(lens);
        if (new != NULL)
        {
            lnxall_decrypt_string((unsigned char *) data, lens, (unsigned char *) new);
            free(data);
            data = new;
            root = cJSON_Parse(data);
        }
        if (!root)
        {

            dy_syslog(LOG_ERR, "parse cfg file %s error, load default", cfg_file);
            goto out;
        }
    }

    GET_JSON_VALUE_INT(root, "heartbeat_interval", var->heartbeat_interval);
    GET_JSON_VALUE_INT(root, "login_interval", var->login_interval);

    cJSON_Delete(root);
    ret = 0;

out:
    if (data)
    {
        free(data);
    }
    if (var->heartbeat_interval == 0)
    {
        var->heartbeat_interval = 10 * 60; // 10 min
    }
    if (var->login_interval == 0)
    {
        var->login_interval = 5 * 60;
    }
    return ret;
}

// 建立与broker之间的MQTT连接
int cloud_conn_mqtt_init(cloud_conn_var_t *var)
{
    char clientId[MAX_CLIENT_ID_LEN] = {0};

    snprintf(clientId, MAX_CLIENT_ID_LEN, "%s", var->sn_str);
    var->session_cloud = mqtt_session_new(clientId,  (void *)var);
    if (var->session_cloud == NULL)
        return -1;

    if (load_server_config(var->session_cloud, MQTT_SERVER_FILE) != 0)
    {
        load_server_default(var);
    }
    load_interval_cfg(var, MQTT_SERVER_FILE);

    mqtt_session_set_opts(var->session_cloud, DEFAULT_QOS, KEEPALIVEINTERVAL);
    mqtt_session_set_callbacks(var->session_cloud, recv_host_msg, cloud_conn_state_change);

    var->comm_status = COMM_STATUS_CONNECTING;
    cloud_conn_subscribe_all(var);
    mqtt_session_start(var->session_cloud);
    post_net_led(LED_TWINKLE);
    return 0;
}
