#include "DeepPluckStandaloneKeyboard.h"

namespace deeppluck::gui
{
namespace
{
constexpr int buttonWidth = 74;
constexpr int horizontalInset = 8;
constexpr int verticalInset = 6;
constexpr int controlGap = 8;
}

DeepPluckStandaloneKeyboardPanel::DeepPluckStandaloneKeyboardPanel (
    juce::MidiKeyboardState::Listener* midiListener)
    : listener (midiListener)
{
    setComponentID ("standalone.keyboard.panel");
    setTitle ("Standalone virtual MIDI keyboard");
    setDescription ("Mouse-playable MIDI keyboard available only in the Standalone application");

    keyboard.setComponentID ("standalone.keyboard");
    keyboard.setTitle ("Virtual MIDI keyboard");
    keyboard.setDescription ("Play MIDI notes with the mouse");
    keyboard.setAvailableRange (0, 127);
    keyboard.setMidiChannel (midiChannel);
    keyboard.setMidiChannelsToDisplay (1 << (midiChannel - 1));
    keyboard.setVelocity (0.82f, true);
    keyboard.setKeyWidth (38.0f);
    // DeepPluck documents MIDI note 36 as C2, so the visible note labels use
    // the conventional MIDI-60-is-C4 naming rather than JUCE's C3 default.
    keyboard.setOctaveForMiddleC (4);
    // JUCE resets the viewport to note zero when scrolling is disabled. Keep
    // its viewport active but make the internal arrows zero-width; the two
    // project-owned octave buttons are the only navigation controls.
    keyboard.setScrollButtonsVisible (true);
    keyboard.setScrollButtonWidth (0);
    keyboard.setLowestVisibleKey (lowestVisibleKey);
    keyboard.clearKeyMappings();
    keyboard.setWantsKeyboardFocus (false);
    keyboard.setMouseClickGrabsKeyboardFocus (false);
    keyboard.setColour (juce::MidiKeyboardComponent::whiteNoteColourId,
                        juce::Colour::fromRGB (205, 217, 213));
    keyboard.setColour (juce::MidiKeyboardComponent::blackNoteColourId,
                        juce::Colour::fromRGB (5, 12, 14));
    keyboard.setColour (juce::MidiKeyboardComponent::keySeparatorLineColourId,
                        Colours::border.brighter (0.20f));
    keyboard.setColour (juce::MidiKeyboardComponent::mouseOverKeyOverlayColourId,
                        Colours::teal.withAlpha (0.24f));
    keyboard.setColour (juce::MidiKeyboardComponent::keyDownOverlayColourId,
                        Colours::amber.withAlpha (0.58f));
    keyboard.setColour (juce::MidiKeyboardComponent::textLabelColourId,
                        Colours::background.withAlpha (0.72f));
    keyboard.setColour (juce::MidiKeyboardComponent::shadowColourId,
                        Colours::background.withAlpha (0.72f));

    octaveDown.setComponentID ("standalone.keyboard.octaveDown");
    octaveDown.setTitle ("Octave down");
    octaveDown.setDescription ("Move the visible keyboard down by one octave");
    octaveDown.setTooltip ("Move the virtual keyboard down by one octave");
    octaveDown.setExplicitFocusOrder (90);
    octaveDown.setWantsKeyboardFocus (true);
    octaveDown.onClick = [this] { shiftOctave (-octaveStep); };

    octaveUp.setComponentID ("standalone.keyboard.octaveUp");
    octaveUp.setTitle ("Octave up");
    octaveUp.setDescription ("Move the visible keyboard up by one octave");
    octaveUp.setTooltip ("Move the virtual keyboard up by one octave");
    octaveUp.setExplicitFocusOrder (91);
    octaveUp.setWantsKeyboardFocus (true);
    octaveUp.onClick = [this] { shiftOctave (octaveStep); };

    addAndMakeVisible (keyboard);
    addAndMakeVisible (octaveDown);
    addAndMakeVisible (octaveUp);

    if (listener != nullptr)
    {
        keyboardState.addListener (listener);
        connected = true;
    }

    updateOctaveButtons();
}

DeepPluckStandaloneKeyboardPanel::~DeepPluckStandaloneKeyboardPanel()
{
    disconnect();
}

void DeepPluckStandaloneKeyboardPanel::paint (juce::Graphics& graphics)
{
    graphics.fillAll (Colours::background);
    const auto panelBounds = getLocalBounds().toFloat().reduced (1.0f);
    graphics.setColour (Colours::surface);
    graphics.fillRoundedRectangle (panelBounds, 5.0f);
    graphics.setColour (Colours::border);
    graphics.drawRoundedRectangle (panelBounds, 5.0f, 1.0f);
}

void DeepPluckStandaloneKeyboardPanel::resized()
{
    auto bounds = getLocalBounds().reduced (horizontalInset, verticalInset);
    octaveDown.setBounds (bounds.removeFromLeft (buttonWidth));
    bounds.removeFromLeft (controlGap);
    octaveUp.setBounds (bounds.removeFromRight (buttonWidth));
    bounds.removeFromRight (controlGap);
    keyboard.setBounds (bounds);
}

void DeepPluckStandaloneKeyboardPanel::disconnect()
{
    if (! connected)
        return;

    keyboard.setEnabled (false);
    releaseAllNotes();
    keyboardState.removeListener (listener);
    connected = false;
    listener = nullptr;
}

void DeepPluckStandaloneKeyboardPanel::releaseAllNotes()
{
    if (connected)
        keyboardState.allNotesOff (midiChannel);
}

int DeepPluckStandaloneKeyboardPanel::getLowestVisibleKey() const noexcept
{
    return keyboard.getLowestVisibleKey();
}

bool DeepPluckStandaloneKeyboardPanel::isOctaveDownEnabled() const noexcept
{
    return octaveDown.isEnabled();
}

bool DeepPluckStandaloneKeyboardPanel::isOctaveUpEnabled() const noexcept
{
    return octaveUp.isEnabled();
}

bool DeepPluckStandaloneKeyboardPanel::keyboardWantsKeyboardFocus() const noexcept
{
    return keyboard.getWantsKeyboardFocus();
}

juce::Rectangle<int> DeepPluckStandaloneKeyboardPanel::getKeyboardBounds() const noexcept
{
    return keyboard.getBounds();
}

int DeepPluckStandaloneKeyboardPanel::getKeyboardRangeStart() const noexcept
{
    return keyboard.getRangeStart();
}

int DeepPluckStandaloneKeyboardPanel::getKeyboardRangeEnd() const noexcept
{
    return keyboard.getRangeEnd();
}

int DeepPluckStandaloneKeyboardPanel::getKeyboardMidiChannel() const noexcept
{
    return keyboard.getMidiChannel();
}

void DeepPluckStandaloneKeyboardPanel::octaveDownForTests()
{
    shiftOctave (-octaveStep);
}

void DeepPluckStandaloneKeyboardPanel::octaveUpForTests()
{
    shiftOctave (octaveStep);
}

void DeepPluckStandaloneKeyboardPanel::noteOnForTests (int midiNoteNumber, float velocity)
{
    if (juce::isPositiveAndBelow (midiNoteNumber, 128))
        keyboardState.noteOn (midiChannel, midiNoteNumber, juce::jlimit (0.0f, 1.0f, velocity));
}

void DeepPluckStandaloneKeyboardPanel::noteOffForTests (int midiNoteNumber, float velocity)
{
    if (juce::isPositiveAndBelow (midiNoteNumber, 128))
        keyboardState.noteOff (midiChannel, midiNoteNumber, juce::jlimit (0.0f, 1.0f, velocity));
}

bool DeepPluckStandaloneKeyboardPanel::isNoteOnForTests (int midiNoteNumber) const noexcept
{
    return juce::isPositiveAndBelow (midiNoteNumber, 128)
        && keyboardState.isNoteOn (midiChannel, midiNoteNumber);
}

void DeepPluckStandaloneKeyboardPanel::shiftOctave (int semitones)
{
    lowestVisibleKey = juce::jlimit (minimumLowestVisibleKey,
                                     maximumLowestVisibleKey,
                                     lowestVisibleKey + semitones);
    keyboard.setLowestVisibleKey (lowestVisibleKey);
    updateOctaveButtons();
}

void DeepPluckStandaloneKeyboardPanel::updateOctaveButtons()
{
    octaveDown.setEnabled (lowestVisibleKey > minimumLowestVisibleKey);
    octaveUp.setEnabled (lowestVisibleKey < maximumLowestVisibleKey);
}
}
