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

    SampleNavigator.h
    Created: 19 Sep 2023 4:41:12pm
    Author:  binya

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

#pragma once
#include <JuceHeader.h>

#include "../Sampler/CustomSamplerVoice.h"
#include "../Utilities/ComponentUtils.h"
#include "Displays/SamplePainter.h"

enum class NavigatorParts
{
    NONE,
    SAMPLE_START,
    SAMPLE_END,
    SAMPLE_FULL
};

/** A navigator control for the viewing window of the sample editor */
class SampleNavigator final : public CustomComponent, public ValueListener<int>, public ValueListener<bool>
{
    using Drag = NavigatorParts;

public:
    SampleNavigator(APVTS& apvts, PluginParameters::State& pluginState, const juce::OwnedArray<CustomSamplerVoice>& synthVoices);
    ~SampleNavigator() override;

    //==============================================================================
    void setSample(const juce::AudioBuffer<float>& sampleBuffer, float bufferSampleRate, bool resetView);

    /** Call this while recording when more samples have been filled in the sampleBuffer */
    void sampleUpdated(int oldSize, int newSize);
    void setRecordingMode(bool recording);

    /** Scrolling can be centered on a sample */
    void scrollView(const juce::MouseWheelDetails& wheel, int sampleCenter, bool centerZoomOut = false);

    /** Set the view to fit the sample play bounds */
    void fitView();

private:
    /** React to view changes */
    void valueChanged(ListenableValue<int>& source, int newValue) override;
    void valueChanged(ListenableValue<bool>& source, bool newValue) override;

    void paintOverChildren(juce::Graphics& g) override;
    void resized() override;
    void lookAndFeelChanged() override;
    void enablementChanged() override;

    //==============================================================================
    void mouseDown(const juce::MouseEvent& event) override;
    void mouseUp(const juce::MouseEvent& event) override;
    void mouseMove(const juce::MouseEvent& event) override;
    void mouseDrag(const juce::MouseEvent& event) override;
    void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override;
    void mouseDoubleClick(const juce::MouseEvent& event) override;

    /** Move the view start, maintaining constraints */
    void moveStart(float change, float sensitivity);
    void moveEnd(float change, float sensitivity);
    void moveBoth(float change, float sensitivity);

    NavigatorParts getDraggingTarget(int x, int y) const;

    /** Relative to the bounds of the painter */
    float sampleToPosition(int sampleIndex) const;
    int positionToSample(float position) const;

    /** constant sensitivity means that the drag distance is the same regardless of zoom level */
    float getDragSensitivity(bool constant) const;

    /** In some cases we'd like to adjust the loop start/end positions when they are enabled */
    void loopHasStartUpdate(bool newValue);
    void loopHasEndUpdate(bool newValue);

    /** Update the stored position ratios, for use when pinned */
    void updatePinnedPositions();
    void moveBoundsToPinnedPositions(bool startToEnd);

    bool isWaveformMode() const;

    //==============================================================================
    APVTS& apvts;
    PluginParameters::State& state;
    UIDummyParam dummyParam;
    SamplePainter painter;
    juce::ParameterAttachment gainAttachment;
    juce::ParameterAttachment monoAttachment;

    const juce::AudioBuffer<float>* sample{ nullptr };
    float sampleRate;
    const juce::OwnedArray<CustomSamplerVoice>& synthVoices;

    juce::AudioParameterBool* isWavetableModeDisabled, * isLooping, * loopHasStart, * loopHasEnd;
    juce::ParameterAttachment isWavetableModeDisabledAttachment, loopAttachment, loopStartAttachment, loopEndAttachment;

    bool dragging{ false };
    NavigatorParts draggingTarget{ NavigatorParts::NONE };
    float dragSelectOffset{ 0 };  // When dragging full, this is the offset from the view start where the drag started
    int lastDragOffset{ 0 };

    bool recordingMode{ false };

    // To keep the bounds in a consistent location when pinned, we need to store the ratios (necessary because zooming in more
    // causes rounding issues and the position moves drastically).
    long double pinnedSampleStart{ 0.f }, pinnedSampleEnd{ 0.f }, pinnedLoopStart{ 0.f }, pinnedLoopEnd{ 0.f };
    bool navigatorUpdate{ false };  // This flag allows the navigator to avoid updating the pinned positions when they are being used to update the bounds

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SampleNavigator)
};