/* Copyright (c) 2024 Skyward Experimental Rocketry
 * Authors: Davide Mor, Emilio Corigliano
 *
 * 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 <interfaces-impl/bsp_impl.h>
#include <miosix.h>

#include <array>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <thread>

using namespace miosix;

struct PinDef
{
    GpioPin pin;
    const char* gpio_name;
    const char* conan_name;
    // ADRIANOOOOOOOOOOOOOOOO
    const char* breakout_v1_name;
};

std::array<PinDef, 53> PIN_DEFS{{
    {Gpio<GPIOA_BASE, 0>::getPin(), "PA0", "PIN57", "PIN13"},
    {Gpio<GPIOA_BASE, 1>::getPin(), "PA1", "PIN55", "PIN15"},
    {Gpio<GPIOA_BASE, 2>::getPin(), "PA2", "PIN62", "PIN8"},
    {Gpio<GPIOA_BASE, 3>::getPin(), "PA3", "PIN60", "PIN10"},
    {Gpio<GPIOA_BASE, 4>::getPin(), "PA4", "PIN58", "PIN12"},
    {Gpio<GPIOA_BASE, 5>::getPin(), "PA5", "PIN56", "PIN14"},
    {Gpio<GPIOA_BASE, 6>::getPin(), "PA6", "PIN54", "PIN16"},
    {Gpio<GPIOA_BASE, 7>::getPin(), "PA7", "PIN52", "PIN18"},
    {Gpio<GPIOA_BASE, 8>::getPin(), "PA8", "PIN33", "PIN37"},
    // {Gpio<GPIOA_BASE, 9>::getPin(), "PA9", "PIN36", "PIN"}, Miosix serial
    // {Gpio<GPIOA_BASE, 10>::getPin(), "PA10", "PIN34", "PIN"}, Miosix serial
    {Gpio<GPIOA_BASE, 11>::getPin(), "PA11", "PIN27", "PIN43"},
    {Gpio<GPIOA_BASE, 12>::getPin(), "PA12", "PIN30", "PIN40"},
    // {Gpio<GPIOA_BASE, 13>::getPin(), "PA13", NULL, NULL}, SWDIO
    // {Gpio<GPIOA_BASE, 14>::getPin(), "PA14", NULL, NULL}, SWCLK
    {Gpio<GPIOA_BASE, 15>::getPin(), "PA15", "PIN32", "PIN38"},
    {Gpio<GPIOB_BASE, 0>::getPin(), "PB0", "PIN53", "PIN17"},
    {Gpio<GPIOB_BASE, 1>::getPin(), "PB1", "PIN51", "PIN19"},
    {Gpio<GPIOB_BASE, 2>::getPin(), "PB2", "PIN65", "PIN5"},
    {Gpio<GPIOB_BASE, 3>::getPin(), "PB3", "PIN15", "PIN55"},
    {Gpio<GPIOB_BASE, 4>::getPin(), "PB4", "PIN17", "PIN53"},
    // {Gpio<GPIOB_BASE, 5>::getPin(), "PB5", NULL, NULL}, RAM
    // {Gpio<GPIOB_BASE, 6>::getPin(), "PB6", NULL, NULL}, RAM
    {Gpio<GPIOB_BASE, 7>::getPin(), "PB7", "PIN14", "PIN56"},
    {Gpio<GPIOB_BASE, 8>::getPin(), "PB8", "PIN13", "PIN57"},
    {Gpio<GPIOB_BASE, 9>::getPin(), "PB9", "PIN11", "PIN59"},
    // {Gpio<GPIOB_BASE, 10>::getPin(), "PB10", NULL}, Flash
    {Gpio<GPIOB_BASE, 11>::getPin(), "PB11", "PIN49", "PIN21"},
    {Gpio<GPIOB_BASE, 12>::getPin(), "PB12", "PIN47", "PIN23"},
    {Gpio<GPIOB_BASE, 13>::getPin(), "PB13", "PIN45", "PIN25"},
    {Gpio<GPIOB_BASE, 14>::getPin(), "PB14", "PIN43", "PIN27"},
    {Gpio<GPIOB_BASE, 15>::getPin(), "PB15", "PIN46", "PIN24"},
    // {Gpio<GPIOC_BASE, 0>::getPin(), "PC0", NULL, NULL}, RAM
    {Gpio<GPIOC_BASE, 1>::getPin(), "PC1", "PIN63", "PIN7"},
    // {Gpio<GPIOC_BASE, 2>::getPin(), "PC2", NULL, NULL}, LED
    {Gpio<GPIOC_BASE, 3>::getPin(), "PC3", "PIN61", "PIN9"},
    {Gpio<GPIOC_BASE, 4>::getPin(), "PC4", "PIN50", "PIN20"},
    {Gpio<GPIOC_BASE, 5>::getPin(), "PC5", "PIN48", "PIN22"},
    {Gpio<GPIOC_BASE, 6>::getPin(), "PC6", "PIN37", "PIN33"},
    {Gpio<GPIOC_BASE, 7>::getPin(), "PC7", "PIN35", "PIN35"},
    // {Gpio<GPIOC_BASE, 8>::getPin(), "PC8", NULL, NULL}, SD Card
    // {Gpio<GPIOC_BASE, 9>::getPin(), "PC9", NULL, NULL}, SD Card
    // {Gpio<GPIOC_BASE, 10>::getPin(), "PC10", NULL, NULL}, SD Card
    // {Gpio<GPIOC_BASE, 11>::getPin(), "PC11", NULL, NULL}, SD Card
    // {Gpio<GPIOC_BASE, 12>::getPin(), "PC12", NULL, NULL}, SD Card
    // {Gpio<GPIOC_BASE, 13>::getPin(), "PC13", NULL, NULL}, LED
    // {Gpio<GPIOC_BASE, 14>::getPin(), "PC14", NULL, NULL}, LED
    // {Gpio<GPIOC_BASE, 15>::getPin(), "PC15", NULL, NULL}, LED
    // {Gpio<GPIOD_BASE, 0>::getPin(), "PD0", NULL, NULL}, RAM
    // {Gpio<GPIOD_BASE, 1>::getPin(), "PD1", NULL, NULL}, RAM
    // {Gpio<GPIOD_BASE, 2>::getPin(), "PD2", NULL, NULL}, SD Card
    {Gpio<GPIOD_BASE, 3>::getPin(), "PD3", "PIN28", "PIN42"},
    {Gpio<GPIOD_BASE, 4>::getPin(), "PD4", "PIN26", "PIN44"},
    {Gpio<GPIOD_BASE, 5>::getPin(), "PD5", "PIN22", "PIN48"},
    {Gpio<GPIOD_BASE, 6>::getPin(), "PD6", "PIN24", "PIN46"},
    {Gpio<GPIOD_BASE, 7>::getPin(), "PD7", "PIN23", "PIN47"},
    // {Gpio<GPIOD_BASE, 8>::getPin(), "PD8", NULL, NULL}, RAM
    // {Gpio<GPIOD_BASE, 9>::getPin(), "PD9", NULL, NULL}, RAM
    // {Gpio<GPIOD_BASE, 10>::getPin(), "PD10", NULL, NULL}, RAM
    {Gpio<GPIOD_BASE, 11>::getPin(), "PD11", "PIN44", "PIN26"},
    {Gpio<GPIOD_BASE, 12>::getPin(), "PD12", "PIN41", "PIN29"},
    {Gpio<GPIOD_BASE, 13>::getPin(), "PD13", "PIN39", "PIN31"},
    // {Gpio<GPIOD_BASE, 14>::getPin(), "PD14", NULL, NULL}, RAM
    // {Gpio<GPIOD_BASE, 15>::getPin(), "PD15", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 0>::getPin(), "PE0", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 1>::getPin(), "PE1", NULL, NULL}, RAM
    {Gpio<GPIOE_BASE, 2>::getPin(), "PE2", "PIN7", "PIN63"},
    {Gpio<GPIOE_BASE, 3>::getPin(), "PE3", "PIN12", "PIN58"},
    {Gpio<GPIOE_BASE, 4>::getPin(), "PE4", "PIN9", "PIN61"},
    {Gpio<GPIOE_BASE, 5>::getPin(), "PE5", "PIN5", "PIN65"},
    {Gpio<GPIOE_BASE, 6>::getPin(), "PE6", "PIN3", "PIN67"},
    // {Gpio<GPIOE_BASE, 7>::getPin(), "PE7", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 8>::getPin(), "PE8", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 9>::getPin(), "PE9", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 10>::getPin(), "PE10", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 11>::getPin(), "PE11", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 12>::getPin(), "PE12", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 13>::getPin(), "PE13", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 14>::getPin(), "PE14", NULL, NULL}, RAM
    // {Gpio<GPIOE_BASE, 15>::getPin(), "PE15", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 0>::getPin(), "PF0", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 1>::getPin(), "PF1", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 2>::getPin(), "PF2", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 3>::getPin(), "PF3", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 4>::getPin(), "PF4", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 5>::getPin(), "PF5", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 6>::getPin(), "PF6", NULL, NULL}, Flash
    // {Gpio<GPIOF_BASE, 7>::getPin(), "PF7", NULL, NULL}, Flash
    // {Gpio<GPIOF_BASE, 8>::getPin(), "PF8", NULL, NULL}, Flash
    // {Gpio<GPIOF_BASE, 9>::getPin(), "PF9", NULL, NULL}, Flash
    // {Gpio<GPIOF_BASE, 10>::getPin(), "PF10", NULL, NULL}, Flash
    // {Gpio<GPIOF_BASE, 11>::getPin(), "PF11", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 12>::getPin(), "PF12", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 13>::getPin(), "PF13", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 14>::getPin(), "PF14", NULL, NULL}, RAM
    // {Gpio<GPIOF_BASE, 15>::getPin(), "PF15", NULL, NULL}, RAM
    // {Gpio<GPIOG_BASE, 0>::getPin(), "PG0", NULL, NULL}, RAM
    // {Gpio<GPIOG_BASE, 1>::getPin(), "PG1", NULL, NULL}, RAM
    // {Gpio<GPIOG_BASE, 2>::getPin(), "PG2", NULL, NULL}, RAM
    {Gpio<GPIOG_BASE, 3>::getPin(), "PG3", "PIN42", "PIN28"},
    // {Gpio<GPIOG_BASE, 4>::getPin(), "PG4", NULL}, RAM
    // {Gpio<GPIOG_BASE, 5>::getPin(), "PG5", NULL}, RAM
    {Gpio<GPIOG_BASE, 6>::getPin(), "PG6", "PIN40", "PIN30"},
    {Gpio<GPIOG_BASE, 7>::getPin(), "PG7", "PIN38", "PIN32"},
    // {Gpio<GPIOG_BASE, 8>::getPin(), "PG8", NULL, NULL}, RAM
    {Gpio<GPIOG_BASE, 9>::getPin(), "PG9", "PIN19", "PIN51"},
    {Gpio<GPIOG_BASE, 10>::getPin(), "PG10", "PIN25", "PIN45"},
    {Gpio<GPIOG_BASE, 11>::getPin(), "PG11", "PIN21", "PIN49"},
    {Gpio<GPIOG_BASE, 12>::getPin(), "PG12", "PIN20", "PIN50"},
    {Gpio<GPIOG_BASE, 13>::getPin(), "PG13", "PIN18", "PIN52"},
    {Gpio<GPIOG_BASE, 14>::getPin(), "PG14", "PIN16", "PIN54"},
    // {Gpio<GPIOG_BASE, 15>::getPin(), "PG15", NULL, NULL} RAM
}};

void banner(bool furry)
{
    if (furry)
    {
        puts(R"(
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠀⠀⠀⠀⠀⠀⠀⢀⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢶⣿⢃⣠⣴⣶⣶⣿⣛⢯⣹⣾⠁⠀⠀⠀⠀⠀⢀⣀⣤⣴⣶⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣻⣾⣿⣞⣯⣷⣽⣾⣿⣁⣀⣠⢤⡶⣶⠿⣻⣶⣟⡿⣿⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣟⣯⣿⣶⣯⣿⣿⣿⡁⠀⣴⠿⣟⣿⣟⣿⣻⢷⢯⣞⡏⠀⠀
⠀⠀⠀⢠⡶⢒⣖⣲⡶⢶⡖⣶⣒⢶⡲⣞⣫⠭⣭⣭⣽⣭⣯⣽⣿⣿⣿⣿⣿⣿⣿⠿⢿⡿⠿⠿⠿⣌⠿⢿⣿⣿⣿⣿⡾⣽⢯⣟⣿⠃⠀⠀
⠀⠀⠀⠀⢻⣯⣛⢧⣛⢧⣛⡶⣹⣎⢷⣝⡮⣟⢷⡾⣹⢯⡟⠉⠉⠛⢿⣿⣿⣯⣟⣿⣫⣽⡿⣃⣔⣬⣿⣷⣯⣿⣿⣿⣿⣽⣻⣾⡏⠀⠀⠀
⠀⠀⠀⠀⠀⠹⣟⡾⣭⡟⣾⣹⢗⣾⣫⢾⡽⣽⢾⡽⢯⡟⠀⠀⠀⢀⢈⣿⣿⣿⣿⣿⠿⢻⣷⣼⣿⣿⣟⣿⣟⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠙⣿⣷⣿⣷⣯⣟⡾⣽⣳⢿⡽⣾⡽⡟⠀⠀⢀⣰⠞⠟⠙⢻⣷⣿⣸⣷⣾⣟⣺⣽⣟⣻⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣷⣯⣿⣽⣳⢿⠃⠀⠀⢀⠀⢠⣤⣦⣄⣇⠉⠛⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠻⣿⣿⣿⣿⣿⣉⠉⠉⠻⠀⠀⠠⢀⣾⢿⠋⢁⣿⣿⠀⠀⠀⠀⣿⣧⠙⢿⣹⣏⢠⣛⠟⠁⠀⠀⢀⣴⣷⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⣿⣿⣿⣓⣈⣰⣦⣤⣀⣼⠋⠀⠀⢭⣽⣿⡇⠀⣀⣀⣿⣿⡇⡸⣿⣿⡤⠉⠒⠤⣀⠀⠠⡄⠀⣀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⢹⠏⠀⠛⣞⣧⣾⠍⠢⣀⠀⠈⠻⠟⣇⢉⣡⡶⣭⠯⡀⠐⠛⠙⢇⠀⠐⢲⠏⠀⠀⠈⠲⠞⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠁⢀⠄⠂⢸⠛⠁⣠⣲⡆⢔⠾⠋⠀⠈⠢⣉⠀⢀⣀⣈⡆⠀⠀⢸⣀⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠚⠤⣌⡁⠀⠀⠈⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⡿⠀⠀⣜⣩⠟⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⣛⡉⠀⠀⠓⢄⡀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⡹⠋⠀⣠⠞⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠐⠒⠚⣿⣶⠦⣄⣀⡀⠘⠿⠽⠶⢾⣷⣀⠖⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣀⡀⣀⣀⣠⡿⠍⣛⠻⡿⠿⣟⣲⣶⣶⡶⠿⣿⣖⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠼⠛⢻⠿⠻⢿⣿⣷⣿⣶⣆⠬⡯⣑⣿⣿⣿⣿⣹⣍⣧⣾⠈⠉⠉⠒⠒⠠⣄⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠋⠀⠀⠐⠒⠠⣔⠁⠀⠉⣿⣿⣿⢛⡖⠾⠿⢿⡿⠿⣾⠟⠁⠀⠀⠀⠀⠀⠸⠀⠱⡄⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡅⠀⠀⠀⠀⠀⠀⠀⠉⠒⠄⠘⢿⣿⠇⠈⠁⠒⠺⣤⣢⡼⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠘⣆⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠹⡄⠐⠒⠠⠤⠀⣀⠀⠀⠀⠀⠈⣏⠀⠀⠀⠀⠀⠈⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⢆⠀⠀⠙⣆⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠘⣆⠀⠀⠀⠀⠀⠉⠑⠢⡀⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠈⢆⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀⠀⠀⠈⢣⡀⠠⢄⣀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⠀⠀⠀⢣⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠧⣀⣀⠀⢀⡤⠋⠀⠀⠀⠀⠈⢁⠒⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⢀⣠⢾⡍⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⣻⡿⠏⠀⠀⠀⠐⠢⠄⢀⡀⣰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡷⠖⠋⣁⡼⣇⠀ 
        )");
    }
    else
    {
        puts(R"(
                                                 /
                                            /  /  /
                                        /    /      /
                                     /    /   /  /
                                      /  _=^|  /    /
                                 /  /  <^   |   /
                                     //     | /   /  /
                                 /   /      /
                                   >/      /    /   /
                               /  //      /  /         /
                                 </      /^>_   /  /
                              /  /      /    ^>_     /   /
                                /      /^<_     ^>_    /
                               /      / /  ^<_     ^>_     /
                               ^<_   /        ^<_     ^>_            ___----____
                                  ^</            ^<_     ^>_   _-<^^^
                                                    ^<_     ^<^
                                                       ^<_  /   /
                                                          ^/   /^>^>_  ********
                                                          '<__/  /^> ^>_  ___***
                                                              ^-/  /^>__^>_  ^^^
                                                                ^-/  /  ! /
                                                                  ^-/^_/_/
               _____________._____________________
              /,   O ..    ==   *          O ___  /
             /,         ,@@    @@@@@@@@@@   /()/ /
            === %%%%    """   @@@@@@@@@@   /"'/ /
           ==='%%%%      +   @@@@@@@@@@   /"'/ /
          /%     .   .%  .  @@@@@@@@@@   /"'/ /
         /%     . %..%  . ,  ,  ,  ,    /__/ /
        /____O_______________________O______/
                                      L__V
        )");
    }

    puts(R"(
 Welcome to the
   ________  __   _    _____      ______          __             _ __     
  / ____/ / / /  | |  / /__ \    /_  __/__  _____/ /________  __(_) /____ 
 / /   / / / /   | | / /__/ /     / / / _ \/ ___/ __/ ___/ / / / / __/ _ \
/ /___/ /_/ /    | |/ // __/     / / /  __(__  ) /_(__  ) /_/ / / /_/  __/
\____/\____/     |___//____/    /_/  \___/____/\__/____/\__,_/_/\__/\___/ 
    )");
}

void led_task()
{
    ledOff();

    const unsigned int DELAY = 200;
    while (1)
    {
        led2Off();
        led1On();
        Thread::sleep(DELAY);
        led1Off();
        led2On();
        Thread::sleep(DELAY);
        led2Off();
        led3On();
        Thread::sleep(DELAY);
        led3Off();
        led4On();
        Thread::sleep(DELAY);
        led4Off();
        led3On();
        Thread::sleep(DELAY);
        led3Off();
        led2On();
        Thread::sleep(DELAY);
    }
}

void sd_test()
{
    // Write 1MB of bytes
    const size_t COUNT           = 256 * 1024;
    const uint32_t INITIAL_VALUE = 0xdeadbeef;

    puts("*** Starting SD test...");
    bool ok = true;

    FILE* f = fopen("/sd/test.bin", "wb");
    if (f == NULL)
    {
        puts("Failed to open test.bin");
        ok = false;
    }

    if (ok)
    {
        uint32_t value = INITIAL_VALUE;
        for (size_t i = 0; i < COUNT; i++)
        {
            size_t result = fwrite(&value, sizeof(value), 1, f);
            if (result != 1)
            {
                printf("Failed to write word %zu\n", i);

                // We had a problem
                ok = false;
                break;
            }

            // Update value with xorshift
            value = (value << 8) ^ (value >> 8);
        }
    }

    if (f != NULL)
        fclose(f);

    if (ok)
    {
        // Reopen file
        f = fopen("/sd/test.bin", "rb");
        if (f == NULL)
        {
            puts("Failed to reopen test.bin");
            ok = false;
        }
    }

    if (ok)
    {
        uint32_t value = INITIAL_VALUE;
        for (size_t i = 0; i < COUNT; i++)
        {
            uint32_t actual = 0;
            size_t result   = fread(&actual, sizeof(actual), 1, f);
            if (result != 1)
            {
                printf("Failed to write word %zu\n", i);
                ok = false;
                break;
            }

            if (actual != value)
            {
                // cppcheck-suppress invalidPrintfArgType_uint
                printf(
                    "Failed to validate word %zu, expected: %lu, actual: %lu\n",
                    i, value, actual);
                ok = false;
                break;
            }

            // Update value with xorshift
            value = (value << 8) ^ (value >> 8);
        }
    }

    if (f != NULL)
        fclose(f);

    if (ok)
        puts("*** SD test succesfull!");
    else
        puts("*** SD test failed!");
}

void xram_test()
{
    volatile uint16_t* const START = (volatile uint16_t*)0xd0000000;
    volatile uint16_t* const END   = START + (16 * 1024 * 1024);

    // First clear the whole RAM
    for (volatile uint16_t* iter = START; iter != END; iter++)
        *iter = 0;

    __DMB();  // Flush cache

    puts("*** Starting basic XRAM test...");
    bool ok = true;

    for (volatile uint16_t* iter = START; iter != END; iter++)
    {
        // Set a marker value at this address
        *iter = 0xffff;
        __DMB();  // Flush cache

        if (*iter != 0xffff)
        {
            ok = false;
            printf("Readback failed: %p\n", iter);
        }

        // Reset the value back to 0
        *iter = 0;
        __DMB();  // Flush cache
    }

    if (ok)
        puts("*** XRAM basic test succesfull!");
    else
        puts("*** XRAM basic test failed!");

    puts("*** Starting XRAM mirroring test...");
    ok = true;

    // Generate addresses to test every line
    for (int i = 1; i < 25; i++)
    {
        volatile uint16_t* other =
            (volatile uint16_t*)((size_t)START | (1 << i));

        // Write something
        *other = 0xdead;
        __DMB();  // Flush cache

        for (volatile uint16_t* iter = START; iter != END; iter++)
        {
            // Skip the written address
            if (iter == other)
                continue;

            // Check for mirroring
            if (*iter == 0xdead)
            {
                printf("Mirroring %p -> %p\n", other, iter);
                ok = false;
            }
        }

        // Reset the value
        *other = 0;
        __DMB();  // Flush cache
    }

    if (ok)
        puts("*** XRAM mirror test succesfull!");
    else
        puts("*** XRAM mirror test failed!");
}

void pin_continuity_test()
{
    for (auto& a : PIN_DEFS)
        a.pin.mode(Mode::INPUT_PULL_DOWN);

    puts("*** Starting pin continuity test...");
    bool ok = true;

    // Ok now start testing
    for (auto& a : PIN_DEFS)
    {
        a.pin.mode(Mode::OUTPUT);
        a.pin.high();
        for (auto& b : PIN_DEFS)
        {
            if (a.pin.getNumber() == b.pin.getNumber() &&
                a.pin.getPort() == b.pin.getPort())
            {
                continue;
            }

            if (b.pin.value() == 1)
            {
                printf("Shorted pins %s %s\n", a.gpio_name, b.gpio_name);
                ok = false;
            }
        }

        a.pin.low();
        a.pin.mode(Mode::INPUT_PULL_DOWN);
    }

    if (ok)
        puts("*** Pin continuity test succesfull!");
    else
        puts("*** Pin continuity test failed!");
}

void pin_semi_test()
{
    for (auto& a : PIN_DEFS)
    {
        a.pin.mode(Mode::OUTPUT);
        a.pin.low();
    }

    puts("*** Starting semi-automatic pin test...");

    std::atomic<bool> running(true);
    std::atomic<int> cur_idx(0);

    // Parallel test pulsing pin
    std::thread t(
        [&]()
        {
            while (running)
            {
                int idx = cur_idx;

                PIN_DEFS[idx].pin.high();
                Thread::sleep(10);
                PIN_DEFS[idx].pin.low();
                Thread::sleep(10);
            }
        });

    // Clean any pending newlines
    while (getchar() != '\n')
        ;
    puts("Press enter to continue...");

    for (size_t i = 0; i < PIN_DEFS.size(); i++)
    {
        PinDef& a = PIN_DEFS[i];

        // Pause waiting for keys
        while (getchar() != '\n')
            ;

        cur_idx = i;
        printf(
            "Pulsing %s, conan: %s, breakout v1: %s, press enter to "
            "continue...\n",
            a.gpio_name, a.conan_name, a.breakout_v1_name);
    }

    running = false;
    t.join();

    puts("*** Test finished!");
}

int main()
{
    banner(true);

    // Spawn the led blinker task
    std::thread t(led_task);

    while (1)
    {
        printf(
            "\nSelect test to run:\n"
            "- 1 Automatic SD test\n"
            "- 2 Automatic RAM test\n"
            "- 3 Automatic pin continuity test\n"
            "- 4 Semi-automatic pin test\n"
            "\n"
            "Command: ");

        int cmd;
        scanf("%d", &cmd);

        switch (cmd)
        {
            case 1:
                sd_test();
                break;

            case 2:
                xram_test();
                break;

            case 3:
                pin_continuity_test();
                break;

            case 4:
                pin_semi_test();
                break;
        }
    }

    return 0;
}