#include "d51_util.h"

static volatile uint32_t w;

// Display unsigned 32-bit number by port toggling DBG_1 (to view on a scope)
// Read as follows: 1230 = |    | |    | | |    ||  (note zero is fast double toggle)
#define DBG_PAUSE 5
void dbg_print(uint32_t x) {
    int8_t   t;
    uint32_t n;
    uint32_t p, p2;

    if (x < 10)
        t = 0;
    else if (x < 100)
        t = 1;
    else if (x < 1000)
        t = 2;
    else if (x < 10000)
        t = 3;
    else if (x < 100000)
        t = 4;
    else if (x < 1000000)
        t = 5;
    else if (x < 10000000)
        t = 6;
    else if (x < 100000000)
        t = 7;
    else if (x < 1000000000)
        t = 8;
    else
        t = 9;

    while (t >= 0) {
        p2 = t;
        p  = 1;
        while (p2--) p *= 10;
        n = x / p;
        x -= n * p;
        if (!n) {
            DBG_1_ON;
            DBG_1_OFF;
            DBG_1_ON;
            DBG_1_OFF;
            n--;
        } else {
            while (n > 0) {
                DBG_1_ON;
                DBG_1_OFF;
                n--;
            }
        }

        t--;
    }

    for (w = DBG_PAUSE; w; w--)
        ;  // Long pause after number is complete
}

// Display unsigned 32-bit number through debug led
// Read as follows: 1230 = [*]  [* *]  [* * *]  [**]  (note zero is fast double flash)
#define DLED_ONTIME 1000000
#define DLED_PAUSE 1500000
void dled_print(uint32_t x, uint8_t long_pause) {
    int8_t   t;
    uint32_t n;
    uint32_t p, p2;

    if (x < 10)
        t = 0;
    else if (x < 100)
        t = 1;
    else if (x < 1000)
        t = 2;
    else if (x < 10000)
        t = 3;
    else if (x < 100000)
        t = 4;
    else if (x < 1000000)
        t = 5;
    else if (x < 10000000)
        t = 6;
    else if (x < 100000000)
        t = 7;
    else if (x < 1000000000)
        t = 8;
    else
        t = 9;

    while (t >= 0) {
        p2 = t;
        p  = 1;
        while (p2--) p *= 10;
        n = x / p;
        x -= n * p;
        if (!n) {
            DBG_LED_ON;
            for (w = DLED_ONTIME / 4; w; w--)
                ;
            DBG_LED_OFF;
            for (w = DLED_ONTIME / 4; w; w--)
                ;
            DBG_LED_ON;
            for (w = DLED_ONTIME / 4; w; w--)
                ;
            DBG_LED_OFF;
            for (w = DLED_ONTIME / 4; w; w--)
                ;
            n--;
        } else {
            while (n > 0) {
                DBG_LED_ON;
                for (w = DLED_ONTIME; w; w--)
                    ;
                DBG_LED_OFF;
                for (w = DLED_ONTIME / 2; w; w--)
                    ;
                n--;
            }
        }

        for (w = DLED_PAUSE; w; w--)
            ;
        t--;
    }

    if (long_pause) {
        for (w = DLED_PAUSE * 4; w; w--)
            ;
    }
}

#ifdef DEBUG_BOOT_TRACING_ENABLE

volatile uint32_t debug_code;

// These macros are for compile time substitution
#    define DEBUG_BOOT_TRACING_EXTINTn (DEBUG_BOOT_TRACING_PIN % _U_(0x10))
#    define DEBUG_BOOT_TRACING_EXTINTb (_U_(0x1) << DEBUG_BOOT_TRACING_EXTINTn)
#    define DEBUG_BOOT_TRACING_CONFIG_INDn (DEBUG_BOOT_TRACING_EXTINTn / _U_(0x8))
#    define DEBUG_BOOT_TRACING_CONFIG_SENSEn (DEBUG_BOOT_TRACING_EXTINTn % _U_(0x8))
#    define DEBUG_BOOT_TRACING_CONFIG_SENSEb (DEBUG_BOOT_TRACING_CONFIG_SENSEn * _U_(0x4))
#    define DEBUG_BOOT_TRACING_IRQn (EIC_0_IRQn + DEBUG_BOOT_TRACING_EXTINTn)

// These macros perform PORT+PIN definition translation to IRQn in the preprocessor
#    define PORTPIN_TO_IRQn_EXPAND(def) def
#    define PORTPIN_TO_IRQn_DEF(def) PORTPIN_TO_IRQn_EXPAND(def)
#    if DEBUG_BOOT_TRACING_PIN < 10
#        define PORTPIN_TO_IRQn_TODEF(port, pin) PORTPIN_TO_IRQn_DEF(PIN_##port##0##pin##A_EIC_EXTINT_NUM)
#    else
#        define PORTPIN_TO_IRQn_TODEF(port, pin) PORTPIN_TO_IRQn_DEF(PIN_##port##pin##A_EIC_EXTINT_NUM)
#    endif
#    define PORTPIN_TO_IRQn(port, pin) PORTPIN_TO_IRQn_TODEF(port, pin)

// These macros perform function name output in the preprocessor
#    define DEBUG_BOOT_TRACING_HANDLER_CONCAT(irq) void EIC_##irq##_Handler(void)
#    define DEBUG_BOOT_TRACING_HANDLER(irq) DEBUG_BOOT_TRACING_HANDLER_CONCAT(irq)

// To generate the function name of the IRQ handler catching boot tracing,
//  certain macros must be undefined, so save their current values to macro stack
#    pragma push_macro("PA")
#    pragma push_macro("PB")
#    pragma push_macro("_L_")

// Undefine / redefine pushed macros
#    undef PA
#    undef PB
#    undef _L_
#    define _L_(x) x

// Perform the work and output
// Ex: PORT PB, PIN 31 = void EIC_15_Handler(void)
DEBUG_BOOT_TRACING_HANDLER(PORTPIN_TO_IRQn(DEBUG_BOOT_TRACING_PORT, DEBUG_BOOT_TRACING_PIN))
// Restore macros
#    pragma pop_macro("PA")
#    pragma pop_macro("PB")
#    pragma pop_macro("_L_")
{
    // This is only for non-functional keyboard troubleshooting and should be disabled after boot
    // Intention is to lock up the keyboard here with repeating debug led code
    while (1) {
        dled_print(debug_code, 1);
    }
}

void debug_code_init(void) {
    DBGC(DC_UNSET);

    // Configure Ports for EIC
    PORT->Group[DEBUG_BOOT_TRACING_PORT].DIRCLR.reg                                 = 1 << DEBUG_BOOT_TRACING_PIN;  // Input
    PORT->Group[DEBUG_BOOT_TRACING_PORT].OUTSET.reg                                 = 1 << DEBUG_BOOT_TRACING_PIN;  // High
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.INEN    = 1;                            // Input Enable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.PULLEN  = 1;                            // Pull Enable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.PMUXEN  = 1;                            // Mux Enable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PMUX[DEBUG_BOOT_TRACING_PIN / 2].bit.PMUXO = 0;                            // Mux A

    // Enable CLK_EIC_APB
    MCLK->APBAMASK.bit.EIC_ = 1;

    // Configure EIC
    EIC->CTRLA.bit.SWRST = 1;
    while (EIC->SYNCBUSY.bit.SWRST) {
    }
    EIC->ASYNCH.reg   = DEBUG_BOOT_TRACING_EXTINTb;
    EIC->INTENSET.reg = DEBUG_BOOT_TRACING_EXTINTb;
    EIC->CONFIG[DEBUG_BOOT_TRACING_CONFIG_INDn].reg |= (EIC_CONFIG_SENSE0_FALL_Val << DEBUG_BOOT_TRACING_CONFIG_SENSEb);
    EIC->CTRLA.bit.ENABLE = 1;
    while (EIC->SYNCBUSY.bit.ENABLE) {
    }

    // Enable EIC IRQ
    NVIC_EnableIRQ(DEBUG_BOOT_TRACING_IRQn);
}

void debug_code_disable(void) {
    // Disable EIC IRQ
    NVIC_DisableIRQ(DEBUG_BOOT_TRACING_IRQn);

    // Disable EIC
    EIC->CTRLA.bit.ENABLE = 0;
    while (EIC->SYNCBUSY.bit.ENABLE) {
    }

    // Default port configuration
    PORT->Group[DEBUG_BOOT_TRACING_PORT].DIRCLR.reg                                 = 1 << DEBUG_BOOT_TRACING_PIN;  // Input
    PORT->Group[DEBUG_BOOT_TRACING_PORT].OUTCLR.reg                                 = 1 << DEBUG_BOOT_TRACING_PIN;  // Low
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.INEN    = 0;                            // Input Disable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.PULLEN  = 0;                            // Pull Disable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PINCFG[DEBUG_BOOT_TRACING_PIN].bit.PMUXEN  = 0;                            // Mux Disable
    PORT->Group[DEBUG_BOOT_TRACING_PORT].PMUX[DEBUG_BOOT_TRACING_PIN / 2].bit.PMUXO = 0;                            // Mux A

    // Disable CLK_EIC_APB
    MCLK->APBAMASK.bit.EIC_ = 0;
}

#else

void debug_code_init(void) {}
void debug_code_disable(void) {}

#endif  // DEBUG_BOOT_TRACING_ENABLE