/*
 ==============================================================================
 This file is part of the IEM plug-in suite.
 Author: Sebastian Grill
 Copyright (c) 2017 - Institute of Electronic Music and Acoustics (IEM)
 https://iem.at

 The IEM plug-in suite is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 The IEM plug-in suite is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this software.  If not, see <https://www.gnu.org/licenses/>.
 ==============================================================================
 */

#include "PluginEditor.h"
#include "PluginProcessor.h"

//==============================================================================
FdnReverbAudioProcessorEditor::FdnReverbAudioProcessorEditor (
    FdnReverbAudioProcessor& p,
    juce::AudioProcessorValueTreeState& vts) :
    juce::AudioProcessorEditor (&p),
    processor (p),
    valueTreeState (vts),
    footer (p.getOSCParameterInterface()),
    tv (20.f, 20000.f, 0.1f, 25.f, 5.f),
    fv (20.f, 20000.f, -80.f, 5.f, 5.f, false)
{
    // Make sure that before the constructor has finished, you've set the
    // editor's size to whatever you need it to be.

    setResizeLimits (650, 480, 1000, 950);
    setResizable (true, true);
    setLookAndFeel (&globalLaF);

    addAndMakeVisible (&freezeMode);
    freezeAttachment.reset (new ButtonAttachment (valueTreeState, "freeze", freezeMode));
    freezeMode.setButtonText ("freeze");

    addAndMakeVisible (&title);
    title.setTitle (juce::String ("FDN"), juce::String ("Reverb"));
    title.setFont (globalLaF.robotoBold, globalLaF.robotoLight);

    addAndMakeVisible (&footer);
    addAndMakeVisible (&delayGroup);
    delayGroup.setText ("General Settings");
    delayGroup.setTextLabelPosition (juce::Justification::centredLeft);
    delayGroup.setColour (juce::GroupComponent::outlineColourId, globalLaF.ClSeperator);
    delayGroup.setColour (juce::GroupComponent::textColourId, juce::Colours::white);
    delayGroup.setVisible (true);

    addAndMakeVisible (&filterGroup);
    filterGroup.setText ("Filter Settings");
    addAndMakeVisible (&t60Group);
    t60Group.setText ("Reverberation Time");
    t60Group.setTextLabelPosition (juce::Justification::centredLeft);
    t60Group.setColour (juce::GroupComponent::outlineColourId, globalLaF.ClSeperator);
    t60Group.setColour (juce::GroupComponent::textColourId, juce::Colours::red);
    t60Group.setVisible (true);

    addAndMakeVisible (&delayLengthSlider);
    delayAttachment.reset (new SliderAttachment (valueTreeState, "delayLength", delayLengthSlider));
    delayLengthSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    delayLengthSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    delayLengthSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                                 globalLaF.ClWidgetColours[1]);
    delayLengthSlider.setTooltip ("Room Size");

    addAndMakeVisible (&revTimeSlider);
    feedbackAttachment.reset (new SliderAttachment (valueTreeState, "revTime", revTimeSlider));
    revTimeSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    revTimeSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    revTimeSlider.setColour (juce::Slider::rotarySliderOutlineColourId, juce::Colours::white);
    revTimeSlider.setTooltip ("Reverberation Time");
    revTimeSlider.addListener (this);

    addAndMakeVisible (&fadeInSlider);
    fadeInAttachment.reset (new SliderAttachment (valueTreeState, "fadeInTime", fadeInSlider));
    fadeInSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    fadeInSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    fadeInSlider.setColour (juce::Slider::rotarySliderOutlineColourId, juce::Colours::white);
    fadeInSlider.setTooltip ("FadeIn Time");

    addAndMakeVisible (&dryWetSlider);
    dryWetAttachment.reset (new SliderAttachment (valueTreeState, "dryWet", dryWetSlider));
    dryWetSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    dryWetSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    dryWetSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                            globalLaF.ClWidgetColours[2]);
    dryWetSlider.setTooltip ("Dry/Wet");

    addAndMakeVisible (&lowCutoffSlider);
    lowCutoffAttachment.reset (new SliderAttachment (valueTreeState, "lowCutoff", lowCutoffSlider));
    lowCutoffSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    lowCutoffSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    lowCutoffSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                               globalLaF.ClWidgetColours[3]);
    lowCutoffSlider.setTooltip ("Low Shelf Cutoff Freq");

    addAndMakeVisible (&lowQSlider);
    lowQAttachment.reset (new SliderAttachment (valueTreeState, "lowQ", lowQSlider));
    lowQSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    lowQSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    lowQSlider.setColour (juce::Slider::rotarySliderOutlineColourId, globalLaF.ClWidgetColours[3]);
    lowQSlider.setTooltip ("Low Shelf Q");

    addAndMakeVisible (&lowGainSlider);
    lowGainAttachment.reset (new SliderAttachment (valueTreeState, "lowGain", lowGainSlider));
    lowGainSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    lowGainSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    lowGainSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                             globalLaF.ClWidgetColours[3]);
    lowGainSlider.setTooltip ("Low Shelf Gain");

    addAndMakeVisible (&highCutoffSlider);
    highCutoffAttachment.reset (
        new SliderAttachment (valueTreeState, "highCutoff", highCutoffSlider));
    highCutoffSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    highCutoffSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    highCutoffSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                                globalLaF.ClWidgetColours[0]);
    ;
    highCutoffSlider.setTooltip ("High Shelf Cutoff Freq");

    addAndMakeVisible (&highQSlider);
    highQAttachment.reset (new SliderAttachment (valueTreeState, "highQ", highQSlider));
    highQSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    highQSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    highQSlider.setColour (juce::Slider::rotarySliderOutlineColourId, globalLaF.ClWidgetColours[0]);
    highQSlider.setTooltip ("High Shelf Q");

    addAndMakeVisible (&highGainSlider);
    highGainAttachment.reset (new SliderAttachment (valueTreeState, "highGain", highGainSlider));
    highGainSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    highGainSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    highGainSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                              globalLaF.ClWidgetColours[0]);
    highGainSlider.setTooltip ("High Shelf Gain");

    addAndMakeVisible (&hpCutoffSlider);
    hpCutoffAttachment.reset (new SliderAttachment (valueTreeState, "hpFrequency", hpCutoffSlider));
    hpCutoffSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    hpCutoffSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    hpCutoffSlider.setColour (juce::Slider::rotarySliderOutlineColourId,
                              globalLaF.ClWidgetColours[2]);
    hpCutoffSlider.setTooltip ("Highpass Cutoff Freq");

    addAndMakeVisible (&hpQSlider);
    hpQAttachment.reset (new SliderAttachment (valueTreeState, "hpQ", hpQSlider));
    hpQSlider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
    hpQSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, false, 50, 15);
    hpQSlider.setColour (juce::Slider::rotarySliderOutlineColourId, globalLaF.ClWidgetColours[2]);
    hpQSlider.setTooltip ("Highpass Q");

    addAndMakeVisible (cbHpMode);
    cbHpMode.addSectionHeading ("HP Mode");
    cbHpMode.addItem ("off", 1);
    cbHpMode.addItem ("6 dB/oct", 2);
    cbHpMode.addItem ("12 dB/oct", 3);
    cbHpMode.addItem ("24 dB/oct", 4);
    cbHpMode.setJustificationType (juce::Justification::centred);
    cbHpModeAttachment.reset (new ComboBoxAttachment (valueTreeState, "hpOrder", cbHpMode));
    cbHpMode.addListener (this);

    if (cbHpMode.getSelectedId() == 3)
        hpQSlider.setEnabled (true);
    else
        hpQSlider.setEnabled (false);

    addAndMakeVisible (cbFdnSize);
    cbFdnSize.addSectionHeading ("Fdn Size");
    cbFdnSize.addItem ("16", 1);
    cbFdnSize.addItem ("32", 2);
    cbFdnSize.addItem ("64", 3);
    cbFdnSize.addItem ("128", 4);
    cbFdnSize.addItem ("256", 5);
    cbFdnSize.setJustificationType (juce::Justification::centred);
    cbFdnSizeAttachment.reset (new ComboBoxAttachment (valueTreeState, "fdnSize", cbFdnSize));

    addAndMakeVisible (&freezeMode);
    freezeMode.setButtonText ("Freeze");
    addAndMakeVisible (&lbDelay);
    lbDelay.setText ("Room Size");
    addAndMakeVisible (&lbTime);
    lbTime.setText ("Rev. Time");
    addAndMakeVisible (&lbDryWet);
    lbDryWet.setText ("Dry/Wet");
    addAndMakeVisible (&lbHighCutoff);
    lbHighCutoff.setText ("Freq.");
    addAndMakeVisible (&lbHighQ);
    lbHighQ.setText ("Q");
    addAndMakeVisible (&lbHighGain);
    lbHighGain.setText ("Gain");
    addAndMakeVisible (&lbLowCutoff);
    lbLowCutoff.setText ("Freq.");
    addAndMakeVisible (&lbLowQ);
    lbLowQ.setText ("Q");
    addAndMakeVisible (&lbLowGain);
    lbLowGain.setText ("Gain");
    addAndMakeVisible (&lbHpCutoff);
    lbHpCutoff.setText ("Freq.");
    addAndMakeVisible (&lbHpQ);
    lbHpQ.setText ("Q");
    addAndMakeVisible (fdnSize);
    fdnSize.setText ("Fdn Size");
    addAndMakeVisible (&fdnLbTime);
    fdnLbTime.setText ("Fade In");

    // left side
    addAndMakeVisible (&tv);
    addAndMakeVisible (&fv);

    auto fdnPtr = processor.getFdnPtr();

    if (fdnPtr != nullptr)
    {
        fdnPtr->updateGuiCoefficients();
        tv.addCoefficients (fdnPtr->getCoefficientsForGui (0),
                            globalLaF.ClWidgetColours[2],
                            &hpCutoffSlider);
        tv.addCoefficients (fdnPtr->getCoefficientsForGui (1),
                            juce::Colours::orangered,
                            &lowCutoffSlider,
                            &lowGainSlider);
        tv.addCoefficients (fdnPtr->getCoefficientsForGui (2),
                            juce::Colours::cyan,
                            &highCutoffSlider,
                            &highGainSlider);

        fv.addCoefficients (fdnPtr->getCoefficientsForGui (0),
                            globalLaF.ClWidgetColours[2],
                            &hpCutoffSlider,
                            nullptr,
                            &hpQSlider);
        fv.addCoefficients (fdnPtr->getCoefficientsForGui (1),
                            globalLaF.ClWidgetColours[3],
                            &lowCutoffSlider,
                            &lowGainSlider,
                            &lowQSlider);
        fv.addCoefficients (fdnPtr->getCoefficientsForGui (2),
                            globalLaF.ClWidgetColours[0],
                            &highCutoffSlider,
                            &highGainSlider,
                            &highQSlider);
    }

    float gain = pow (10.0, -3.0 / revTimeSlider.getValue());
    tv.setOverallGain (gain);
    fv.setOverallGain (gain);

    startTimer (20);
}

FdnReverbAudioProcessorEditor::~FdnReverbAudioProcessorEditor()
{
    setLookAndFeel (nullptr);
}

//==============================================================================
void FdnReverbAudioProcessorEditor::paint (juce::Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (globalLaF.ClBackground);
}

void FdnReverbAudioProcessorEditor::timerCallback()
{
    auto fdnPtr = processor.getFdnPtr();
    if (fdnPtr != nullptr && fdnPtr->repaintFV)
    {
        updateVisualizers();
        fdnPtr->repaintFV = false;
    }
}

void FdnReverbAudioProcessorEditor::sliderValueChanged (juce::Slider* slider)
{
    if (slider == &revTimeSlider)
    {
        float gain = pow (10.0, -3.0 / revTimeSlider.getValue());
        fv.setOverallGain (gain);
        tv.setOverallGain (gain);

        updateVisualizers();
    }
}

void FdnReverbAudioProcessorEditor::comboBoxChanged (juce::ComboBox* comboBox)
{
    if (comboBox == &cbHpMode)
    {
        if (cbHpMode.getSelectedId() == 3)
            hpQSlider.setEnabled (true);
        else
            hpQSlider.setEnabled (false);
    }
}

void FdnReverbAudioProcessorEditor::updateVisualizers()
{
    auto fdnPtr = processor.getFdnPtr();

    if (fdnPtr != nullptr && processor.getSampleRate() > 0)
    {
        fdnPtr->updateGuiCoefficients();

        fv.setSampleRate (processor.getSampleRate());
        tv.setSampleRate (processor.getSampleRate());

        for (int i = 0; i < 3; ++i)
        {
            fv.replaceCoefficients (i, fdnPtr->getCoefficientsForGui (i));
            tv.replaceCoefficients (i, fdnPtr->getCoefficientsForGui (i));
        }
    }

    fv.repaint();
    tv.repaint();
}

void FdnReverbAudioProcessorEditor::resized()
{
    // This is generally where you'll want to lay out the positions of any
    // subcomponents in your editor..
    const int leftRightMargin = 30;
    const int headerHeight = 60;
    const int footerHeight = 25;
    const int rotSliderHeight = 55;
    const int rotSliderSpacing = 10;
    const int labelHeight = 20;

    juce::Rectangle<int> area (getLocalBounds());
    juce::Rectangle<int> footerArea (area.removeFromBottom (footerHeight));
    footer.setBounds (footerArea);

    area.removeFromLeft (leftRightMargin);
    area.removeFromRight (leftRightMargin);
    juce::Rectangle<int> headerArea = area.removeFromTop (headerHeight);
    title.setBounds (headerArea);
    area.removeFromTop (10);
    area.removeFromBottom (5);

    { //====================== DELAY SETTINGS GROUP ==================================
        const int rotSliderWidth = 55;

        juce::Rectangle<int> settingsArea (
            area.removeFromRight (2 * rotSliderWidth + rotSliderSpacing));
        delayGroup.setBounds (settingsArea);
        settingsArea.removeFromTop (25); //for box headline

        juce::Rectangle<int> sliderRow (settingsArea.removeFromTop (rotSliderHeight));

        delayLengthSlider.setBounds (sliderRow.removeFromLeft (rotSliderWidth));
        sliderRow.removeFromLeft (rotSliderSpacing);
        revTimeSlider.setBounds (sliderRow.removeFromLeft (rotSliderWidth));
        sliderRow.removeFromLeft (rotSliderSpacing);

        sliderRow = settingsArea.removeFromTop (labelHeight);
        lbDelay.setBounds (sliderRow.removeFromLeft (rotSliderWidth));
        sliderRow.removeFromLeft (rotSliderSpacing);
        lbTime.setBounds (sliderRow.removeFromLeft (rotSliderWidth));

        settingsArea.removeFromTop (25);

        sliderRow = settingsArea.removeFromTop (rotSliderHeight);
        auto cbArea = sliderRow.removeFromLeft (rotSliderWidth);
        cbFdnSize.setBounds (cbArea.removeFromBottom (24));
        cbArea.removeFromBottom (5);
        freezeMode.setBounds (cbArea.removeFromBottom (24));
        sliderRow.removeFromLeft (rotSliderSpacing);
        fadeInSlider.setBounds (sliderRow.removeFromLeft (rotSliderWidth));
        sliderRow.removeFromLeft (rotSliderSpacing);

        sliderRow = settingsArea.removeFromTop (labelHeight);
        fdnSize.setBounds (sliderRow.removeFromLeft (rotSliderWidth));
        sliderRow.removeFromLeft (rotSliderSpacing);
        fdnLbTime.setBounds (sliderRow.removeFromLeft (rotSliderWidth));

        settingsArea.removeFromTop (25);

        sliderRow = settingsArea.removeFromTop (rotSliderHeight);
        dryWetSlider.setBounds (sliderRow.removeFromRight (rotSliderWidth));
        sliderRow = settingsArea.removeFromTop (labelHeight);
        lbDryWet.setBounds (sliderRow.removeFromRight (rotSliderWidth));
    }

    area.removeFromRight (10); //spacing

    const int height = (area.getHeight() - 10 - labelHeight - rotSliderHeight + 10) / 2;
    { //====================== T60 GROUP ==================================
        juce::Rectangle<int> t60Area (area.removeFromTop (height));
        t60Group.setBounds (t60Area);
        t60Area.removeFromTop (25);
        tv.setBounds (t60Area);
    }

    area.removeFromTop (10); //spacing

    { //====================== FILTER GROUP ==================================
        const int rotSliderWidth = 40;
        const int filterSliderHeight = rotSliderHeight - 5;
        juce::Rectangle<int> filterArea (area);
        filterGroup.setBounds (filterArea);
        filterArea.removeFromTop (25);

        juce::FlexBox flexboxUI;

        juce::Array<juce::FlexItem> uiItems;
        juce::Array<juce::FlexItem> lbItems;

        flexboxUI.flexDirection = juce::FlexBox::Direction::row;
        flexboxUI.flexWrap = juce::FlexBox::Wrap::noWrap;
        flexboxUI.alignContent = juce::FlexBox::AlignContent::center;
        flexboxUI.justifyContent = juce::FlexBox::JustifyContent::spaceBetween;
        flexboxUI.alignItems = juce::FlexBox::AlignItems::center;

        juce::FlexBox flexboxLb = flexboxUI;

        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, hpCutoffSlider));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, hpQSlider));
        uiItems.add (juce::FlexItem ((int) rotSliderWidth * 2, 24, cbHpMode));
        uiItems.add (juce::FlexItem ((int) rotSliderWidth / 2, filterSliderHeight));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, lowCutoffSlider));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, lowQSlider));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, lowGainSlider));
        uiItems.add (juce::FlexItem ((int) rotSliderWidth / 2, filterSliderHeight));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, highCutoffSlider));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, highQSlider));
        uiItems.add (juce::FlexItem (rotSliderWidth, filterSliderHeight, highGainSlider));

        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbHpCutoff));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbHpQ));
        lbItems.add (juce::FlexItem ((int) rotSliderWidth * 2, labelHeight));
        lbItems.add (juce::FlexItem ((int) rotSliderWidth / 2, labelHeight));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbLowCutoff));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbLowQ));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbLowGain));
        lbItems.add (juce::FlexItem ((int) rotSliderWidth / 2, labelHeight));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbHighCutoff));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbHighQ));
        lbItems.add (juce::FlexItem (rotSliderWidth, labelHeight, lbHighGain));

        juce::Rectangle<int> sliderRow (filterArea.removeFromBottom (labelHeight));
        flexboxLb.items = lbItems;
        flexboxLb.performLayout (sliderRow);

        sliderRow = filterArea.removeFromBottom (rotSliderHeight);
        flexboxUI.items = uiItems;
        flexboxUI.performLayout (sliderRow);

        fv.setBounds (filterArea);
    }
}
