#include "PluginEditor.h"

juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter();
#include <juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h>

namespace
{
constexpr int editorWidth = 1536;
constexpr int mainEditorHeight = 900;
constexpr int standaloneKeyboardHeight = 80;
constexpr int standaloneEditorHeight = mainEditorHeight + standaloneKeyboardHeight;

juce::MidiKeyboardState::Listener* findStandaloneMidiCollector (
    DeepPluckAudioProcessor& processor) noexcept
{
    if (processor.wrapperType != juce::AudioProcessor::wrapperType_Standalone)
        return nullptr;

    if (auto* holder = juce::StandalonePluginHolder::getInstance();
        holder != nullptr && holder->processor.get() == &processor)
        return &holder->player.getMidiMessageCollector();

    jassertfalse;
    return nullptr;
}
}

DeepPluckAudioProcessorEditor::DeepPluckAudioProcessorEditor (DeepPluckAudioProcessor& processor)
    : DeepPluckAudioProcessorEditor (processor, deeppluck::gui::DeepPluckGuiPreferences::shared())
{
}

DeepPluckAudioProcessorEditor::DeepPluckAudioProcessorEditor (
    DeepPluckAudioProcessor& processor,
    std::shared_ptr<deeppluck::gui::DeepPluckGuiPreferences> injectedPreferences)
    : DeepPluckAudioProcessorEditor (processor, std::move (injectedPreferences), nullptr)
{
}

DeepPluckAudioProcessorEditor::DeepPluckAudioProcessorEditor (
    DeepPluckAudioProcessor& processor,
    std::shared_ptr<deeppluck::gui::DeepPluckGuiPreferences> injectedPreferences,
    juce::MidiKeyboardState::Listener* injectedStandaloneMidiListener)
    : AudioProcessorEditor (&processor),
      audioProcessor (processor),
      preferences (injectedPreferences != nullptr ? std::move (injectedPreferences)
                                                  : deeppluck::gui::DeepPluckGuiPreferences::shared()),
      tooltipWindow (this),
      header (processor),
      oscillatorRow (processor),
      mainPage (processor),
      modulationPage (processor),
      effectsPage (processor),
      settingsPage (*preferences),
      welcome (*preferences)
{
    setLookAndFeel (&lookAndFeel);
    audioProcessor.ensureInitialPresetScan();
    addAndMakeVisible (header);
    addAndMakeVisible (oscillatorRow);
    addAndMakeVisible (navigation);
    addAndMakeVisible (mainPage);
    addChildComponent (modulationPage);
    addChildComponent (effectsPage);
    addChildComponent (settingsPage);
    addAndMakeVisible (footer);

    if (audioProcessor.wrapperType == juce::AudioProcessor::wrapperType_Standalone)
    {
        auto* midiListener = injectedStandaloneMidiListener != nullptr
                                 ? injectedStandaloneMidiListener
                                 : findStandaloneMidiCollector (audioProcessor);
        standaloneKeyboard =
            std::make_unique<deeppluck::gui::DeepPluckStandaloneKeyboardPanel> (midiListener);
        addAndMakeVisible (*standaloneKeyboard);
    }

    addChildComponent (welcome);

    navigation.onPageSelected = [this] (deeppluck::gui::Page page) { showPage (page); };
    settingsPage.onHoverHelpChanged = [this] (bool enabled)
    {
        tooltipWindow.setEnabled (enabled);
    };
    settingsPage.onWelcomeRequested = [this]
    {
        if (standaloneKeyboard != nullptr)
            standaloneKeyboard->releaseAllNotes();
        welcome.showNow();
    };
    tooltipWindow.setEnabled (preferences->get().hoverHelp);

    setResizable (false, false);
    setSize (editorWidth, standaloneKeyboard != nullptr
                              ? standaloneEditorHeight : mainEditorHeight);
    showPage (deeppluck::gui::Page::main);
    refreshForTests();
    welcome.showIfNeeded();
    startTimerHz (30);
}

DeepPluckAudioProcessorEditor::~DeepPluckAudioProcessorEditor()
{
    stopTimer();
    standaloneKeyboard.reset();
    setLookAndFeel (nullptr);
}

void DeepPluckAudioProcessorEditor::paint (juce::Graphics& graphics)
{
    graphics.fillAll (deeppluck::gui::Colours::background);
    graphics.setColour (deeppluck::gui::Colours::surface.withAlpha (0.55f));
    graphics.fillRoundedRectangle (getLocalBounds().toFloat().reduced (3.0f), 8.0f);
    graphics.setColour (deeppluck::gui::Colours::border);
    graphics.drawRoundedRectangle (getLocalBounds().toFloat().reduced (1.5f), 8.0f, 1.0f);
}

void DeepPluckAudioProcessorEditor::resized()
{
    auto bounds = juce::Rectangle<int> (0, 0, editorWidth, mainEditorHeight).reduced (8, 4);
    header.setBounds (bounds.removeFromTop (112));
    bounds.removeFromTop (6);
    oscillatorRow.setBounds (bounds.removeFromTop (200));
    bounds.removeFromTop (6);
    navigation.setBounds (bounds.removeFromTop (40));
    bounds.removeFromTop (6);
    footer.setBounds (bounds.removeFromBottom (26));
    bounds.removeFromBottom (6);
    mainPage.setBounds (bounds);
    modulationPage.setBounds (bounds);
    effectsPage.setBounds (bounds);
    settingsPage.setBounds (bounds);
    if (standaloneKeyboard != nullptr)
        standaloneKeyboard->setBounds (0, mainEditorHeight,
                                       editorWidth, standaloneKeyboardHeight);
    welcome.setBounds (getLocalBounds());
}

void DeepPluckAudioProcessorEditor::showPage (deeppluck::gui::Page page)
{
    currentPage = page;
    if (page != deeppluck::gui::Page::main
        && page != deeppluck::gui::Page::modulation
        && page != deeppluck::gui::Page::effects
        && page != deeppluck::gui::Page::settings)
        page = deeppluck::gui::Page::main;
    currentPage = page;
    mainPage.setVisible (page == deeppluck::gui::Page::main);
    mainPage.setEnabled (page == deeppluck::gui::Page::main);
    modulationPage.setVisible (page == deeppluck::gui::Page::modulation);
    modulationPage.setEnabled (page == deeppluck::gui::Page::modulation);
    effectsPage.setVisible (page == deeppluck::gui::Page::effects);
    effectsPage.setEnabled (page == deeppluck::gui::Page::effects);
    settingsPage.setVisible (page == deeppluck::gui::Page::settings);
    settingsPage.setEnabled (page == deeppluck::gui::Page::settings);
    navigation.setSelectedPage (page);
    refreshForTests();
}

deeppluck::gui::Page DeepPluckAudioProcessorEditor::getCurrentPage() const noexcept
{
    return currentPage;
}

bool DeepPluckAudioProcessorEditor::isPageVisible (deeppluck::gui::Page page) const noexcept
{
    switch (page)
    {
        case deeppluck::gui::Page::main: return mainPage.isVisible();
        case deeppluck::gui::Page::modulation: return modulationPage.isVisible();
        case deeppluck::gui::Page::effects: return effectsPage.isVisible();
        case deeppluck::gui::Page::settings: return settingsPage.isVisible();
        case deeppluck::gui::Page::sound:
        case deeppluck::gui::Page::filter:
        case deeppluck::gui::Page::shape: return false;
    }
    return false;
}

bool DeepPluckAudioProcessorEditor::isModulationNavigationEnabled() const noexcept
{
    return navigation.isModulationEnabled();
}

bool DeepPluckAudioProcessorEditor::isEffectsNavigationEnabled() const noexcept
{
    return navigation.isEffectsEnabled();
}

int DeepPluckAudioProcessorEditor::getAttachmentCount() const noexcept
{
    return header.attachmentCount + oscillatorRow.attachmentCount
           + mainPage.attachmentCount
           + modulationPage.attachmentCount + effectsPage.attachmentCount;
}

bool DeepPluckAudioProcessorEditor::isOscillatorRowVisible() const noexcept
{
    return oscillatorRow.isVisible();
}

bool DeepPluckAudioProcessorEditor::isWelcomeVisible() const noexcept
{
    return welcome.isVisible();
}

bool DeepPluckAudioProcessorEditor::hasStandaloneKeyboard() const noexcept
{
    return standaloneKeyboard != nullptr;
}

juce::Rectangle<int> DeepPluckAudioProcessorEditor::getStandaloneKeyboardPanelBounds() const noexcept
{
    return standaloneKeyboard != nullptr ? standaloneKeyboard->getBounds()
                                         : juce::Rectangle<int> {};
}

juce::Rectangle<int> DeepPluckAudioProcessorEditor::getStandaloneKeyboardBounds() const noexcept
{
    return standaloneKeyboard != nullptr ? standaloneKeyboard->getKeyboardBounds()
                                         : juce::Rectangle<int> {};
}

int DeepPluckAudioProcessorEditor::getStandaloneKeyboardLowestVisibleKey() const noexcept
{
    return standaloneKeyboard != nullptr ? standaloneKeyboard->getLowestVisibleKey() : -1;
}

bool DeepPluckAudioProcessorEditor::isStandaloneOctaveDownEnabled() const noexcept
{
    return standaloneKeyboard != nullptr && standaloneKeyboard->isOctaveDownEnabled();
}

bool DeepPluckAudioProcessorEditor::isStandaloneOctaveUpEnabled() const noexcept
{
    return standaloneKeyboard != nullptr && standaloneKeyboard->isOctaveUpEnabled();
}

bool DeepPluckAudioProcessorEditor::standaloneKeyboardWantsKeyboardFocus() const noexcept
{
    return standaloneKeyboard != nullptr && standaloneKeyboard->keyboardWantsKeyboardFocus();
}

void DeepPluckAudioProcessorEditor::standaloneOctaveDownForTests()
{
    if (standaloneKeyboard != nullptr)
        standaloneKeyboard->octaveDownForTests();
}

void DeepPluckAudioProcessorEditor::standaloneOctaveUpForTests()
{
    if (standaloneKeyboard != nullptr)
        standaloneKeyboard->octaveUpForTests();
}

void DeepPluckAudioProcessorEditor::standaloneNoteOnForTests (int midiNoteNumber, float velocity)
{
    if (standaloneKeyboard != nullptr)
        standaloneKeyboard->noteOnForTests (midiNoteNumber, velocity);
}

void DeepPluckAudioProcessorEditor::standaloneNoteOffForTests (int midiNoteNumber, float velocity)
{
    if (standaloneKeyboard != nullptr)
        standaloneKeyboard->noteOffForTests (midiNoteNumber, velocity);
}

bool DeepPluckAudioProcessorEditor::isStandaloneNoteOnForTests (int midiNoteNumber) const noexcept
{
    return standaloneKeyboard != nullptr
        && standaloneKeyboard->isNoteOnForTests (midiNoteNumber);
}

std::array<float, 2> DeepPluckAudioProcessorEditor::getDisplayedMeterLevels() const noexcept
{
    return header.getDisplayedMeterLevels();
}

void DeepPluckAudioProcessorEditor::refreshForTests()
{
    header.refreshPresetState();
    oscillatorRow.refreshEnabledState();
    settingsPage.refresh();
    switch (currentPage)
    {
        case deeppluck::gui::Page::main: mainPage.refreshEnabledState(); break;
        case deeppluck::gui::Page::modulation: modulationPage.refreshEnabledState(); break;
        case deeppluck::gui::Page::effects: effectsPage.refreshEnabledState(); break;
        case deeppluck::gui::Page::settings: break;
        case deeppluck::gui::Page::sound:
        case deeppluck::gui::Page::filter:
        case deeppluck::gui::Page::shape: break;
    }
}

void DeepPluckAudioProcessorEditor::refreshMeterForTests()
{
    header.refreshOutputMeter();
}

void DeepPluckAudioProcessorEditor::timerCallback()
{
    refreshForTests();
    header.refreshOutputMeter();
}
