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

    FxChain.cpp
    Created: 5 Jan 2024 3:26:37pm
    Author:  binya

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

#include <JuceHeader.h>

#include "FxChain.h"

void FxChainShadows::resized()
{
    int innerShadowOffset = int(0.001f * getWidth());
    innerShadow.setOffset({ 0, innerShadowOffset }, 0);
    innerShadow.setOffset({ 0, -innerShadowOffset }, 1);
    dragShadow.setOffset({ innerShadowOffset, innerShadowOffset });
}

void FxChainShadows::paint(juce::Graphics& g)
{
    juce::Path innerShadowPath;
    innerShadowPath.addRectangle(getLocalBounds());
    innerShadow.render(g, innerShadowPath);

    auto parent = dynamic_cast<FxChain*>(getParentComponent());
    if (parent->isChainDragging())
    {
        juce::Path dragShadowPath;
        dragShadowPath.addRectangle(parent->getDragComp()->getBounds());
        dragShadow.render(g, dragShadowPath);
    }
}

void FxChainShadows::lookAndFeelChanged()
{
    auto colors = getTheme();

    innerShadow.setColor(colors.slate.withAlpha(0.25f), 0);
    innerShadow.setColor(colors.slate.withAlpha(0.25f), 1);
    dragShadow.setColor(colors.slate.withAlpha(0.125f), 0);
}

FxChain::FxChain(JustaSampleAudioProcessor& processor) :
    reverbDisplay(processor.APVTS()),
    distortionDisplay(processor.APVTS(), int(processor.getSampleRate())),
    eqDisplay(processor.APVTS(), int(processor.getSampleRate())),
    chorusDisplay(processor.APVTS(), int(processor.getSampleRate())),

    reverbModule(this, processor.APVTS(), "Reverb", PluginParameters::REVERB, PluginParameters::REVERB_ENABLED, PluginParameters::REVERB_MIX, reverbDisplay),
    distortionModule(this, processor.APVTS(), "Distortion", PluginParameters::DISTORTION, PluginParameters::DISTORTION_ENABLED, PluginParameters::DISTORTION_MIX, distortionDisplay),
    eqModule(this, processor.APVTS(), "Equalizer", PluginParameters::EQ, PluginParameters::EQ_ENABLED, eqDisplay),
    chorusModule(this, processor.APVTS(), "Chorus", PluginParameters::CHORUS, PluginParameters::CHORUS_ENABLED, PluginParameters::CHORUS_MIX, chorusDisplay),

    fxPermAttachment(*processor.APVTS().getParameter(PluginParameters::FX_PERM), [&](float newValue) { moduleOrder = PluginParameters::paramToPerm(int(newValue)); oldVal = int(newValue); resized(); }, &processor.getUndoManager())
{
    reverbModule.addRotary(PluginParameters::REVERB_SIZE, "Size", { 11.4f, 82.f }, 87.f);
    reverbModule.addRotary(PluginParameters::REVERB_DAMPING, "Damping", { 108.4f, 52.f }, 87.f);
    reverbModule.addRotary(PluginParameters::REVERB_PREDELAY, "Delay", { 208.f, 82.f }, 87.f, PluginParameters::TIME_UNIT);
    reverbModule.addRotary(PluginParameters::REVERB_LOWS, "Lows", { 302.f, 52.f }, 87.f);
    reverbModule.addRotary(PluginParameters::REVERB_HIGHS, "Highs", { 399.4f, 82.f }, 87.f);
    addAndMakeVisible(reverbModule);

    distortionModule.addRotary(PluginParameters::DISTORTION_DENSITY, "Density", { 99.f, 46.f }, 110.f);
    distortionModule.addRotary(PluginParameters::DISTORTION_HIGHPASS, "Highpass", { 289.f, 46.f }, 110.f);
    addAndMakeVisible(distortionModule);

    eqModule.addRotary(PluginParameters::EQ_LOW_GAIN, "Lows", { 44.5f, 46.f }, 110.f, PluginParameters::VOLUME_UNIT);
    eqModule.addRotary(PluginParameters::EQ_MID_GAIN, "Mids", { 194.f, 46.f }, 110.f, PluginParameters::VOLUME_UNIT);
    eqModule.addRotary(PluginParameters::EQ_HIGH_GAIN, "Highs", { 344.f, 46.f }, 110.f, PluginParameters::VOLUME_UNIT);
    addAndMakeVisible(eqModule);

    chorusModule.addRotary(PluginParameters::CHORUS_RATE, "Rate", { 40.f, 82.f }, 87.f, PluginParameters::FREQUENCY_UNIT);
    chorusModule.addRotary(PluginParameters::CHORUS_DEPTH, "Depth", { 150.f, 52.f }, 87.f);
    chorusModule.addRotary(PluginParameters::CHORUS_CENTER_DELAY, "Delay", { 260.6f, 82.f }, 87.f, PluginParameters::TIME_UNIT);
    chorusModule.addRotary(PluginParameters::CHORUS_FEEDBACK, "Feedback", { 371.f, 52.f }, 87.f);
    addAndMakeVisible(chorusModule);

    shadows.setInterceptsMouseClicks(false, false);
    addAndMakeVisible(shadows);

    fxPermAttachment.sendInitialUpdate();
    addMouseListener(this, true);
}

void FxChain::paint(juce::Graphics& g)
{
    auto colors = getTheme();

    g.setColour(colors.slate);

    auto bounds = getLocalBounds();

    auto dividerWidth = int(std::ceil(getWidth() * Layout::fxChainDivider));
    auto moduleWidth = int(std::round((bounds.getWidth() - 3 * dividerWidth) / 4.f));
    for (size_t i = 0; i < moduleOrder.size() - 1; i++)
    {
        bounds.removeFromLeft(moduleWidth);
        auto divider = bounds.removeFromLeft(dividerWidth);
        g.fillRect(divider);
    }
}

void FxChain::resized()
{
    auto bounds = getLocalBounds();

    auto dividerWidth = int(std::ceil(getWidth() * Layout::fxChainDivider));
    auto moduleWidth = int(std::round((bounds.getWidth() - 3 * dividerWidth) / 4.f));
    for (const auto& fxType : moduleOrder)
    {
        auto& module = getModule(fxType);
        auto moduleBounds = bounds.removeFromLeft(moduleWidth);
        bounds.removeFromLeft(dividerWidth);
        if (!(dragging && &module == dragComp))
            module.setBounds(moduleBounds.toNearestInt());
    }

    shadows.setBounds(getLocalBounds());

    if (dragging)
    {
        auto dragCompBounds = dragComp->getBounds();
        dragCompBounds.setPosition(juce::jlimit<int>(0, getWidth() - dragComp->getWidth(), mouseX + dragOffset), 0);
        dragComp->setBounds(dragCompBounds);
        repaint();
    }
}

void FxChain::mouseDrag(const juce::MouseEvent& event)
{
    if (!dragging)
        return;

    auto localEvent = event.getEventRelativeTo(this);
    mouseX = localEvent.x;
    auto dragCompBounds = dragComp->getBounds();
    dragCompBounds.setPosition(juce::jlimit<int>(0, getWidth() - dragComp->getWidth(), mouseX + dragOffset), 0);

    // See if the chain's module order needs to be updated
    auto bounds = getLocalBounds();
    auto moduleWidth = bounds.getWidth() / 4;
    for (int i = 0; i < 4; i++)
    {
        auto moduleBounds = bounds.removeFromLeft(moduleWidth);
        if (i != dragCompIndex && moduleBounds.getX() <= dragCompBounds.getCentreX() && dragCompBounds.getCentreX() <= moduleBounds.getRight())
        {
            auto fxType = moduleOrder[i];
            moduleOrder[i] = moduleOrder[dragCompIndex];
            moduleOrder[dragCompIndex] = fxType;
            dragCompIndex = i;
        }
    }

    resized();
}

void FxChain::dragStarted(Component* comp, const juce::MouseEvent& event)
{
    dragging = true;
    dragComp = comp;
    dragTarget = dynamic_cast<FxModule*>(comp)->getEffectType();
    
    for (int i = 0; i < 4; i++)
        if (&getModule(moduleOrder[i]) == dragComp)
            dragCompIndex = i;

    shadows.toFront(true);
    dragComp->toFront(true);
    mouseX = dragComp->getX();
    dragOffset = dragComp->getX() - event.getEventRelativeTo(this).x;
}

void FxChain::dragEnded()
{
    dragging = false;
    int newVal = permToParam(moduleOrder);
    if (newVal != oldVal)
        fxPermAttachment.setValueAsCompleteGesture(float(newVal));
    dragComp->toBehind(&shadows);

    resized();
    repaint();
}

void FxChain::enablementChanged()
{
    reverbModule.setEnabled(isEnabled());
    distortionModule.setEnabled(isEnabled());
    eqModule.setEnabled(isEnabled());
    chorusModule.setEnabled(isEnabled());
}

FxModule& FxChain::getModule(PluginParameters::FxTypes type)
{
    switch (type)
    {
    case PluginParameters::REVERB:
        return reverbModule;
    case PluginParameters::DISTORTION:
        return distortionModule;
    case PluginParameters::EQ:
        return eqModule;
    case PluginParameters::CHORUS:
    default:
        return chorusModule;
    }
}
