/*
 *  Copyright 2019 Microchip Technology Inc. and its subsidiaries
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <wrap-json.h>
#include "wrap-unicens.h"

typedef struct async_job_ {
    wrap_ucs_result_cb_t result_fptr;
    void *result_user_ptr;
} async_job_t;

typedef struct parse_result_ {
    int done;
    char *str_result;
} parse_result_t;

static afb_api_t api_handle_ = NULL;

/* Initializes UNICENS API wrapper */
extern int wrap_ucs_init(afb_api_t api_handle) {
    api_handle_ = api_handle;
    return afb_api_require_api(api_handle_, "UNICENS", 1);
}

/*
 * Subscribes to unicens2-binding events.
 * \return    Returns 0 if successful, otherwise != 0".
 */
extern int wrap_ucs_subscribe_sync(void) {
    int err;

    json_object *j_response, *j_query = NULL;
    char *error, *info;

    /* Build an empty JSON object */
    if((err = wrap_json_pack(&j_query, "{}"))) {
        AFB_API_ERROR(api_handle_, "Failed to create subscribe json object");
        return err;
    }

    if((err = afb_api_call_sync(api_handle_, "UNICENS", "subscribe", j_query, &j_response, &error, &info))) {
        AFB_API_ERROR(api_handle_, "Fail subscribing to UNICENS events");
        return err;
    }
    else {
        AFB_API_NOTICE(api_handle_, "Subscribed to UNICENS events, res=%s", json_object_to_json_string(j_response));
        json_object_put(j_response);
    }

    return 0;
}

/*
 * Subscribes to unicens2-binding RX message events.
 * \return    Returns 0 if successful, otherwise != 0".
 */
extern int wrap_ucs_subscriberx_sync(void) {
    int err;

    json_object *j_response, *j_query = NULL;
    char *error, *info;

    /* Build an empty JSON object */
    if((err = wrap_json_pack(&j_query, "{}"))) {
        AFB_API_ERROR(api_handle_, "Failed to create subscribe RX json object");
        return err;
    }

    if((err = afb_api_call_sync(api_handle_, "UNICENS", "subscriberx", j_query, &j_response, &error, &info))) {
        AFB_API_ERROR(api_handle_, "Fail subscribing to UNICENS RX events");
        return err;
    }
    else {
        AFB_API_NOTICE(api_handle_, "Subscribed to UNICENS RX events, res=%s", json_object_to_json_string(j_response));
        json_object_put(j_response);
    }

    return 0;
}

extern int wrap_ucs_interpretrx_event(const char *event, struct json_object *object, wrap_ucs_rx_message_cb_t callback) {
    int node_id = 0;
    int msg_id = 0;
    uint8_t *data_ptr = NULL;
    size_t data_sz = 0;

    if (strcmp(event, "UNICENS/rx-message") != 0) {
        return -1; // unhandled event
    }

    if (wrap_json_unpack(object, "{s:i, s:i, s?Y}", "node", &node_id, "msgid", &msg_id, "data", &data_ptr, &data_sz) != 0) {
        AFB_API_NOTICE(api_handle_, "Parsing rx-message failed.");
        return -2;
    }

    if (callback != NULL) {
        callback((uint16_t)node_id, (uint16_t)msg_id, (uint16_t)data_sz, data_ptr);
    }

    return 0;
}

extern int wrap_ucs_interpret_event(const char *event, struct json_object *object, wrap_ucs_availability_cb_t callback) {
    int node_id = 0;
    int available = false;

    if (strcmp(event, "UNICENS/node-availibility") != 0) {
        return -1; // unhandled event
    }

    if (wrap_json_unpack(object, "{s:i,s:b}", "node", &node_id, "available", &available) != 0) {
        AFB_API_NOTICE(api_handle_, "Parsing node-availibility failed.");
        return -2;
    }

    if (callback != NULL) {
        callback((uint16_t)node_id, (bool)available);
    }

    return 0;
}

/*
 * Write I2C command to a network node.
 * \param    node    Node address
 * \param    ata_ptr Reference to command data
 * \param     data_sz    Size of the command data. Valid values: 1..32.
 * \return    Returns 0 if successful, otherwise != 0".
 */
extern int wrap_ucs_i2cwrite_sync(uint16_t node, uint8_t *data_ptr, uint8_t data_sz) {
    int err;
    uint8_t cnt;

    json_object *j_response, *j_query, *j_array = NULL;
    char *error, *info;

    j_query = json_object_new_object();
    j_array = json_object_new_array();

    if(! j_query || ! j_array) {
	if (j_query)
            json_object_put(j_query);
	if (j_array)
            json_object_put(j_array);
        AFB_API_ERROR(api_handle_, "Failed to create writei2c json objects");
        return -1;
    }

    for(cnt = 0U; cnt < data_sz; cnt++)
        json_object_array_add(j_array, json_object_new_int(data_ptr[cnt]));

    json_object_object_add(j_query, "node", json_object_new_int(node));
    json_object_object_add(j_query, "data", j_array);

    if((err = afb_api_call_sync(api_handle_, "UNICENS", "writei2c", j_query, &j_response, &error, &info))) {
        AFB_API_ERROR(api_handle_, "Failed to call writei2c_sync");
        return err;
    }
    else {
        AFB_API_INFO(api_handle_, "Called writei2c_sync, res=%s", json_object_to_json_string(j_response));
        json_object_put(j_response);
    }

    return 0;
}

extern int wrap_ucs_sendmessage_sync(uint16_t src_addr, uint16_t msg_id, uint8_t *data_ptr, uint8_t data_sz) {

    json_object *j_query, *j_response = NULL;
    char *error, *info;
    int err = 1;
    int node = (int)src_addr;
    int msgid = (int)msg_id;
    size_t data_size = (size_t)data_sz;

    AFB_API_NOTICE(api_handle_, "--- HAL triggering send message ---");

    /* skip data attribute if possible, wrap_json_unpack may fail to deal with
     * an empty Base64 string */
    if (data_size > 0)
        wrap_json_pack(&j_query, "{s:i, s:i, s:Y}", "node", node, "msgid", msgid, "data", data_ptr, data_size);
    else
        wrap_json_pack(&j_query, "{s:i, s:i}", "node", node, "msgid", msgid);

    AFB_API_NOTICE(api_handle_, "wrap_ucs_sendmessage: jquery=%s", json_object_to_json_string(j_query));

    err = afb_api_call_sync(api_handle_, "UNICENS", "sendmessage", j_query, &j_response, &error, &info);

    if (err != 0) {
        AFB_API_ERROR(api_handle_, "Failed to call wrap_ucs_sendmessage ret=%d", err);
    }
    else {
        AFB_API_NOTICE(api_handle_, "Called wrap_ucs_sendmessage, successful");
    }

    if (j_response != NULL) {
        AFB_API_NOTICE(api_handle_, "wrap_ucs_sendmessage, response=%s", json_object_to_json_string(j_response));
        json_object_put(j_response);
    }

    return err;
}

/* ---------------------------- ASYNCHRONOUS API ---------------------------- */
static void wrap_ucs_i2cwrite_cb(void *closure, /*int status, */struct json_object *j_result,
        const char *error, const char * info, afb_api_t api) {
    async_job_t *job_ptr;

    AFB_API_INFO(api_handle_, "%s: closure=%p status=?, res=%s", __func__, closure, /*status,*/ json_object_to_json_string(j_result));

    if(closure) {
        job_ptr = (async_job_t *) closure;

        if(job_ptr->result_fptr)
            job_ptr->result_fptr(0/*(uint8_t) abs(status)*/, job_ptr->result_user_ptr);

        free(closure);
    }
}

/*
 * Write I2C command to a network node.
 * \param    node        Node address
 * \param    data_ptr    Reference to command data
 * \param    data_sz        Size of the command data. Valid values: 1..32.
 * \return    Returns 0 if successful, otherwise != 0".
 */
extern int wrap_ucs_i2cwrite(uint16_t node,
        uint8_t *data_ptr,
        uint8_t data_sz,
        wrap_ucs_result_cb_t result_fptr,
        void *result_user_ptr)
{
    uint8_t cnt;

    json_object *j_query, *j_array = NULL;
    async_job_t *job_ptr = NULL;

    j_query = json_object_new_object();
    j_array = json_object_new_array();

    if(! j_query || ! j_array) {
        AFB_API_ERROR(api_handle_, "Failed to create writei2c json objects");
        return -1;
    }

    for(cnt = 0U; cnt < data_sz; cnt++)
        json_object_array_add(j_array, json_object_new_int(data_ptr[cnt]));

    json_object_object_add(j_query, "node", json_object_new_int(node));
    json_object_object_add(j_query, "data", j_array);

    job_ptr = malloc(sizeof(async_job_t));

    if(! job_ptr) {
        AFB_API_ERROR(api_handle_, "Failed to create async job object");
        json_object_put(j_query);
        return -2;
    }

    job_ptr->result_fptr = result_fptr;
    job_ptr->result_user_ptr = result_user_ptr;

    afb_api_call(api_handle_, "UNICENS", "writei2c", j_query, wrap_ucs_i2cwrite_cb, job_ptr);
    return 0;
}