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

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

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

        std::array<u16 *, 4> pots_raw{ nullptr, nullptr, nullptr, nullptr };
        std::array<f32, 4> pot{};

        std::array<DigitalIn, Constants::Hardware::BUT_COUNT> button{};

        std::array<dsp::Oscillator, Constants::Hardware::POT_COUNT> osc{};


        void fmtmon()
        {
            std::array<char, 500> record{};
            for (u16 i = 0; i < pot.size(); i++) {
                std::array<char, 10> field{};
                snprintf(field.data(), field.size(), "%4f ", pot[i]);
                strcat(record.data(), field.data());
            }
            strcat(record.data(), "-");
            for (u16 i = 0; i < button.size(); i++) {
                std::array<char, 5> field{};
                snprintf(field.data(), field.size(), "%2d ", button[i].read());
                strcat(record.data(), field.data());
            }
            seed.PrintLine(record.data());
        }


        // Tasks
        PeriodicTask ui_task{ 1, [](u32 time_now) {
                                 for (u16 i = 0; i < button.size(); i++) {
                                     button[i].cache.update_and_notify_change();
                                 }

                                 for (u16 i = 0; i < pots_raw.size(); i++) {
                                     pot[i] = float(*pots_raw[i]) / std::numeric_limits<u16>::max();
                                 }

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

                                 fmtmon();

                                 for(u16 i=0; i < osc.size(); i++) {
                                     osc[i].SetFreq(8000 * pot[i]);
                                 }
                             } };

        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);

            { // BUTTONS
                for (int i = 0; i < Constants::Hardware::BUT_COUNT; i++) {
                    button[i].init(Constants::Hardware::PIN_BUT[i]);
                    button[i].cache.set_notify_change_callback([i](Cache<bool> &obj) {
                        fmtmon();
                    });
                }
            }

            { // POTS
                std::array<ld::AdcChannelConfig, Constants::Hardware::POT_COUNT> adc_cfg{};
                for (int i = 0; i < Constants::Hardware::POT_COUNT; i++) {
                    adc_cfg[i].InitSingle(ld::DaisySeed::GetPin(Constants::Hardware::PIN_POT[i]));
                }
                seed.adc.Init(adc_cfg.data(), Constants::Hardware::POT_COUNT);
                for (int i = 0; i < Constants::Hardware::POT_COUNT; i++) {
                    pots_raw[i] = seed.adc.GetPtr(i);
                }

                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);

            for(dsp::Oscillator& o : osc) {
                o.Init(Constants::AUDIO_SAMPLERATE);
                o.SetWaveform(o.WAVE_SIN);
            }
        }


        // =============================================================================================
        // 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++) {
                    for(dsp::Oscillator& o : osc) {
                        osc_out += o.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();
}