#ifdef __cplusplus
extern "C" {
#endif
#include "802_15_4_config.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "ral_irq_handlers.h"

#include "nrf_drv_clock.h"

#include "boards.h"
#include "mac_mcps_data.h"
#include "sys_init.h"
#include "sys_task_scheduler.h"

#include "mac_common.h"
#include "mac_mlme_beacon_notify.h"
#include "mac_mlme_comm_status.h"
#include "mac_mlme_pib.h"
#include "mac_mlme_associate.h"
#include "mac_mlme_poll.h"
#include "mac_mlme_scan.h"
#include "mac_mlme_start.h"
#include "mac_security.h"
#ifdef __cplusplus
}
#endif

#include "SEGGER_RTT.h"
#include "SEGGER_RTT_Conf.h"

#define CONFIG_POOL_SIZE 0x2000
static __ALIGN(ALIGN_VALUE) uint8_t m_heap[CONFIG_POOL_SIZE];
#define CONFIG_ERROR_PIN LED_4

#define C11 (1 << 11)
#define C12 (1 << 12)
#define C13 (1 << 13)
#define C14 (1 << 14)
#define C15 (1 << 15)
#define C16 (1 << 16)
#define C17 (1 << 17)
#define C18 (1 << 18)
#define C19 (1 << 19)
#define C20 (1 << 20)
#define C21 (1 << 21)
#define C22 (1 << 22)
#define C23 (1 << 23)
#define C24 (1 << 24)
#define C25 (1 << 25)
#define C26 (1 << 26)

static void out_of_memory_callback(const void* data);
static void memory_freed_callback(const void* data);

#ifdef __cplusplus
constexpr sys_event_desc_t gen_sys_event_desc_t(sys_event_id_t event_id,
                                                void(callback)(const void*)) {
    return sys_event_desc_t{sys_queue_item_t{}, event_id, callback};
}
static sys_event_desc_t m_out_of_memory_desc =
    gen_sys_event_desc_t(SYS_EVENT_OUT_OF_MEMORY, out_of_memory_callback);
static sys_event_desc_t m_memory_freed_desc =
    gen_sys_event_desc_t(SYS_EVENT_MEMORY_FREED, memory_freed_callback);
#else
static sys_event_desc_t m_out_of_memory_desc = {
    .event_id = SYS_EVENT_OUT_OF_MEMORY, .callback = out_of_memory_callback};
static sys_event_desc_t m_memory_freed_desc = {
    .event_id = SYS_EVENT_MEMORY_FREED, .callback = memory_freed_callback};
#endif

void wait_ms(const uint32_t count) {
    SEGGER_RTT_printf(0, "dummy wait: ");
    for(volatile uint32_t i=0; i<2432u*count; i++) {
        uint32_t j = i%(2432u*100u);
        if(j == 0) {
            SEGGER_RTT_printf(0, ".", j);
        }
    }
    SEGGER_RTT_printf(0, "\n");
}

static void out_of_memory_callback(const void* data) {
    LEDS_ON(BIT(CONFIG_ERROR_PIN));
}

static void memory_freed_callback(const void* data) {
    LEDS_OFF(BIT(CONFIG_ERROR_PIN));
}

static void app_task_init(void) {
    LEDS_CONFIGURE(LEDS_MASK);
    LEDS_OFF(LEDS_MASK);

    sys_init(m_heap, CONFIG_POOL_SIZE);

    sys_event_subscribe(&m_out_of_memory_desc);
    sys_event_subscribe(&m_memory_freed_desc);
}

static void clock_init(void) {
    ret_code_t err_code = nrf_drv_clock_init();
    ASSERT((err_code == NRF_SUCCESS) ||
           (err_code == NRF_ERROR_MODULE_ALREADY_INITIALIZED));

    nrf_drv_clock_hfclk_request(NULL);
    while (!nrf_drv_clock_hfclk_is_running()) {
        // spin lock
    }

    nrf_drv_clock_lfclk_request(NULL);
    while (!nrf_drv_clock_lfclk_is_running()) {
        // spin lock
    }
}

void print_mac_status(const mac_status_t status) {
    switch (status) {
        case MAC_NO_BEACON:
            SEGGER_RTT_printf(0, "MAC_NO_BEACON");
            break;
        case MAC_SUCCESS:
            SEGGER_RTT_printf(0, "MAC_SUCCESS");
            break;
        case MAC_LIMIT_REACHED:
            SEGGER_RTT_printf(0, "MAC_LIMIT_REACHED");
            break;
        default:
            SEGGER_RTT_printf(0, "0x%x", status);
            break;
    }
}

void print_addr(const mac_addr_t* addr, const mac_addr_mode_t addr_mode,
                const char* field) {
    SEGGER_RTT_printf(0, "%s: ", field);
    switch (addr_mode) {
        case MAC_ADDR_NONE:
            SEGGER_RTT_printf(0, "NONE");
            break;
        case MAC_ADDR_SHORT:
            SEGGER_RTT_printf(0, "0x%04x (short)", addr->short_address);
            break;
        case MAC_ADDR_LONG:
        default:
            SEGGER_RTT_printf(0, "0x%08x%08x (long)",
                              (uint32_t)(addr->long_address >> 32),
                              (uint32_t)(addr->long_address));
            break;
    }
}

void print_pan_desc(const mac_pan_descriptor_t* pan) {
    SEGGER_RTT_printf(0, "{coord_pan_id: 0x%04x, ", pan->coord_pan_id);
    print_addr(&(pan->coord_address), pan->coord_addr_mode, "address");
    SEGGER_RTT_printf(0,
                      ", logical_channel: %u, timestamp: 0x%x, link_quality: "
                      "%u, superframe_spec: 0x%x, gts: %s}",
                      pan->logical_channel, pan->timestamp, pan->link_quality,
                      pan->superframe_spec, pan->gts_permit ? "true" : "false");
}

void print_scan_conf(const mlme_scan_conf_t* sc) {
    SEGGER_RTT_printf(0, "{status: ");
    print_mac_status(sc->status);
    SEGGER_RTT_printf(0, ", scan_type: ");
    switch (sc->scan_type) {
        case ED_SCAN:
            SEGGER_RTT_printf(0, "ED_SCAN");
            break;
        case ACTIVE_SCAN:
            SEGGER_RTT_printf(0, "ACTIVE_SCAN");
            break;
        case PASSIVE_SCAN:
            SEGGER_RTT_printf(0, "PASSIVE_SCAN");
            break;
        case ORPHAN_SCAN:
            SEGGER_RTT_printf(0, "ORPHAN_SCAN");
            break;
        default:
            SEGGER_RTT_printf(0, "Unknown(%x)", sc->scan_type);
            break;
    }
    SEGGER_RTT_printf(0, ", unscanned_channels: %x", sc->unscanned_channels);
    SEGGER_RTT_printf(0, ", result_list_size: %u, results: {",
                      sc->result_list_size);
    for (size_t i = 0; i < sc->result_list_size; i++) {
        const mac_pan_descriptor_t* pan = &(sc->pan_descriptor_list[i]);
        SEGGER_RTT_printf(0, "Result %u: {pan_descriptor: ", i);
        print_pan_desc(pan);

        //for an active scan, the content of the energy_detect_list must not be read.
        //const uint8_t energy = sc->energy_detect_list[i];
        //(void)energy;
        //SEGGER_RTT_printf(0, ", energy: %x",  energy);
        SEGGER_RTT_printf(0, "}, ");
    }
    SEGGER_RTT_printf(0, "}\n");
}

bool callback_called = false;
void scan_callback(mlme_scan_conf_t* sc) { callback_called = true; };
void start_callback(mlme_start_conf_t* sc) { callback_called = true; };

mlme_scan_req_t gen_mac_scan_req_t(
    mac_scan_type_t scan_type, uint32_t scan_channels, uint8_t scan_duration,
    mac_pan_descriptor_t* pan_desc_buf, size_t pan_desc_size,
    uint8_t* energy_detect_buf, size_t energy_detect_size) {

    mlme_scan_req_t ret;
    ret.scan_type = scan_type;
    ret.scan_channels = scan_channels;
    ret.scan_duration = scan_duration;
    ret.pan_descriptors_buf = pan_desc_buf;
    ret.pan_descriptors_buf_size = pan_desc_size;
    ret.energy_detect_buf = energy_detect_buf;
    ret.energy_detect_buf_size = energy_detect_size;
    return ret;
}

const mlme_scan_conf_t* scan_req(mac_scan_type_t scan_type,
                                 uint32_t scan_channels,
                                 uint8_t scan_duration) {
#define _ENERGY_DET_SIZE 8
#define _PAN_DESC_SIZE 8
    static mac_pan_descriptor_t pd[_PAN_DESC_SIZE];
    static uint8_t ed[_ENERGY_DET_SIZE];

    static mlme_scan_req_t scan_req;
    scan_req = gen_mac_scan_req_t(scan_type, scan_channels, scan_duration, pd,
                                  _PAN_DESC_SIZE, ed, _ENERGY_DET_SIZE);

    callback_called = false;

    mlme_scan_req(&scan_req, scan_callback);

    while (!callback_called) {
        sys_task_run();
    }

    return &scan_req.confirm;
}

void gen_mac_key_descr_t(mac_key_descr_t* key_descr) {
    mac_table_init(&key_descr->id_lookup_list);
    mac_table_init(&key_descr->key_device_list);
    mac_table_init(&key_descr->key_usage_list);
}

mac_device_descr_t gen_mac_device_descr_t(uint16_t pan_id,
                                          uint16_t short_address,
                                          uint64_t extended_address,
                                          uint32_t frame_counter, bool exempt) {
    mac_device_descr_t ret;
    ret.pan_id = pan_id;
    ret.short_address = short_address;
    ret.extended_address = extended_address;
    ret.frame_counter = frame_counter;
    ret.exempt = exempt;
    return ret;
}

mac_security_level_descr_t gen_mac_security_level_descr_t(
    uint8_t frame_type, uint8_t cmd_frame_id, uint8_t security_min,
    uint8_t override_min) {
    mac_security_level_descr_t ret;
    ret.security_min = security_min;
    ret.override_min = override_min;
    ret.frame_type = frame_type;
    ret.cmd_frame_id = cmd_frame_id;
    return ret;
}

mac_key_id_lookup_descr_t gen_mac_key_id_lookup_descr_t(
    mac_key_lookup_size_t size) {
    mac_key_id_lookup_descr_t ret;
    ret.size = size;
    return ret;
}

mac_key_device_descr_t gen_mac_key_device_descr_t(uint8_t device_handle,
                                                  uint8_t unique_device,
                                                  uint8_t blacklisted) {
    mac_key_device_descr_t ret;
    ret.device_handle = device_handle;
    ret.blacklisted = blacklisted;
    ret.unique_device = unique_device;
    return ret;
}

mac_key_usage_descr_t gen_mac_key_usage_descr_t(uint8_t frame_type,
                                                uint8_t cmd_frame_id) {
    mac_key_usage_descr_t ret;
    ret.frame_type = frame_type;
    ret.cmd_frame_id = cmd_frame_id;
    return ret;
}

pib_id_t gen_pib_id_t_plme(plme_pib_attr_id_t id) {
    pib_id_t ret;
    ret.plme_id = id;
    return ret;
}

pib_id_t gen_pib_id_t(mlme_pib_attr_id_t id) {
    pib_id_t ret;
    ret.mlme_id = id;
    return ret;
}

const char* pib_id_str(pib_id_t id) {
    switch(id.mlme_id) {
        case MAC_PAN_ID:
            return "MAC_PAN_ID";
        case MAC_PAN_COORD_EXTENDED_ADDRESS:
            return "MAC_PAN_COORD_EXTENDED_ADDRESS";
        case MAC_PROMISCUOUS_MODE:
            return "MAC_PROMISCUOUS_MODE";
        case MAC_EXTENDED_ADDRESS:
            return "MAC_EXTENDED_ADDRESS";
        case MAC_SHORT_ADDRESS:
            return "MAC_SHORT_ADDRESS";
        case MAC_BEACON_PAYLOAD_LENGTH:
            return "MAC_BEACON_PAYLOAD_LENGTH";
        case MAC_BEACON_PAYLOAD:
            return "MAC_BEACON_PAYLOAD";
        case MAC_BEACON_ORDER:
            return "MAC_BEACON_ORDER";
        case MAC_RX_ON_WHEN_IDLE:
            return "MAC_RX_ON_WHEN_IDLE";
        case MAC_BATT_LIFE_EXT:
            return "MAC_BATT_LIFE_EXT";
        case MAC_IS_PAN_COORD:
            return "MAC_IS_PAN_COORD";
        case MAC_ASSOCIATION_PERMIT:
            return "MAC_ASSOCIATION_PERMIT";
        case MAC_GTS_PERMIT:
            return "MAC_GTS_PERMIT";
        default:
            return "**************UNKNOWN****************";
    }
}

void print_pib_get_request(const uint8_t* value, uint16_t size,
                           const char* name) {
    SEGGER_RTT_printf(0, "mlme_get: %s: 0x", name);
    for (uint16_t i = 0; i < size; i++) {
        SEGGER_RTT_printf(0, "%02x", value[i]);
    }
    SEGGER_RTT_printf(0, "\n");
}

const uint8_t* pib_get_request(pib_id_t id) {
    static uint8_t value[256];
    mac_status_t res = mlme_get(id, 0, value);
    const char* name = pib_id_str(id);
    if (res != MAC_SUCCESS) {
        SEGGER_RTT_printf(0, "failure in pib-get request(%s): ", name);
        print_mac_status(res);
        SEGGER_RTT_printf(0, "\n");
        // TODO: handle this error
        return NULL;
    }
    print_pib_get_request(value, mlme_pib_attr_size_calc(id, 0), name);
    return value;
}

void pib_set_request(pib_id_t id, void * data) {
    mac_status_t state = mlme_set(id, 0, data);
    if (state != MAC_SUCCESS) {
        SEGGER_RTT_printf(0, "failure in pib-set request(%s): ", pib_id_str(id));
        print_mac_status(state);
        SEGGER_RTT_printf(0, "\n");
        //TODO: handle this error
    }
}

const uint8_t* pib_set_get_request(pib_id_t id, void * data) {
    pib_set_request(id, data);
    return pib_get_request(id);
}

void print_beacon_notify_ind(const mlme_beacon_notify_ind_t* ind) {
    SEGGER_RTT_printf(0, "{bsn: %u, pan_descriptor: ", ind->bsn);
    print_pan_desc(&(ind->pan_descriptor));
    SEGGER_RTT_printf(0, ", sdu_length: %u, sdu: 0x", ind->sdu_length);
    for (size_t i = 0u; i < ind->sdu_length; i++) {
        SEGGER_RTT_printf(0, "%02x", ind->sdu.p_payload[i]);
    }
    SEGGER_RTT_printf(0, ", pend_addr_spec: %x}\n", ind->pend_addr_spec);
}

void print_data_ind(const mcps_data_ind_t* ind) {
    SEGGER_RTT_printf(0, "{src_pan_id: 0x%04x, ", ind->src_pan_id);
    print_addr(&(ind->src_addr), ind->src_addr_mode, "src_addr");
    SEGGER_RTT_printf(0, ", dst_pan_id: 0x%04x, ", ind->dst_pan_id);
    print_addr(&(ind->dst_addr), ind->dst_addr_mode, "dst_addr");
    SEGGER_RTT_printf(0, ", msdu_length: %u, sdu: 0x", ind->msdu_length);
    for (size_t i = 0u; i < ind->msdu_length; i++) {
        SEGGER_RTT_printf(0, "%02x", ind->msdu.p_payload[i]);
    }
    SEGGER_RTT_printf(0, ", mpdu_link_quality: %u", ind->mpdu_link_quality);
    SEGGER_RTT_printf(0, ", dsn: %u", ind->dsn);
    SEGGER_RTT_printf(0, ", timestamp: %u}\n", ind->timestamp);
    SEGGER_RTT_printf(0, "\n");
}

void mlme_beacon_notify_ind(mlme_beacon_notify_ind_t* _ind) {
    SEGGER_RTT_printf(0, "Received Beacon:\n");
    print_beacon_notify_ind(_ind);
}

void mlme_poll_ind(mlme_poll_ind_t* _ind) {
    SEGGER_RTT_printf(0, "Received Poll:\n");
}

//void mac_auto_request_notify_ind(mac_beacon_ind_t* _ind) {
//    SEGGER_RTT_printf(0, "Received Auto Request Notif:\n");
//}
//
//void mac_panid_conflict_beacon_notify_ind(const mac_beacon_ind_t * p_beacon) {
//    SEGGER_RTT_printf(0, "Received Panid Conflict beacon:\n");
//}

void mcps_data_ind(mcps_data_ind_t* _ind) {
    SEGGER_RTT_printf(0, "Received Data:\n");
    print_data_ind(_ind);
}

void mlme_associate_ind(mlme_associate_ind_t* _ind) {
    SEGGER_RTT_printf(0, "Received Association:\n");
}

#define UINT16_TO_ARR(value) {((uint8_t)(value>>8u)), ((uint8_t)(0xffu & value))}

int main(void) {
    ral_irq_handler_import();

    SEGGER_RTT_Init();
    uint8_t rtt_buf_out[256u];
    SEGGER_RTT_ConfigUpBuffer(0, "OUT", rtt_buf_out, 256u,
                              SEGGER_RTT_MODE_NO_BLOCK_TRIM);

    app_task_init();
    sys_task_post(APP_TASK_ID);

    clock_init();

    SEGGER_RTT_printf(0, "....RESET COORD....\n");
#if (CONFIG_SECURE == 1)
    SEGGER_RTT_printf(0, "Compiled with CONFIG_SECURE\n");
#endif

    static uint8_t mac_association_permit = true;

#define BEACON_ORDER 0xfu        //nonbeacon-enabled   // see 7.5.1.1, page 168
#define SUPERFRAME_ORDER 0xfu    //should be set to 15 // see 7.5.1.1, page 168
    static uint8_t mac_gts_permit = false;              // see 7.5.1.1, page 168
    static uint8_t rx_on_when_idle = true;              // see table 86, page 165

    static uint8_t beacon_payload[] = {0xabu, 0xabu, 0xabu, 0xabu, 0xbbu, 0xbbu, 0xbbu, 0xbbu};
    static uint8_t beacon_payload_length = 8u;
    static const uint8_t ext_addr[] = {0xcau, 0xfeu, 0xbeu, 0xefu, 0x00u, 0x00u, 0xffu, 0xffu};

    static uint16_t ext_addr_only = MAC_EXTENDED_ADDRESS_ONLY;

    //TODO: according to page 174, If a coordinator of a nonbeacon-enabled PAN receives this command, it shall transmit a single beacon frame using unslotted CSMA-CA. => How to do this?

    pib_set_get_request(gen_pib_id_t(MAC_EXTENDED_ADDRESS), (void *)ext_addr);
    pib_set_get_request(gen_pib_id_t(MAC_SHORT_ADDRESS), &ext_addr_only);
    pib_set_get_request(gen_pib_id_t(MAC_BEACON_PAYLOAD_LENGTH), &beacon_payload_length);
    pib_set_get_request(gen_pib_id_t(MAC_BEACON_PAYLOAD), (void*)beacon_payload);
    pib_set_get_request(gen_pib_id_t(MAC_RX_ON_WHEN_IDLE), &rx_on_when_idle);
    pib_set_get_request(gen_pib_id_t(MAC_ASSOCIATION_PERMIT), &mac_association_permit);
    pib_get_request(gen_pib_id_t(MAC_GTS_PERMIT));
    pib_set_get_request(gen_pib_id_t(MAC_GTS_PERMIT), &mac_gts_permit);

    static mlme_start_req_t start_req =
	{
			.pan_id  = 0x1234,
			.logical_channel = 14,
			.start_time = 0x000000,
			.beacon_order = BEACON_ORDER,
			.superframe_order = SUPERFRAME_ORDER,
			.pan_coordinator = true,
			.battery_life_extension = false,
			.coord_realignment = false,
#if (CONFIG_SECURE == 1)
            .coord_realign_security_level = false,
            .beacon_security_level = false,
#endif
	};

    callback_called = false;
	mlme_start_req(&start_req, start_callback);
    while (!callback_called) {
        sys_task_run();
    }

    SEGGER_RTT_printf(0, "Sent the Start-Request: ");
    print_mac_status(start_req.confirm.status);
    SEGGER_RTT_printf(0, "\n");

    pib_get_request(gen_pib_id_t(MAC_IS_PAN_COORD));
    pib_get_request(gen_pib_id_t(MAC_PAN_ID));
    pib_get_request(gen_pib_id_t(MAC_SUPERFRAME_ORDER));
    pib_get_request(gen_pib_id_t(MAC_BEACON_ORDER));
    pib_get_request(gen_pib_id_t(MAC_RX_ON_WHEN_IDLE));
    pib_get_request(gen_pib_id_t(MAC_GTS_PERMIT));

    while (true) {
        sys_task_run();
    }
}
