/*
 * Copyright (C) 2022 EXOLIGENT (www.exoligent.com)
 * Author: Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include <json-c/json.h>
#include <stdlib.h>     // strtol()
#include <string.h>     // strcpy(), strcmp()

#include "gw.h"

#define READ_BUF_SZ (1024*1024)

static void gw_fip_evt_registration(struct gateway *gw,
                struct pwrfip_var_cfg *cfg, struct json_object *evt)
{
    bool r_op = (!strcmp("r_op", json_object_get_string(evt))) ? true : false;
    bool w_op = (!strcmp("w_op", json_object_get_string(evt))) ? true : false;
    bool rw_op = (!strcmp("rw_op", json_object_get_string(evt))) ? true : false;
    bool wr_op = (!strcmp("wr_op", json_object_get_string(evt))) ? true : false;
    if (r_op || w_op || rw_op || wr_op) {
        if (rw_op || wr_op) {
            cfg->pwrfip_var_handler = (rw_op) ? \
                fip_id_sync_rw_op_handler : fip_id_sync_wr_op_handler;
            gw->fip.has_r_op_evt = 1;
            gw->fip.has_w_op_evt = 1;
        }
        else if (r_op) {
            cfg->pwrfip_var_handler = fip_id_sync_r_op_handler;
            gw->fip.has_r_op_evt = 1;
        }
        else {
            cfg->pwrfip_var_handler = fip_id_sync_w_op_handler;
            gw->fip.has_w_op_evt = 1;
        }
        if (cfg->type == PWRFIP_VAR_TYPE_CONS)
            cfg->cons.evt_type = PWRFIP_EVT_TYPE_PERMANENT_ID;
        else if (cfg->type == PWRFIP_VAR_TYPE_PROD)
            cfg->prod.evt_type = PWRFIP_EVT_TYPE_PERMANENT_ID;
    }
}

int gw_cfg_parse(const char *json_path, struct gateway *gw)
{
    struct json_object *root, *opc, *fip;
    FILE *fp = fopen(json_path, "r");
    char buffer[READ_BUF_SZ];

    if (!fp)
        return -1;

    memset(buffer, 0, sizeof(buffer));
    if (fread(buffer, READ_BUF_SZ, 1, fp)) {}
    fclose(fp);

    root = opc = fip = NULL;
    root = json_tokener_parse(buffer);
    json_object_object_get_ex(root, "opc", &opc);
    json_object_object_get_ex(root, "fip", &fip);

    /* opcua settings */
    if (opc) {
        struct json_object *pub, *sub;

        pub = sub = NULL;
        json_object_object_get_ex(opc, "publisher", &pub);
        json_object_object_get_ex(opc, "subscriber", &sub);

        /* => dataset_writer */
        if (pub) {
            struct json_object *pub_url, *pub_net;
            struct json_object *pub_id, *w_ds_id, *w_group_id, *pub_usper;

            pub_url = pub_net = pub_id = w_ds_id = w_group_id = pub_usper = NULL;
            json_object_object_get_ex(pub, "network_interface", &pub_net);
            json_object_object_get_ex(pub, "url", &pub_url);
            json_object_object_get_ex(pub, "publisher_id", &pub_id);
            json_object_object_get_ex(pub, "dataset_writer_id", &w_ds_id);
            json_object_object_get_ex(pub, "writer_group_id", &w_group_id);
            json_object_object_get_ex(pub, "publish_period_us", &pub_usper);

            //printf("network_interface : %s\n", json_object_get_string(pub_net));
            //printf("url               : %s\n", json_object_get_string(pub_url));
            //printf("publisher_id      : %d\n", json_object_get_int(pub_id));
            //printf("dataset_writer_id : %d\n", json_object_get_int(w_ds_id));
            //printf("writer_group_id   : %d\n", json_object_get_int(w_group_id));
            //printf("publish_period_us  : %d\n", json_object_get_int(pub_usper));

            if (pub_net) {
                if (sizeof(gw->opc.pub.net_str) > strlen(json_object_get_string(pub_net)))
                    strcpy(gw->opc.pub.net_str, json_object_get_string(pub_net));
            }

            if (pub_url) {
                if (sizeof(gw->opc.pub.url_str) > strlen(json_object_get_string(pub_url)))
                    strcpy(gw->opc.pub.url_str, json_object_get_string(pub_url));
            }

            gw->opc.pub.publisher_id = json_object_get_int(pub_id);
            gw->opc.pub.dataset_writer_id = json_object_get_int(w_ds_id);
            gw->opc.pub.writer_group_id = json_object_get_int(w_group_id);
            gw->opc.pub.publish_period_us = json_object_get_int(pub_usper);
        }

        /* => dataset_reader */
        if (sub) {
            struct json_object *sub_url, *sub_net;
            struct json_object *pub_id, *w_ds_id, *w_group_id;

            sub_url = sub_net = pub_id = w_ds_id = w_group_id = NULL;
            json_object_object_get_ex(sub, "network_interface", &sub_net);
            json_object_object_get_ex(sub, "url", &sub_url);
            json_object_object_get_ex(sub, "publisher_id", &pub_id);
            json_object_object_get_ex(sub, "dataset_writer_id", &w_ds_id);
            json_object_object_get_ex(sub, "writer_group_id", &w_group_id);

            //printf("network_interface : %s\n", json_object_get_string(sub_net));
            //printf("url               : %s\n", json_object_get_string(sub_url));
            //printf("publisher_id      : %d\n", json_object_get_int(pub_id));
            //printf("dataset_writer_id : %d\n", json_object_get_int(w_ds_id));
            //printf("writer_group_id   : %d\n", json_object_get_int(w_group_id));

            if (sub_net) {
                if (sizeof(gw->opc.sub.net_str) > strlen(json_object_get_string(sub_net)))
                    strcpy(gw->opc.sub.net_str, json_object_get_string(sub_net));
            }

            if (sub_url) {
                if (sizeof(gw->opc.sub.url_str) > strlen(json_object_get_string(sub_url)))
                    strcpy(gw->opc.sub.url_str, json_object_get_string(sub_url));
            }

            gw->opc.sub.publisher_id = json_object_get_int(pub_id);
            gw->opc.sub.dataset_writer_id = json_object_get_int(w_ds_id);
            gw->opc.sub.writer_group_id = json_object_get_int(w_group_id);
        }
    }

    /* fip settings */
    if (fip) {
        struct pwrfip_node_cfg *n_cfg = &gw->fip.n_cfg;
        struct json_object *name, *addr, *seg, *frm_type, *bitrate;
        struct json_object *tr_us, *ts_us, *vars, *var, *ba;
        int i, vars_cnt = 0;

        name = addr = seg = frm_type = bitrate = NULL;
        tr_us = ts_us = vars = var = ba = NULL;
        json_object_object_get_ex(fip, "name", &name);
        json_object_object_get_ex(fip, "address", &addr);
        json_object_object_get_ex(fip, "segment", &seg);
        json_object_object_get_ex(fip, "frame_type", &frm_type);
        json_object_object_get_ex(fip, "bitrate", &bitrate);
        json_object_object_get_ex(fip, "turn_around_us", &tr_us);
        json_object_object_get_ex(fip, "silence_us", &ts_us);
        json_object_object_get_ex(fip, "vars", &vars);
        json_object_object_get_ex(fip, "bus_arbiter", &ba);
        if (vars)
            vars_cnt = json_object_array_length(vars);

        //printf("name          : %s\n", json_object_get_string(name));
        //printf("address       : %d\n", json_object_get_int(addr));
        //printf("segment       : %d\n", json_object_get_int(seg));
        //printf("frame_type    : %s\n", json_object_get_string(frm_type));
        //printf("bitrate       : %s\n", json_object_get_string(bitrate));
        //printf("turn_around_us: %d\n", json_object_get_int(tr_us));
        //printf("silence_us    : %d\n", json_object_get_int(ts_us));
        //printf("vars_cnt      : %d\n", vars_cnt);

        if (name) {
            if (sizeof(gw->fip.name_str) > strlen(json_object_get_string(name)))
                strcpy(gw->fip.name_str, json_object_get_string(name));
        }

        n_cfg->fip_phy_addr = (uint8_t) json_object_get_int(addr);
        n_cfg->fip_seg_num = (uint8_t) json_object_get_int(seg);

        if (frm_type) {
            if (!strcmp("worldfip", json_object_get_string(frm_type)))
                n_cfg->fip_frm_type = PWRFIP_FRM_WORLDFIP;
            else if (!strcmp("fip", json_object_get_string(frm_type)))
                n_cfg->fip_frm_type = PWRFIP_FRM_FIP;
        }

        if (bitrate) {
            if (!strcmp("31.25kbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_31K25;
            else if (!strcmp("1mbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_1M;
            else if (!strcmp("2.5mbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_2M5;
            else if (!strcmp("5mbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_5M;
            else if (!strcmp("12.5mbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_12M5;
            else if (!strcmp("25mbps", json_object_get_string(bitrate)))
                n_cfg->fip_bitrate = PWRFIP_BITRATE_25M;
        }

        n_cfg->turn_around_ustime = (uint32_t) json_object_get_int(tr_us);
        if (!n_cfg->turn_around_ustime && n_cfg->fip_bitrate) {
            int idx = n_cfg->fip_bitrate - 1;
            n_cfg->turn_around_ustime = tr_ustime[idx];

        }
        n_cfg->silence_ustime = (uint32_t) json_object_get_int(ts_us);
        if (!n_cfg->silence_ustime && n_cfg->fip_bitrate) {
            int idx = n_cfg->fip_bitrate - 1;
            n_cfg->silence_ustime = ts_ustime[idx];
        }

        for (i = 0; i < vars_cnt; i++) {
            struct json_object *name, *type, *id, *bsz, *evt;
            struct json_object *refresh, *refresh_us, *prompt, *prompt_us;
            struct pwrfip_var_cfg *cfg = &gw->cfg_varlist[i].pwrfip_cfg;

            name = type = id = bsz = evt = NULL;
            refresh = refresh_us = prompt = prompt_us = NULL;
            var = json_object_array_get_idx(vars, i);
            json_object_object_get_ex(var, "name", &name);
            json_object_object_get_ex(var, "type", &type);
            json_object_object_get_ex(var, "id", &id);
            json_object_object_get_ex(var, "payload_bsz", &bsz);
            json_object_object_get_ex(var, "refreshment", &refresh);
            json_object_object_get_ex(var, "refresh_us", &refresh_us);
            json_object_object_get_ex(var, "promptness", &prompt);
            json_object_object_get_ex(var, "prompt_us", &prompt_us);
            json_object_object_get_ex(var, "event", &evt);

            //printf("[%03d]\n", i + 1);
            //printf("  name        : %s\n", json_object_get_string(name));
            //printf("  type        : %s\n", json_object_get_string(type));
            //printf("  id          : 0x%04x\n",
            //    (uint16_t) strtol(json_object_get_string(id), NULL, 0));
            //printf("  payload_bsz : %d\n", json_object_get_int(bsz));
            //printf("  refreshment : %s\n", json_object_get_string(refresh));
            //printf("  refresh_us  : %d\n", json_object_get_int(refresh_us));
            //printf("  promptness  : %s\n", json_object_get_string(prompt));
            //printf("  prompt_us   : %d\n", json_object_get_int(prompt_us));
            //printf("  event       : %s\n", json_object_get_string(evt));

            if (i >= FIP_VAR_COUNT_MAX)
                break;

            /* => attach user context to var config */
            cfg->user_ctx = (void *) &gw->cfg_varlist[i];

            /* => fill extra var config */
            gw->cfg_varlist[i].n = gw->fip.n;
            if (name) {
                if (sizeof(gw->cfg_varlist[i].name) >
                    strlen(json_object_get_string(name))) {
                    strcpy(gw->cfg_varlist[i].name,
                        json_object_get_string(name));
                }
            }

            /* => fill powerfip var config */
            cfg->type = PWRFIP_VAR_TYPE_SYNC;
            if (type) {
                if (!strcmp("prod", json_object_get_string(type)))
                    cfg->type = PWRFIP_VAR_TYPE_PROD;
                else if (!strcmp("cons", json_object_get_string(type)))
                    cfg->type = PWRFIP_VAR_TYPE_CONS;
            }

            cfg->id = (uint16_t) strtol(json_object_get_string(id), NULL, 0);

            switch (cfg->type)
            {
            case PWRFIP_VAR_TYPE_PROD:
                cfg->prod.payload_bsz = json_object_get_int(bsz);
                /* refreshment ? */
                if (refresh) {
                    if (!strcmp("yes", json_object_get_string(refresh))) {
                        cfg->prod.flags |= PWRFIP_VAR_FLAGS_REFRESH;
                        cfg->prod.refreshment_ustime = \
                            json_object_get_int(refresh_us);
                    }
                }
            break;
            case PWRFIP_VAR_TYPE_CONS:
                cfg->cons.payload_bsz = json_object_get_int(bsz);
                cfg->cons.flags |= PWRFIP_VAR_FLAGS_CHK_PDU_LEN;
                /* refreshment ? */
                if (refresh) {
                    if (!strcmp("yes", json_object_get_string(refresh))) {
                        cfg->cons.flags |= PWRFIP_VAR_FLAGS_REFRESH;
                    }
                }
                /* promptness ? */
                if (prompt) {
                    if (!strcmp("yes", json_object_get_string(prompt))) {
                        cfg->cons.flags |= PWRFIP_VAR_FLAGS_PROMPT;
                        cfg->cons.promptness_ustime = \
                            json_object_get_int(prompt_us);
                    }
                }
            break;
            default: break;
            }

            /* is an event attached ? */
            if (evt)
                gw_fip_evt_registration(gw, cfg, evt);

            gw->cfg_varcnt++;
        }

        /* bus arbiter settings */
        if (ba) {
            struct json_object *ba_enable, *prio, *cycles, *cycle;
            int j, k, cycles_cnt = 0;

            ba_enable = prio = cycles = NULL;
            json_object_object_get_ex(ba, "enable", &ba_enable);
            json_object_object_get_ex(ba, "priority", &prio);
            json_object_object_get_ex(ba, "cycles", &cycles);

            /* is ba is enabled ? */
            if (ba_enable) {
                if (!strcmp("yes", json_object_get_string(ba_enable))) {
                    n_cfg->ba.enable = 1;
                    n_cfg->ba.priority = json_object_get_int(prio);
                    if (cycles)
                        cycles_cnt = json_object_array_length(cycles);
                }
            }

            //printf("ba_enable     : %s\n", json_object_get_string(ba_enable));
            //printf("ba_priority   : %d\n", json_object_get_int(prio));
            //printf("ba_cycles_cnt : %d\n", cycles_cnt);

            for (i = 0; i < cycles_cnt; i++) {
                struct gw_fip_mcycle *mc_cfg = &gw->fip.mc_cfglist[i];
                struct json_object *winds, *wind;
                int winds_cnt = 0, reqs_idx = 0;

                winds = wind = NULL;
                cycle = json_object_array_get_idx(cycles, i);
                json_object_object_get_ex(cycle, "windows", &winds);
                if (winds)
                    winds_cnt = json_object_array_length(winds);

                //printf("[mcycle %03d]\n", i + 1);
                //printf("  winds_cnt   : %d\n", winds_cnt);

                for (j = 0; j < winds_cnt; j++) {
                    struct pwrfip_ba_wind_cfg *wind_cfg = &mc_cfg->ba_wind_cfglist[j];
                    struct json_object *wind_type, *end_ustime, *reqs, *req;
                    int reqs_cnt = 0;

                    wind_type = end_ustime = reqs = req = NULL;
                    wind = json_object_array_get_idx(winds, j);
                    json_object_object_get_ex(wind, "type", &wind_type);
                    json_object_object_get_ex(wind, "end_ustime", &end_ustime);
                    json_object_object_get_ex(wind, "requests", &reqs);

                    //printf("  [wind %03d]\n", j + 1);
                    //printf("    wind_type : %s\n", json_object_get_string(wind_type));
                    //printf("    end_ustime: %dus\n", json_object_get_int(end_ustime));
                    //printf("    reqs_cnt  : %d\n", (reqs) ? json_object_array_length(reqs) : 0);

                    if (wind_type) {
                        if (!strcmp("periodic", json_object_get_string(wind_type)))
                        {
                            if (reqs)
                                reqs_cnt = json_object_array_length(reqs);

                            wind_cfg->type = PWRFIP_BA_WIND_PER;
                            wind_cfg->per.req_cnt = reqs_cnt;
                            wind_cfg->per.req_list = &mc_cfg->ba_reqlist[reqs_idx];
                        }
                        else if (!strcmp("aperiodic_var", json_object_get_string(wind_type)))
                        {
                            wind_cfg->type = PWRFIP_BA_WIND_APER_VAR;
                            wind_cfg->aper_var.end_ustime = json_object_get_int(end_ustime);
                            wind_cfg->aper_var.enable_testp = 1;
                        }
                        else if (!strcmp("aperiodic_msg", json_object_get_string(wind_type)))
                        {
                            wind_cfg->type = PWRFIP_BA_WIND_APER_MSG;
                            wind_cfg->aper_msg.end_ustime = json_object_get_int(end_ustime);
                        }
                        else if (!strcmp("wait", json_object_get_string(wind_type)))
                        {
                            wind_cfg->type = PWRFIP_BA_WIND_WAIT;
                            wind_cfg->wait.end_ustime = json_object_get_int(end_ustime);
                            wind_cfg->wait.is_silent = 0;
                            wind_cfg->wait.is_ext_resync = 0;
                        }
                    }
                    /* get mcycle periodic requests */
                    for (k = 0; k < reqs_cnt; k++) {
                        struct pwrfip_ba_request *reqlist = &mc_cfg->ba_reqlist[reqs_idx + k];
                        struct json_object *req_type, *req_id;

                        req_type = req_id = NULL;
                        req = json_object_array_get_idx(reqs, k);
                        json_object_object_get_ex(req, "type", &req_type);
                        json_object_object_get_ex(req, "id", &req_id);

                        //printf("      + %s(0x%04x)\n",
                        //    json_object_get_string(req_type),
                        //    (uint16_t) strtol(json_object_get_string(req_id), NULL, 0));

                        if (req_type) {
                            if (!strcmp("id_dat", json_object_get_string(req_type)))
                            {
                                reqlist->type = PWRFIP_BA_ID_DAT;
                                reqlist->id = \
                                    (uint16_t) strtol(json_object_get_string(req_id), NULL, 0);
                            }
                            else if (!strcmp("id_msg", json_object_get_string(req_type)))
                            {
                                reqlist->type = PWRFIP_BA_ID_MSG;
                                reqlist->id = \
                                    (uint16_t) strtol(json_object_get_string(req_id), NULL, 0);
                            }
                        }
                    }
                    reqs_idx += reqs_cnt;
                }

                gw->fip.mc_cfg_cnt++;
                mc_cfg->mc_cfg.wind_cnt = winds_cnt;
                mc_cfg->mc_cfg.wind_list = &mc_cfg->ba_wind_cfglist[0];
            }
        }
    }

    /* release root object */
    json_object_put(root);
    return 0;
}
