/*
 * This file is part of Adafruit for EFR32 project
 *
 * The MIT License (MIT)
 *
 * Copyright 2023 Silicon Laboratories Inc. www.silabs.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "supervisor/background_callback.h"
#include "supervisor/board.h"
#include "supervisor/port.h"
#include "shared/timeutils/timeutils.h"

#include "common-hal/microcontroller/Pin.h"
#include "shared-bindings/microcontroller/__init__.h"

#if CIRCUITPY_AUDIOPWMIO
#include "common-hal/audiopwmio/PWMAudioOut.h"
#endif
#if CIRCUITPY_BUSIO
#include "common-hal/busio/I2C.h"
#include "common-hal/busio/SPI.h"
#include "common-hal/busio/UART.h"
#endif
#if CIRCUITPY_PULSEIO
#include "common-hal/pulseio/PulseOut.h"
#include "common-hal/pulseio/PulseIn.h"
#endif
#if CIRCUITPY_PWMIO
#include "common-hal/pwmio/PWMOut.h"
#endif
#if CIRCUITPY_PULSEIO || CIRCUITPY_PWMIO
#include "peripherals/timers.h"
#endif
#if CIRCUITPY_SDIOIO
#include "common-hal/sdioio/SDCard.h"
#endif
#if CIRCUITPY_PULSEIO || CIRCUITPY_ALARM
#include "peripherals/exti.h"
#endif
#if CIRCUITPY_ALARM
#include "common-hal/alarm/__init__.h"
#endif
#if CIRCUITPY_RTC
#include "shared-bindings/rtc/__init__.h"
#endif
#if CIRCUITPY_ANALOGIO
#include "common-hal/analogio/AnalogOut.h"
#endif

#if CIRCUITPY_BLEIO
#include "common-hal/_bleio/__init__.h"
#endif

// Include headers of EFR32
#include <cmsis_os2.h>
#include "em_chip.h"
#include "sl_cmsis_os2_common.h"
#include "sl_component_catalog.h"
#include "sl_sleeptimer.h"
#include "sl_system_init.h"
#include "sl_system_kernel.h"

#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
#include "sl_power_manager.h"
#endif // SL_CATALOG_POWER_MANAGER_PRESENT

#if !defined(SL_CATALOG_KERNEL_PRESENT)
#error "Error: Requires SL_CATALOG_KERNEL_PRESENT definition"
#endif

#define SL_CIRCUITPYTHON_TASK_STACK_EXTRA_SIZE (32)
#define SL_CIRCUITPYTHON_TASK_PRIORITY (40)
#define HEAP_SIZE (88 * 1024)

extern uint32_t __bss_start__;
extern uint32_t __bss_end__;

uint32_t _sbss;
uint32_t _ebss;

uint32_t *heap;
uint32_t heap_size;

STATIC sl_sleeptimer_timer_handle_t _tick_timer;
STATIC sl_sleeptimer_timer_handle_t _sleep_timer;

// CircuitPython stack thread
STATIC void circuitpython_thread(void *p_arg);
STATIC osThreadId_t tid_thread_circuitpython;
__ALIGNED(8)
STATIC uint8_t thread_circuitpython_stk[(CIRCUITPY_DEFAULT_STACK_SIZE +
    SL_CIRCUITPYTHON_TASK_STACK_EXTRA_SIZE) &
                                        0xFFFFFFF8u];
__ALIGNED(4)
STATIC uint8_t thread_circuitpython_cb[osThreadCbSize];

STATIC const osThreadAttr_t thread_circuitpython_attr = {
    .name = "CircuitPython stack",
    .stack_mem = thread_circuitpython_stk,
    .stack_size = sizeof(thread_circuitpython_stk),
    .cb_mem = thread_circuitpython_cb,
    .cb_size = osThreadCbSize,
    .priority = (osPriority_t)SL_CIRCUITPYTHON_TASK_PRIORITY
};

STATIC bool isSchedulerStarted = false;

safe_mode_t port_init(void) {
    #if defined(SL_CATALOG_KERNEL_PRESENT)

    if (!isSchedulerStarted) {
        _sbss = __bss_start__;
        _ebss = __bss_end__;

        isSchedulerStarted = true;
        // Initialize Silicon Labs device, system, service(s) and protocol stack(s).
        // Note that if the kernel is present, processing task(s) will be created by
        // this call.
        sl_system_init();
        // Create thread for Bluetooth stack
        if (tid_thread_circuitpython == NULL) {
            tid_thread_circuitpython = osThreadNew(circuitpython_thread,
                NULL,
                &thread_circuitpython_attr);
        }

        // Create mutex for Bluetooth handle
        if (bluetooth_connection_mutex_id == NULL) {
            bluetooth_connection_mutex_id = osMutexNew(&bluetooth_connection_mutex_attr);
        }

        if (tid_thread_circuitpython == NULL) {
            for (;;) {
            }
        }
        // Start the kernel. Task(s) created in app_init() will start running.
        sl_system_kernel_start();
    }

    #endif // SL_CATALOG_KERNEL_PRESENT

    if (heap == NULL) {
        heap = malloc(HEAP_SIZE);
        heap_size = HEAP_SIZE / sizeof(uint32_t);
    }
    if (heap == NULL) {
        return SAFE_MODE_NO_HEAP;
    }
    return SAFE_MODE_NONE;
}

void reset_port(void) {
    reset_all_pins();

    #if CIRCUITPY_BUSIO
    i2c_reset();
    spi_reset();
    uart_reset();
    #endif

    #if CIRCUITPY_PWMIO
    pwmout_reset();
    #endif

    #if CIRCUITPY_ANALOGIO
    analogout_reset();
    #endif

    #if CIRCUITPY_BLEIO
    bleio_reset();
    #endif

    #if CIRCUITPY_RTC
    rtc_reset();
    #endif

    #if CIRCUITPY_WATCHDOG
    watchdog_reset();
    #endif
}

void reset_to_bootloader(void) {
    CHIP_Reset();
    for (;;) {
    }
}

void reset_cpu(void) {
    CHIP_Reset();
    for (;;) {
    }
}

uint32_t *port_heap_get_bottom(void) {
    return heap;
}

uint32_t *port_heap_get_top(void) {
    return heap + heap_size;
}

bool port_has_fixed_stack(void) {
    return true;
}

uint32_t *port_stack_get_limit(void) {
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wcast-align"
    return (uint32_t *)thread_circuitpython_stk;
    #pragma GCC diagnostic pop
}

uint32_t *port_stack_get_top(void) {
    return port_stack_get_limit() + CIRCUITPY_DEFAULT_STACK_SIZE / sizeof(uint32_t);
}

uint64_t port_get_raw_ticks(uint8_t *subticks) {
    uint32_t timer_freq = sl_sleeptimer_get_timer_frequency();
    uint64_t all_subticks = (uint64_t)(sl_sleeptimer_get_tick_count()) * 1024;
    if (subticks != NULL) {
        *subticks = all_subticks % timer_freq;
    }
    return all_subticks / timer_freq;
}

// Periodic tick timer callback
STATIC void on_tick_timer_timeout(sl_sleeptimer_timer_handle_t *handle,
    void *data) {
    (void)&handle;
    (void)&data;
    supervisor_tick();

    // CircuitPython's VM is run in a separate FreeRTOS task from timer callbacks.
    // So, we have to notify the main task every time in case it's waiting for us.
    osThreadFlagsSet(tid_thread_circuitpython, 0x0001);
}

// Enable 1/1024 second tick.
void port_enable_tick(void) {
    uint32_t timer_freq = sl_sleeptimer_get_timer_frequency();

    // Create timer for waking up the system periodically.
    sl_sleeptimer_start_periodic_timer(&_tick_timer,
        timer_freq / 1024,
        on_tick_timer_timeout, NULL,
        0,
        SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}

// Disable 1/1024 second tick.
void port_disable_tick(void) {
    sl_sleeptimer_stop_timer(&_tick_timer);
}

void port_wake_main_task(void) {
    osThreadFlagsSet(tid_thread_circuitpython, 0x0001);
}

STATIC void on_sleep_timer_timeout(sl_sleeptimer_timer_handle_t *handle,
    void *data) {
    port_wake_main_task();
}

void port_interrupt_after_ticks(uint32_t ticks) {
    uint32_t timer_freq = sl_sleeptimer_get_timer_frequency();

    uint32_t timer_tick = (uint32_t)((((uint64_t)ticks * timer_freq) + 1023) / 1024u);

    // Create one-shot timer for waking up the system.
    sl_sleeptimer_start_timer(&_sleep_timer,
        timer_tick,
        on_sleep_timer_timeout, NULL,
        0,
        SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}

void port_idle_until_interrupt(void) {
    if (!background_callback_pending()) {
        osThreadFlagsWait(0x0001, osFlagsWaitAny, osWaitForever);
    }

}

// Place the word to save just after our BSS section that gets blanked.
void port_set_saved_word(uint32_t value) {
    __bss_end__ = value;
}

uint32_t port_get_saved_word(void) {
    return __bss_end__;
}

#if CIRCUITPY_ALARM
//  Board deinit in case boards/xxx/board.c does not provide board_deinit()
MP_WEAK void board_deinit(void) {
}
#endif

extern void main(void);

void circuitpython_thread(void *p_arg) {
    (void)p_arg;
    main();
}

__attribute__((used)) void BusFault_Handler(void) {
    reset_into_safe_mode(SAFE_MODE_HARD_FAULT);
    while (true) {
        asm ("nop;");
    }
}

__attribute__((used)) void UsageFault_Handler(void) {
    reset_into_safe_mode(SAFE_MODE_HARD_FAULT);
    while (true) {
        asm ("nop;");
    }
}

__attribute__((used)) void HardFault_Handler(void) {
    reset_into_safe_mode(SAFE_MODE_HARD_FAULT);
    while (true) {
        asm ("nop;");
    }
}
