#include "main_framework_proto1.hh"
#include "utils.hh"
#include <functional>
namespace Heck {
    namespace { // anonymous namespace for internal linkage
        // declarations

        void audio_callback(
            ld::AudioHandle::InputBuffer in,
            ld::AudioHandle::OutputBuffer out,
            size_t size);

        // =============================================================================================
        // STATIC INIT
        // =============================================================================================
        ld::DaisySeed seed{};
        ld::MidiUartHandler midi{};
        dsp::Oscillator osc{};


        // Buttons
        std::array<bool, 4> buts_value{ false, false, false, false };

        ld::Switch ld_but_1{};
        PollingObserver<bool> but_1{ []() -> bool {
                                        ld_but_1.Debounce();
                                        return ld_but_1.Pressed();
                                    },
                                     [](bool val) {
                                         buts_value[0] = val;
                                         seed.PrintLine("BUT_1: %i", buts_value[0]);
                                     } };

        ld::Switch ld_but_2{};
        PollingObserver<bool> but_2{ []() -> bool {
                                        ld_but_2.Debounce();
                                        return ld_but_2.Pressed();
                                    },
                                     [](bool val) {
                                         buts_value[1] = val;
                                         seed.PrintLine("BUT_2: %i", buts_value[1]);
                                     } };

        ld::Switch ld_but_3{};
        PollingObserver<bool> but_3{ []() -> bool {
                                        ld_but_3.Debounce();
                                        return ld_but_3.Pressed();
                                    },
                                     [](bool val) {
                                         buts_value[2] = val;
                                         seed.PrintLine("BUT_3: %i", buts_value[2]);
                                     } };

        ld::Switch ld_but_4{};
        PollingObserver<bool> but_4{ []() -> bool {
                                        ld_but_4.Debounce();
                                        return ld_but_4.Pressed();
                                    },
                                     [](bool val) {
                                         buts_value[3] = val;
                                         seed.PrintLine("BUT_4: %i", buts_value[3]);
                                     } };


        // Pots
        std::array<float, 4> pots_value{ -1.F, -1.F, -1.F, -1.F };
        PollingObserver<int> pot1{ []() -> int { return seed.adc.Get(0); },
                                   [](int val) {
                                       pots_value[0] = float(val) /
                                                       (float)std::numeric_limits<u16>::max();
                                       seed.PrintLine("POT_0: %d", (int)(pots_value[0] * 100.));
                                   } };

        PollingObserver<int> pot2{ []() -> int { return seed.adc.Get(1); },
                                   [](int val) {
                                       pots_value[1] = float(val) /
                                                       (float)std::numeric_limits<u16>::max();
                                       seed.PrintLine("POT_1: %d", (int)(pots_value[1] * 100.));
                                   } };

        PollingObserver<int> pot3{ []() -> int { return seed.adc.Get(2); },
                                   [](int val) {
                                       pots_value[2] = float(val) /
                                                       (float)std::numeric_limits<u16>::max();
                                       seed.PrintLine("POT_2: %d", (int)(pots_value[2] * 100.));
                                   } };

        PollingObserver<int> pot4{ []() -> int { return seed.adc.Get(3); },
                                   [](int val) {
                                       pots_value[3] = float(val) /
                                                       (float)std::numeric_limits<u16>::max();
                                       seed.PrintLine("POT_3: %d", (int)(pots_value[3] * 100.));
                                   } };


        // Tasks
        PeriodicTask ui_task{ 10, [](u32 time_now) {
                                 pot1.fetch_and_deliver_fuzzy(Constants::Hardware::BUT_FUZZ);
                                 pot2.fetch_and_deliver_fuzzy(Constants::Hardware::BUT_FUZZ);
                                 pot3.fetch_and_deliver_fuzzy(Constants::Hardware::BUT_FUZZ);
                                 pot4.fetch_and_deliver_fuzzy(Constants::Hardware::BUT_FUZZ);
                                 but_1.fetch_and_deliver();
                                 but_2.fetch_and_deliver();
                                 but_3.fetch_and_deliver();
                                 but_4.fetch_and_deliver();

                                 while (midi.HasEvents()) {
                                     ld::MidiEvent msg = midi.PopEvent();
                                     char strbuf[128];
                                     GetMidiTypeAsString(msg, &strbuf[0]);
                                     seed.PrintLine("%s", strbuf);
                                 }
                             } };


        PeriodicTask heartbeat_task{ 100, [](u32 time) {
                                        static bool heartbeat_led_state{ false };
                                        heartbeat_led_state = !heartbeat_led_state;
                                        seed.SetLed(heartbeat_led_state);
                                        return;
                                    } };


        // =============================================================================================
        // RUNTIME INIT
        // =============================================================================================
        void init()
        {
            seed.Configure();
            seed.Init(Constants::CPU_BOOST480MHZ);
            seed.StartLog(Constants::Developer::LOG_BLOCKS_BOOT);

            {
                ld_but_1.Init(seed.GetPin(Constants::Hardware::PIN_BUT_1));
                ld_but_2.Init(seed.GetPin(Constants::Hardware::PIN_BUT_2));
                ld_but_3.Init(seed.GetPin(Constants::Hardware::PIN_BUT_3));
                ld_but_4.Init(seed.GetPin(Constants::Hardware::PIN_BUT_4));
            }

            {
                ld::AdcChannelConfig adc_cfg[4];
                adc_cfg[0].InitSingle(ld::DaisySeed::GetPin(Constants::Hardware::PIN_POT_1));
                adc_cfg[1].InitSingle(ld::DaisySeed::GetPin(Constants::Hardware::PIN_POT_2));
                adc_cfg[2].InitSingle(ld::DaisySeed::GetPin(Constants::Hardware::PIN_POT_3));
                adc_cfg[3].InitSingle(ld::DaisySeed::GetPin(Constants::Hardware::PIN_POT_4));

                seed.adc.Init(adc_cfg, 4);
                seed.adc.Start();
            }

            seed.PrintLine("Setting Blocksize: %i", Constants::AUDIO_BUFFERSIZE);
            seed.SetAudioBlockSize(Constants::AUDIO_BUFFERSIZE);

            seed.PrintLine("Setting Samplerate: %i", Constants::AUDIO_SAMPLERATE);
            seed.SetAudioSampleRate(Constants::AUDIO_SAMPLERATE);

            seed.PrintLine("Initializing MIDI");
            ld::MidiUartHandler::Config midi_config{};
            midi.Init(midi_config);

            seed.PrintLine("Starting MIDI Receive");
            midi.StartReceiveRt([](const ld::MidiEvent& msg) {
                switch (msg.srt_type) {
                    case ld::TimingClock: {
                    } break;
                    case ld::Start: {
                    } break;
                    case ld::Stop: {
                    } break;
                    case ld::Reset: {
                    } break;
                    case ld::Continue: {
                    } break;
                }
            });
            midi.Listen();

            seed.PrintLine("Starting Audio");
            seed.StartAudio(audio_callback);

            osc.Init(Constants::AUDIO_SAMPLERATE);
            osc.SetWaveform(osc.WAVE_SIN);
            osc.SetAmp(1.F);
            osc.SetFreq(100);
        }


        // =============================================================================================
        // RUN
        // =============================================================================================

        void audio_callback(ld::AudioHandle::InputBuffer in, ld::AudioHandle::OutputBuffer out, size_t size)
        {
            {
                float osc_out{};
                for (size_t i = 0; i < size; i++) {
                    osc_out = osc.Process();
                    osc_out *= 0.05;
                    out[0][i] = osc_out;
                    out[1][i] = osc_out;
                }
            }

            if constexpr (false) {
                // Channel 1
                for (size_t i = 0; i < size; i++) {
                    out[0][i] = in[0][i];
                }

                // Channel 2
                for (size_t i = 0; i < size; i++) {
                    out[1][i] = in[1][i];
                }
            }
        }

        void mainloop()
        {
            seed.PrintLine("Entering MainLoop");
            u32 uptime_ms{};
            while (true) {
                uptime_ms = ld::System::GetNow();
                ui_task.run_pending(uptime_ms);
                heartbeat_task.run_pending(uptime_ms);
            }
        }
    } // namespace
} // namespace Heck


int main()
{
    Heck::init();
    Heck::mainloop();
}