/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#include <assert.h>
#include <string.h>
#include <stdio.h>

#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "console/console.h"
#include "hal/hal_gpio.h"
#include "osdp/osdp_mynewt.h"
#ifdef ARCH_sim
#include "mcu/mcu_sim.h"
#endif

#define COMMAND_HANDLER_INTERVAL (OS_TICKS_PER_SEC)

#if MYNEWT_VAL(OSDP_MODE_PD)
#define OSDP_KEY_STRING MYNEWT_VAL(OSDP_PD_SCBK)
#else
#define OSDP_KEY_STRING MYNEWT_VAL(OSDP_MASTER_KEY)
#endif

/* Use the first "per user" log module slot for test module. */
#define LOG_MODULE (LOG_MODULE_PERUSER + 0)
/* Convenience macro for logging to the app module. */
#define LOG(lvl, ...) LOG_ ## lvl(&g_logger, LOG_MODULE, __VA_ARGS__)

extern struct log g_logger;
struct log g_logger;

static struct os_callout cmd_timer;

static int create_keypress_event(struct osdp_event *event);
static int create_cardreader_event(struct osdp_event *event);
static int create_mfgreply_event(struct osdp_cmd *oc);
static int pd_command_handler(void *arg, struct osdp_cmd *cmd);

static struct osdp_t *ctx;

/* From osdp.h */
static const char *const osdp_cmd_str[] = {
    "OSDP_CMD_OUTPUT",
    "OSDP_CMD_LED",
    "OSDP_CMD_BUZZER",
    "OSDP_CMD_TEXT",
    "OSDP_CMD_KEYSET",
    "OSDP_CMD_COMSET",
    "OSDP_CMD_MFG",
    "OSDP_CMD_SENTINEL"
};

/*
 * Callback function registered with the library
 * Commands received from CP will be reflected here
 */
static int
pd_command_handler(void *arg, struct osdp_cmd *cmd)
{
    (void)(arg);
    int ret = 0;

    LOG(INFO, "CMD [%d] = %s\n", cmd->id, osdp_cmd_str[cmd->id - 1]);

    switch (cmd->id) {
    case OSDP_CMD_COMSET:
        break;
    case OSDP_CMD_BUZZER:
        LOG(INFO, "\n\trdr: %d,\n\tctrl_code: %d,\n\ton_ct: %d,\n\toff_ct: %d,\n\trep_count: %d\n",
            cmd->buzzer.reader, cmd->buzzer.control_code, cmd->buzzer.on_count,
            cmd->buzzer.off_count, cmd->buzzer.rep_count);
        break;
    case OSDP_CMD_LED:
        LOG(INFO, "\n\trdr: %d,\n\tctrl_code: %d,\n\tled_num: %d,\n\ton_ct: %d,\n\toff_ct: %d,\n\ton_clr: %d,\n\toff_clr: %d,\n\ttmr_ct: %d\n",
            cmd->led.reader, cmd->led.temporary.control_code,
            cmd->led.led_number, cmd->led.temporary.on_count,
            cmd->led.temporary.off_count, cmd->led.temporary.on_color,
            cmd->led.temporary.off_color, cmd->led.temporary.timer_count);
        break;
    case OSDP_CMD_TEXT:
        LOG(INFO, "\n\trdr: %d,\n\tctrl_code: %d,\n\ttemp_time: %d,\n\toffset_row: %d,\n\toffset_col: %d,\n\tdata: %s\n",
            cmd->text.reader, cmd->text.control_code, cmd->text.temp_time,
            cmd->text.offset_row, cmd->text.offset_col, cmd->text.data);
        break;
    case OSDP_CMD_MFG:
        LOG(INFO, "\n\tv_code: 0x%04x,\n\tlen: %d,\n\tdata: ", cmd->mfg.vendor_code,
            cmd->mfg.length);
        for (int i = 0; i < cmd->mfg.length; ++i) {
            printf("%02x ", cmd->mfg.data[i]);
        }
        LOG(INFO, "\n");
        /* Send manufacturer specific reply. Indicate ret > 0 to send reply */
        LOG(INFO, "Sending manufacturer specific reply\n");
        create_mfgreply_event(cmd);
        ret = 1;
        break;
    default:
        break;
    }

    return ret;
}

static int
create_mfgreply_event(struct osdp_cmd *oc)
{
    int i, data_length, vendor_code;
    struct osdp_cmd_mfg *cmd = &oc->mfg;

    /* Sample reply */
    uint8_t data_bytes[] = {"ManufacturerReply"};
    data_length = sizeof(data_bytes);
    vendor_code = 0x00030201;

    cmd->vendor_code = (uint32_t)vendor_code;
    cmd->length = data_length;
    for (i = 0; i < cmd->length; i++) {
        cmd->data[i] = data_bytes[i];
    }

    return 0;
}

static int
create_cardreader_event(struct osdp_event *event)
{
    int i, data_length, reader_no, format, direction, len_bytes;
    struct osdp_event_cardread *ev = &event->cardread;

    /* Sample parameters */
    uint8_t data_bytes[] = {"CardCredentials"};
    data_length = sizeof(data_bytes);
    reader_no = 1;
    format = OSDP_CARD_FMT_ASCII;
    direction = 1;
    event->type = OSDP_EVENT_CARDREAD;

    if (format == OSDP_CARD_FMT_RAW_WIEGAND ||
        format == OSDP_CARD_FMT_RAW_UNSPECIFIED) {
        len_bytes = (data_length + 7) / 8; /* bits to bytes */
    } else {
        len_bytes = data_length;
    }

    if (len_bytes > OSDP_EVENT_CARDREAD_MAX_DATALEN) {
        return -1;
    }

    ev->reader_no = (uint8_t)reader_no;
    ev->format = (uint8_t)format;
    ev->direction = (uint8_t)direction;
    ev->length = len_bytes;
    for (i = 0; i < len_bytes; i++) {
        ev->data[i] = data_bytes[i];
    }

    return 0;
}

static int
create_keypress_event(struct osdp_event *event)
{
    int i, data_length, reader_no;
    struct osdp_event_keypress *ev = &event->keypress;

    /* Sample parameters */
    event->type = OSDP_EVENT_KEYPRESS;
    uint8_t data_bytes[] = {"KeyPress"};
    data_length = sizeof(data_bytes);
    reader_no = 1;

    ev->reader_no = (uint8_t)reader_no;
    ev->length = data_length;
    for (i = 0; i < ev->length; i++) {
        ev->data[i] = data_bytes[i];
    }

    return 0;
}

/*
 * Handler called after cmd_handler timer timeout
 */
static void
cmd_handler(struct os_event *ev)
{
    assert(ev != NULL);
    static int cmd = 0;
    uint8_t sc_active;

    osdp_get_sc_status_mask(ctx, &sc_active);

    if (sc_active) {
        struct osdp_event event;
        if (cmd == 0) {
            LOG(INFO, "Sending Card Read\n");
            create_cardreader_event(&event);
        } else if (cmd == 1) {
            LOG(INFO, "Sending Key Press\n");
            create_keypress_event(&event);
        }
        osdp_pd_submit_event(ctx, &event);
        cmd++;
        if (cmd > 1) {
            cmd = 0;
        }
    }

    /* Heartbeat blink */
    hal_gpio_toggle(LED_BLINK_PIN);

    /* Restart */
    os_callout_reset(&cmd_timer, COMMAND_HANDLER_INTERVAL);
}

/*
 * Initialize all timers
 */
static int
timers_init(void)
{
    /* Configure and reset timer */
    os_callout_init(&cmd_timer, os_eventq_dflt_get(), cmd_handler, NULL);

    os_callout_reset(&cmd_timer, COMMAND_HANDLER_INTERVAL);

    return 0;
}

static int
char2hex(char c, uint8_t *x)
{
    if (c >= '0' && c <= '9') {
        *x = c - '0';
    } else if (c >= 'a' && c <= 'f') {
        *x = c - 'a' + 10;
    } else if (c >= 'A' && c <= 'F') {
        *x = c - 'A' + 10;
    } else {
        return -OS_EINVAL;
    }

    return 0;
}

static size_t
hex2bin(const char *hex, size_t hexlen, uint8_t *buf, size_t buflen)
{
    uint8_t dec;

    if (buflen < hexlen / 2 + hexlen % 2) {
        return 0;
    }

    /* if hexlen is uneven, insert leading zero nibble */
    if (hexlen % 2) {
        if (char2hex(hex[0], &dec) < 0) {
            return 0;
        }
        buf[0] = dec;
        hex++;
        buf++;
    }

    /* regular hex conversion */
    for (size_t i = 0; i < hexlen / 2; i++) {
        if (char2hex(hex[2 * i], &dec) < 0) {
            return 0;
        }
        buf[i] = dec << 4;

        if (char2hex(hex[2 * i + 1], &dec) < 0) {
            return 0;
        }
        buf[i] += dec;
    }

    return hexlen / 2 + hexlen % 2;
}

int
mynewt_main(int argc, char **argv)
{
    int len;
    uint8_t key_buf[16];
    uint8_t *key = NULL;

    log_register("osdp_main_log", &g_logger, &log_console_handler, NULL, LOG_SYSLEVEL);

    sysinit();

    hal_gpio_init_out(LED_BLINK_PIN, 1);

    /* List capabilities of this PD */
    struct osdp_pd_cap cap[] = {
        {
            .function_code = OSDP_PD_CAP_READER_LED_CONTROL,
            .compliance_level = 1,
            .num_items = 1
        },
        {
            .function_code = OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
            .compliance_level = 1,
            .num_items = 1
        },
        {
            .function_code = OSDP_PD_CAP_OUTPUT_CONTROL,
            .compliance_level = 1,
            .num_items = 1
        },
        {
            .function_code = OSDP_PD_CAP_READER_TEXT_OUTPUT,
            .compliance_level = 1,
            .num_items = 1
        },
        { -1, 0, 0 }
    };

    /* Validate and assign key */
    if (MYNEWT_VAL(OSDP_SC_ENABLED) && strcmp(OSDP_KEY_STRING, "NONE") != 0) {

        len = strlen(OSDP_KEY_STRING);
        assert(len == 32);

        len = hex2bin(OSDP_KEY_STRING, 32, key_buf, 16);
        assert(len == 16);

        key = key_buf;
    }

    osdp_pd_info_t info_pd = {
        .address = MYNEWT_VAL(OSDP_PD_ADDRESS),
        .baud_rate = MYNEWT_VAL(OSDP_UART_BAUD_RATE),
        .flags = 0,
        .channel.send = NULL, /* Managed by the library */
        .channel.recv = NULL, /* Managed by the library */
        .channel.flush = NULL, /* Managed by the library */
        .channel.data = NULL, /* Managed by the library */
        .id = {
            .version = MYNEWT_VAL(OSDP_PD_ID_VERSION),
            .model = MYNEWT_VAL(OSDP_PD_ID_MODEL),
            .vendor_code = MYNEWT_VAL(OSDP_PD_ID_VENDOR_CODE),
            .serial_number = MYNEWT_VAL(OSDP_PD_ID_SERIAL_NUMBER),
            .firmware_version = MYNEWT_VAL(OSDP_PD_ID_FIRMWARE_VERSION),
        },
        .cap = cap,
        .scbk = key,
    };

    /* Initialize the library module */
    ctx = osdp_init(&info_pd);
    assert(ctx);

    osdp_pd_set_command_callback(ctx, pd_command_handler, NULL);

    timers_init();

    while (1) {
        os_eventq_run(os_eventq_dflt_get());
    }

    assert(0);

    return 0;
}
