/*
 *  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
#ifndef AFB_BINDING_VERSION
#  define AFB_BINDING_VERSION 3
#endif

#include <stdio.h>
#include <string.h>
#include <afb/afb-binding.h>
#include <wrap-json.h>
#include "wrap-unicens.h"
#include "microphone.h"

#define HB(value)       ((uint8_t)((uint16_t)(value) >> 8))
#define LB(value)       ((uint8_t)((uint16_t)(value) & (uint16_t)0xFFU))

/*****************************************************************************
 * types
 */

enum microphone_mode
{
    MICROPHONE_MODE_NONE        = 0,
    MICROPHONE_MODE_DOA         = 1,
    MICROPHONE_MODE_THINKING    = 2,
    MICROPHONE_MODE_SPEAKING    = 3,
    MICROPHONE_MODE_ERROR       = 4,
    MICROPHONE_MODE_WAKING      = 5,
    MICROPHONE_MODE_ENDING      = 6,
    MICROPHONE_MODE_STATIC      = 7,
    MICROPHONE_MODE_CYLON       = 8,
    MICROPHONE_MODE_RAINBOW     = 9,
    MICROPHONE_MODE_WHEEL       = 10,
    MICROPHONE_MODE_UNKNOWN     = 11
};

/*****************************************************************************
 * local prototypes
 */

static int microphone_mode_set(enum microphone_mode mode);
static int microphone_doa_get(void);
static void microphone_doa_status(uint16_t data_sz, uint8_t *data_ptr);

/*****************************************************************************
 * local variables and definitions
 */

#define NODE_ID         ((uint16_t)0x520U)
#define MSG_ID_MODE     0x1001U /* set LED mode */
#define MSG_ID_LED_DIR  0x1002U /* set LED direction*/
#define MSG_ID_DOA      0x1003U /* get audio direction */
#define MSG_ID_LED_COL  0x1006U /* set static LED color */
#define MSG_OP_SET      0x00U
#define MSG_OP_GET      0x01U

#define MSG_MAX_PAYLOAD_SZ  10U
static uint8_t _tx_payload[MSG_MAX_PAYLOAD_SZ];
static bool _available = false;
static bool _doa_running = false;
static afb_req_t _req_doa_get = NULL;

/*****************************************************************************
 * functions
 */

extern void microphone_availablility_changed(uint16_t node_id, bool available) {
    if (node_id == NODE_ID) {
        AFB_API_DEBUG(afbBindingRoot, "%s: microphone new availability=%d", __func__, available);
        _available = available;
        _doa_running = false;
    }
}

extern void microphone_message_received(uint16_t node, uint16_t msg_id, uint16_t data_sz, uint8_t *data_ptr) {
    
    if (node != NODE_ID) {
        return;
    }
    
    switch (msg_id) {
        case MSG_ID_DOA:
            microphone_doa_status(data_sz, data_ptr);
            break;
        default:
            AFB_API_NOTICE(afbBindingRoot, "microphone_message_received node=%d, msg_id=%d, data_sz=%d", node, msg_id, data_sz);
            break;
    }
}

static int microphone_mode_set(enum microphone_mode mode) {
    AFB_API_NOTICE(afbBindingRoot, "microphone_mode_set value=%d", mode);
    if (_available == false) {
        AFB_API_NOTICE(afbBindingRoot, "%s: node is not available", __func__);
        return -1;
    }

    if  (mode < MICROPHONE_MODE_UNKNOWN) {
        _tx_payload[0] = MSG_OP_SET;
        _tx_payload[1] = (uint8_t)mode;
        wrap_ucs_sendmessage_sync(NODE_ID, MSG_ID_MODE, _tx_payload, 2U);
    }
    else {
        AFB_API_NOTICE(afbBindingRoot, "%s: given mode is unknown", __func__);
        return -1;
    }

    return 0;
}

static int microphone_doa_get(void) {
    AFB_API_NOTICE(afbBindingRoot, "microphone_doa_get started");
    if (_available == false) {
        AFB_API_NOTICE(afbBindingRoot, "%s: node is not available", __func__);
        return -1;
    }

    if (_doa_running) {
        AFB_API_NOTICE(afbBindingRoot, "%s: request is still running", __func__);
        return -2;
    }

    _tx_payload[0] = MSG_OP_GET;
    wrap_ucs_sendmessage_sync(NODE_ID, MSG_ID_DOA, _tx_payload, 1U);

    return 0;
}

static void microphone_doa_status(uint16_t data_sz, uint8_t *data_ptr) {
    uint16_t angle = 0U;
    
    if ((data_sz == 3U) && (data_ptr[0] == 0x0CU)) {
        angle = (uint16_t)((uint16_t)data_ptr[1] << 8 | (uint16_t)data_ptr[2]);
        AFB_API_NOTICE(afbBindingRoot, "microphone_doa_status: angle=%d", angle);
        if (_req_doa_get != NULL) {
            struct json_object* j_resp;
            j_resp = json_object_new_object();
            wrap_json_pack(&j_resp, "{s:i}", "value", angle);
            afb_req_reply(_req_doa_get, j_resp, NULL, "response successful");
            afb_req_unref(_req_doa_get);
            _req_doa_get = NULL;
        }
    }
}

static int microphone_led_direction_set(uint16_t direction) {
    AFB_API_NOTICE(afbBindingRoot, "microphone_led_direction_set executed");
    if (_available == false) {
        AFB_API_NOTICE(afbBindingRoot, "%s: node is not available", __func__);
        return -1;
    }

    _tx_payload[0] = MSG_OP_SET;
    _tx_payload[1] = HB(direction);
    _tx_payload[2] = LB(direction);
    wrap_ucs_sendmessage_sync(NODE_ID, MSG_ID_LED_DIR, _tx_payload, 3U);

    return 0;
}

static int microphone_static_ledcolor_set(uint8_t red, uint8_t green, uint8_t blue) {
    AFB_API_NOTICE(afbBindingRoot, "microphone_static_led_color_set executed");
    if (_available == false) {
        AFB_API_NOTICE(afbBindingRoot, "%s: node is not available", __func__);
        return -1;
    }

    _tx_payload[0] = MSG_OP_SET;
    _tx_payload[1] = red;
    _tx_payload[2] = green;
    _tx_payload[3] = blue;
    wrap_ucs_sendmessage_sync(NODE_ID, MSG_ID_LED_COL, _tx_payload, 4U);

    return 0;
}

/*****************************************************************************
 * JSON API
 */

extern void microphone_mode_set_api(afb_req_t request) {
    char *str_mode = NULL;
    struct json_object* j_obj = afb_req_json(request);

    AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: %s:%s", __func__, json_object_get_string(j_obj));

    if (wrap_json_unpack(j_obj, "{s:s}", "value", &str_mode) == 0) {
        AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: decoded value=%s", str_mode);
        enum microphone_mode mic_mode = MICROPHONE_MODE_UNKNOWN;

        if (strcmp(str_mode, "none") == 0) {
            mic_mode = MICROPHONE_MODE_NONE;
        }
        else if (strcmp(str_mode, "doa") == 0) {
            mic_mode = MICROPHONE_MODE_DOA;
        }
        else if (strcmp(str_mode, "thinking") == 0) {
            mic_mode = MICROPHONE_MODE_THINKING;
        }
        else if (strcmp(str_mode, "speaking") == 0) {
            mic_mode = MICROPHONE_MODE_SPEAKING;
        }
        else if (strcmp(str_mode, "error") == 0) {
            mic_mode = MICROPHONE_MODE_ERROR;
        }
        else if (strcmp(str_mode, "waking") == 0) {
            mic_mode = MICROPHONE_MODE_WAKING;
        }
        else if (strcmp(str_mode, "ending") == 0) {
            mic_mode = MICROPHONE_MODE_ENDING;
        }
        else if (strcmp(str_mode, "static") == 0) {
            mic_mode = MICROPHONE_MODE_STATIC;
        }
        else if (strcmp(str_mode, "cylon") == 0) {
            mic_mode = MICROPHONE_MODE_CYLON;
        }
        else if (strcmp(str_mode, "rainbow") == 0) {
            mic_mode = MICROPHONE_MODE_RAINBOW;
        }
        else if (strcmp(str_mode, "wheel") == 0) {
            mic_mode = MICROPHONE_MODE_WHEEL;
        }

        if (mic_mode < MICROPHONE_MODE_UNKNOWN) {
            microphone_mode_set(mic_mode);
            afb_req_success(request, NULL, NULL);
        }
        else {
            afb_req_fail(request, "argument 'value' is not set to a known value", NULL);
        }
    }
    else {
        afb_req_fail(request, "missing argument 'value'", NULL);
    }
}

extern void microphone_doa_get_api(afb_req_t request) {
    struct json_object* j_obj = afb_req_json(request);    
    
    if (microphone_doa_get() != 0) {
        AFB_API_NOTICE(afbBindingRoot, "function call failed: %s:%s", __func__, json_object_get_string(j_obj));
        afb_req_fail(request, "function call failed", NULL);
    }
    else {
        _req_doa_get = afb_req_addref(request);
    }
}

extern void microphone_led_dir_set_api(afb_req_t request) {
    int direction = 0;
    struct json_object* j_obj = afb_req_json(request);

    AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: %s:%s", __func__, json_object_get_string(j_obj));

    if (wrap_json_unpack(j_obj, "{s:i}", "value", &direction) == 0) {
        AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: decoded value=%d", direction);
        
        if ((direction >= 0) && (direction < 360)) {
            if (microphone_led_direction_set((uint16_t)direction) == 0) {
                afb_req_success(request, NULL, NULL);
            }
            else {
                afb_req_fail(request, "Call failed. Microphone not available?", NULL);
            }
        }
        else {
            afb_req_fail(request, "Argument 'value' not in range 0...359.", NULL);
        }
    }
    else {
        afb_req_fail(request, "Missing argument 'value'", NULL);
    }
}

extern void microphone_static_ledcolor_set_api(afb_req_t request) {
    int red, green, blue = 0;
    struct json_object* j_obj = afb_req_json(request);

    AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: %s:%s", __func__, json_object_get_string(j_obj));

    if (wrap_json_unpack(j_obj, "{s:i, s:i, s:i}", "red", &red, "green", &green, "blue", &blue) == 0) {
        AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: decoded rgb=%d,%d,%d", red, green, blue);
        
        if ( (red >= 0) && (red <= 0xFF) && (green >= 0) && (green <= 0xFF) && (blue >= 0) && (blue <= 0xFF)) {
            if (microphone_static_ledcolor_set((uint8_t)red, (uint8_t)green, (uint8_t)blue) == 0) {
                afb_req_success(request, NULL, NULL);
            }
            else {
                afb_req_fail(request, "Call failed. Microphone not available?", NULL);
            }
        }
        else {
            afb_req_fail(request, "Argument 'value' not in range 0..255.", NULL);
        }
    }
    else {
        afb_req_fail(request, "Error while parsing arguments.", NULL);
    }
}

extern void microphone_is_available_api(afb_req_t request) {
    struct json_object* j_obj = afb_req_json(request);
    struct json_object* j_resp = json_object_new_object();

    AFB_API_NOTICE(afbBindingRoot, "UNICENS-CONTROLLER: %s:%s", __func__, json_object_get_string(j_obj));
    
    if (j_resp != NULL) {
        wrap_json_pack(&j_resp, "{s:b}", "available", (int)_available);
        afb_req_reply(request, j_resp, NULL, "response successful");
    }
    else {
        afb_req_fail(request, "Cannot allocate response object.", NULL);
    }
}