/*
  ==============================================================================

    PluginProcessor.cpp
    Created: 5 Sep 2023
    Author:  binya

  ==============================================================================
*/

#pragma once
#include <JuceHeader.h>

#include "PluginProcessor.h"
#include "CustomLookAndFeel.h"
#include "Components/Buttons.h"
#include "Components/InputDeviceSelector.h"
#include "Components/SampleEditor.h"
#include "Components/FxChain.h"
#include "Components/Prompt.h"
#include "Components/SampleNavigator.h"

/** Painting ordering requires a separate component... */
class EditorOverlay final : public CustomComponent
{
public:
    void setWaveformMode(bool isWaveformMode)
    {
        if (waveformModeAvailable != isWaveformMode)
        {
            waveformModeAvailable = isWaveformMode;
            repaint();
        }
    }

    void setWaveformModeDisabled(bool disabled)
    {
        if (waveformModeDisabled != disabled)
        {
            waveformModeDisabled = disabled;
            repaint();
        }
    }

private:
    void paint(juce::Graphics& g) override;
    void resized() override;
    void lookAndFeelChanged() override;

    float scale(float value) const { return value * getWidth() / Layout::figmaWidth; }
    float scale(int value) const { return scale(float(value)); }

    //==============================================================================
    melatonin::DropShadow sampleControlShadow{ defaultTheme.slate.withAlpha(0.125f), 3, {2, 2} };
    melatonin::DropShadow navControlShadow{ defaultTheme.slate.withAlpha(0.125f), 3, {-2, -2} };

    bool waveformModeAvailable{ false };
    bool waveformModeDisabled{ false };
};

//==============================================================================
class JustaSampleAudioProcessorEditor final : public juce::AudioProcessorEditor, public juce::Timer, public juce::FileDragAndDropTarget, 
                                              public juce::FilenameComponentListener, public CustomHelpTextDisplay, public ThemeProvider
{
public:
    explicit JustaSampleAudioProcessorEditor(JustaSampleAudioProcessor& audioProcessor);
    ~JustaSampleAudioProcessorEditor() override;

private:
    void timerCallback() override;
    void paint(juce::Graphics&) override;
    void resized() override;
    void lookAndFeelChanged() override;

    void mouseDown(const juce::MouseEvent& event) override;
    void mouseUp(const juce::MouseEvent& event) override;
    void mouseDrag(const juce::MouseEvent& event) override;

    void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override;

    void helpTextChanged(const juce::String&) override {}

    void setTheme(Colors newTheme) override;
    Colors getTheme() const override;

    juce::Rectangle<int> getConstrainedBounds() const;

    //==============================================================================
    /** Update the Editor to fit with the processor's sample. On the initial load, the
        SampleNavigator will not update the viewing bounds.
    */
    void loadSample();

    /** Handles the incoming recording messages from the recorder queue */
    void handleActiveRecording();

    /** If permitted, toggles whether the plugin state will use a file reference or store the 
        samples directly. If necessary, this will prompt the user to save the current sample.
    */
    void toggleLinkSample();

    /** Signals to the processor to start recording, or first opens the device settings prompt if 
        no valid input device is selected. Note that no editor changes occur here, instead it polls
        for changes in the timer callback (simpler than registering as a listener to the recorder).
    */
    void startRecording(bool promptSettings = true);

    /** Starts a pitch detection prompt */
    void promptPitchDetection();

    /** Opens the device settings prompt */
    void promptDeviceSettings(bool recordOnClose = false);

    /** We use this as a place to update the enablement and display of our plugin controls. */
    void enablementChanged() override;
    void fxEnablementChanged();

    //==============================================================================
    /** Whether the editor is interested in a file */
    bool isInterestedInFile(const juce::String& file) const;
    bool isInterestedInFileDrag(const juce::StringArray& files) override;
    void filesDropped(const juce::StringArray& files, int x, int y) override;
    void filenameComponentChanged(juce::FilenameComponent* fileComponentThatHasChanged) override;

    void fileDragEnter(const juce::StringArray& files, int x, int y) override;
    void fileDragExit(const juce::StringArray& files) override;

    void updateLabel(const juce::String& text = "");

    //==============================================================================
    /** Scaling the sizes in our Figma demo to percentages of width.
        This rounding operation is important to ensure consistent spacing with JUCE's integer component bounds.
        Otherwise, we'd just do a simple division.
     */
    float scalei(int value) const { return scalei(float(value)); }
    float scalei(float value) const { return std::roundf(value * prompt.getWidth() / Layout::figmaWidth); }
    float scalef(float value) const { return value * prompt.getWidth() / Layout::figmaWidth; }

    //==============================================================================
    JustaSampleAudioProcessor& p;
    PluginParameters::State& pluginState;
    UIDummyParam dummyParam;
    const juce::OwnedArray<CustomSamplerVoice>& synthVoices;
    bool currentlyPlaying{ false };

    /** Some thought is needed to keep the editor synchronized when changes to the sample occur
        or files are loaded. I decided the easiest way is to have the processor and editor both
        keep track of the buffer's hash.
    */
    juce::String expectedHash{ 0 };

    bool userDraggedSample{ false };  // We use this to reset the UI only when a sample loads as result of a user selection

    //==============================================================================
    juce::AudioBuffer<float> pendingRecordingBuffer;
    int recordingBufferSize{ 0 };

    //==============================================================================
    // Modules
    juce::Label tuningLabel, attackLabel, releaseLabel, playbackLabel, loopingLabel, masterLabel;
    juce::Array<juce::Slider*> rotaries;

    // Tuning module
    CustomRotary semitoneRotary, centRotary, waveformSemitoneRotary, waveformCentRotary;
    CustomRotaryAttachment semitoneRotaryAttachment, centRotaryAttachment, waveformSemitoneRotaryAttachment, waveformCentRotaryAttachment;

    juce::Label tuningDetectLabel;
    CustomShapeButton tuningDetectButton;

    // Attack and release modules
    CustomRotary attackTimeRotary, attackCurve, releaseTimeRotary, releaseCurve;
    CustomRotaryAttachment attackTimeAttachment, attackCurveAttachment, releaseTimeAttachment, releaseCurveAttachment;
    EnvelopeSliderLookAndFeel<EnvelopeSlider::attack> attackCurveLNF;
    EnvelopeSliderLookAndFeel<EnvelopeSlider::release> releaseCurveLNF;

    // Playback module
    CustomToggleableButton lofiModeButton;
    APVTS::ButtonAttachment lofiModeAttachment;
    CustomChoiceButton playbackModeButton;
    juce::ParameterAttachment playbackModeAttachment;
    CustomRotary playbackSpeedRotary;
    CustomRotaryAttachment playbackSpeedAttachment;

    // Loop module
    CustomToggleableButton loopButton, loopStartButton, loopEndButton;
    APVTS::ButtonAttachment loopAttachment, loopStartAttachment, loopEndAttachment;

    // Mixing module
    CustomToggleableButton monoOutputButton;
    APVTS::ButtonAttachment monoOutputAttachment;
    CustomRotary gainSlider;
    CustomRotaryAttachment gainSliderAttachment;
    VolumeSliderLookAndFeel gainSliderLNF;

    // Sample controls
    EditorOverlay editorOverlay;
    juce::FilenameComponent filenameComponent;
    CustomToggleableButton linkSampleToggle;  // Whether the sample should be stored in the plugin state

    CustomToggleableButton waveformModeLabel;
    juce::ButtonParameterAttachment waveformModeLabelAttachment;

    CustomShapeButton playStopButton;
    juce::Path playPath, stopPath;
    const juce::String playHelpText{ "Play sample" }, stopHelpText{ "Halt voices" };
    CustomShapeButton recordButton;
    CustomShapeButton deviceSettingsButton;
    InputDeviceSelector audioDeviceSettings;

    // Nav controls
    CustomShapeButton fitButton;
    CustomToggleableButton pinButton;
    ToggleButtonAttachment pinButtonAttachment;

    // Main components
    SampleEditor sampleEditor;
    SampleNavigator sampleNavigator;  // Note that SampleNavigator manages ViewStart and ViewEnd
    FxChain fxChain;

    juce::Label statusLabel;
    bool fileDragging{ false };

    // Some variables to help detect scroll gestures
    const int maxCallbacksSinceInZoomGesture{ 20 };
    int zoomGestureEndCallbacks{ 0 };
    bool mouseWheelDetected{ false };
    int lastViewStart{ 0 }, lastViewEnd{ 0 };
  
    // Footer
    juce::Label versionInfo;
    CustomToggleableButton preFXButton;
    APVTS::ButtonAttachment preFXAttachment;
    CustomToggleableButton showFXButton;
    ToggleButtonAttachment showFXAttachment;
    juce::ParameterAttachment eqEnablementAttachment, reverbEnablementAttachment, distortionEnablementAttachment, chorusEnablementAttachment;
    bool eqEnabled{ false }, reverbEnabled{ false }, distortionEnabled{ false }, chorusEnabled{ false };

    Prompt prompt;

    CustomLookAndFeel& lnf;
    Colors theme{ defaultTheme };
    juce::OpenGLContext openGLContext;
    juce::PluginHostType hostType;

#if JUCE_DEBUG
    melatonin::Inspector inspector{ *this, false };
#endif

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JustaSampleAudioProcessorEditor)
};
