// Copyright 2025 tilr

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

TIME12AudioProcessorEditor::TIME12AudioProcessorEditor (TIME12AudioProcessor& p)
    : AudioProcessorEditor (&p)
    , audioProcessor (p)
{
    audioProcessor.loadSettings(); // load saved paint patterns from other plugin instances
    setResizable(true, false);
    setResizeLimits(PLUG_WIDTH, PLUG_HEIGHT, MAX_PLUG_WIDTH, MAX_PLUG_HEIGHT);
    setSize (audioProcessor.plugWidth, audioProcessor.plugHeight);
    setScaleFactor(audioProcessor.scale);

    audioProcessor.addChangeListener(this);
    audioProcessor.params.addParameterListener("sync", this);
    audioProcessor.params.addParameterListener("trigger", this);

    auto col = PLUG_PADDING;
    auto row = PLUG_PADDING;

    // FIRST ROW

    addAndMakeVisible(logoLabel);
    logoLabel.setColour(juce::Label::ColourIds::textColourId, Colours::white);
    logoLabel.setFont(FontOptions(26.0f));
    logoLabel.setText("TIME-12", NotificationType::dontSendNotification);
    logoLabel.setBounds(col, row-3, 100, 30);
    col += 110;

#if defined(DEBUG)
    addAndMakeVisible(presetExport);
    presetExport.setAlpha(0.f);
    presetExport.setTooltip("DEBUG ONLY - Exports preset to debug console");
    presetExport.setButtonText("Export");
    presetExport.setBounds(10, 10, 100, 25);
    presetExport.onClick = [this] {
        std::ostringstream oss;
        auto points = audioProcessor.viewPattern->points;
        //auto points = audioProcessor.getPaintPatern(audioProcessor.paintTool)->points;
        for (const auto& point : points) {
            oss << point.x << " " << point.y << " " << point.tension << " " << point.type << " ";
        }
        DBG(oss.str() << "\n");
    };
#endif

    addAndMakeVisible(syncMenu);
    syncMenu.setTooltip("Tempo sync");
    syncMenu.addSectionHeading("Tempo Sync");
    syncMenu.addItem("Rate Hz", 1);
    syncMenu.addSectionHeading("Straight");
    syncMenu.addItem("1/256", 2);
    syncMenu.addItem("1/128", 3);
    syncMenu.addItem("1/64", 4);
    syncMenu.addItem("1/32", 5);
    syncMenu.addItem("1/16", 6);
    syncMenu.addItem("1/8", 7);
    syncMenu.addItem("1/4", 8);
    syncMenu.addItem("1/2", 9);
    syncMenu.addItem("1 Bar", 10);
    syncMenu.addItem("2 Bars", 11);
    syncMenu.addItem("4 Bars", 12);
    syncMenu.addSectionHeading("Triplet");
    syncMenu.addItem("1/16T", 13);
    syncMenu.addItem("1/8T", 14);
    syncMenu.addItem("1/4T", 15);
    syncMenu.addItem("1/2T", 16);
    syncMenu.addItem("1/1T", 17);
    syncMenu.addSectionHeading("Dotted");
    syncMenu.addItem("1/16.", 18);
    syncMenu.addItem("1/8.", 19);
    syncMenu.addItem("1/4.", 20);
    syncMenu.addItem("1/2.", 21);
    syncMenu.addItem("1/1.", 22);
    syncMenu.setBounds(col, row, 90, 25);
    syncAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(audioProcessor.params, "sync", syncMenu);
    col += 100;

    rateDial = std::make_unique<TextDial>(p, "rate", "", "", TextDialLabel::tdRateHz, 16.f, 0xffffffff);
    addAndMakeVisible(*rateDial);
    rateDial->setBounds(col, row, 50, 25);
    col += 60;

    addAndMakeVisible(triggerLabel);
    triggerLabel.setColour(juce::Label::ColourIds::textColourId, Colour(COLOR_NEUTRAL_LIGHT));
    triggerLabel.setFont(FontOptions(16.0f));
    triggerLabel.setJustificationType(Justification::centredRight);
    triggerLabel.setText("Trigger", NotificationType::dontSendNotification);
    triggerLabel.setBounds(col, row, 60, 25);
    col += 70;

    addAndMakeVisible(triggerMenu);
    triggerMenu.setTooltip("Envelope trigger:\nSync - song playback\nMIDI - midi notes\nAudio - audio input");
    triggerMenu.addSectionHeading("Trigger");
    triggerMenu.addItem("Sync", 1);
    triggerMenu.addItem("MIDI", 2);
    triggerMenu.addItem("Audio", 3);
    triggerMenu.setBounds(col, row, 75, 25);
    triggerAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(audioProcessor.params, "trigger", triggerMenu);
    col += 85;

    addAndMakeVisible(algoMenu);
    algoMenu.setTooltip("Algorithm used for transient detection");
    algoMenu.addItem("Simple", 1);
    algoMenu.addItem("Drums", 2);
    algoMenu.setBounds(col,row,75,25);
    algoMenu.setColour(ComboBox::arrowColourId, Colour(COLOR_AUDIO));
    algoMenu.setColour(ComboBox::textColourId, Colour(COLOR_AUDIO));
    algoMenu.setColour(ComboBox::outlineColourId, Colour(COLOR_AUDIO));
    algoAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(audioProcessor.params, "algo", algoMenu);
    col += 85;

    addAndMakeVisible(audioSettingsButton);
    audioSettingsButton.setBounds(col, row, 25, 25);
    audioSettingsButton.onClick = [this]() {
        audioProcessor.showAudioKnobs = !audioProcessor.showAudioKnobs;
        if (audioProcessor.showKnobs != audioProcessor.showAudioKnobs)
            audioProcessor.toggleShowKnobs();

        MessageManager::callAsync([this]{ toggleUIComponents(); });
    };
    audioSettingsButton.setAlpha(0.0f);
    col += 35;

    // FIRST ROW RIGHT

    col = getWidth() - PLUG_PADDING;
    settingsButton = std::make_unique<SettingsButton>(p);
    addAndMakeVisible(*settingsButton);
    settingsButton->onScaleChange = [this]() { setScaleFactor(audioProcessor.scale); };
    settingsButton->toggleUIComponents = [this]() { toggleUIComponents(); };
    settingsButton->toggleAbout = [this]() { about.get()->setVisible(true); };
    settingsButton->setBounds(col-15,row,25,25);

    col -= 25;
    mixDial = std::make_unique<TextDial>(p, "mix", "Mix ", "", TextDialLabel::tdMix, 14.f, COLOR_NEUTRAL_LIGHT);
    addAndMakeVisible(*mixDial);
    mixDial->setBounds(col - 30, row, 30, 25);

    // SECOND ROW

    row += 35;
    col = PLUG_PADDING;
    for (int i = 0; i < 12; ++i) {
        auto btn = std::make_unique<TextButton>(std::to_string(i + 1));
        btn->setRadioGroupId (1337);
        btn->setToggleState(audioProcessor.pattern->index == i, dontSendNotification);
        btn->setClickingTogglesState (false);
        btn->setColour (TextButton::textColourOffId,  Colour(COLOR_BG));
        btn->setColour (TextButton::textColourOnId,   Colour(COLOR_BG));
        btn->setColour (TextButton::buttonColourId,   Colour(COLOR_ACTIVE).darker(0.8f));
        btn->setColour (TextButton::buttonOnColourId, Colour(COLOR_ACTIVE));
        btn->setBounds (col + i * 22, row, 22+1, 25); // width +1 makes it seamless on higher DPI
        btn->setConnectedEdges (((i != 0) ? Button::ConnectedOnLeft : 0) | ((i != 11) ? Button::ConnectedOnRight : 0));
        btn->setComponentID(i == 0 ? "leftPattern" : i == 11 ? "rightPattern" : "pattern");
        btn->onClick = [i, this]() {
            MessageManager::callAsync([i, this] {
                audioProcessor.queuePattern(i+1);
            });
        };
        addAndMakeVisible(*btn);
        patterns.push_back(std::move(btn));
    }
    col += 265 + 10;

    addAndMakeVisible(patSyncLabel);
    patSyncLabel.setColour(juce::Label::ColourIds::textColourId, Colour(COLOR_NEUTRAL_LIGHT));
    patSyncLabel.setFont(FontOptions(16.0f));
    patSyncLabel.setText("Pat. Sync", NotificationType::dontSendNotification);
    patSyncLabel.setJustificationType(Justification::centred);
    patSyncLabel.setBounds(col, row, 80, 25);
    col += 90;

    addAndMakeVisible(patSyncMenu);
    patSyncMenu.setTooltip("Changes pattern in sync with song position during playback");
    patSyncMenu.addSectionHeading("Pattern Sync");
    patSyncMenu.addItem("Off", 1);
    patSyncMenu.addItem("1/4 Beat", 2);
    patSyncMenu.addItem("1/2 Beat", 3);
    patSyncMenu.addItem("1 Beat", 4);
    patSyncMenu.addItem("2 Beats", 5);
    patSyncMenu.addItem("4 Beats", 6);
    patSyncMenu.setBounds(col, row, 75, 25);
    patSyncAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(audioProcessor.params, "patsync", patSyncMenu);

    // SECOND ROW RIGHT
    col = getWidth() - PLUG_PADDING;

    // AUDIO KNOBS
    row += 35;
    col = PLUG_PADDING;

    threshold = std::make_unique<Rotary>(p, "threshold", "Thres", RotaryLabel::gainTodB1f, false, true);
    addAndMakeVisible(*threshold);
    threshold->setBounds(col,row,80,65);
    col += 75;

    sense = std::make_unique<Rotary>(p, "sense", "Sense", RotaryLabel::percx100, false, true);
    addAndMakeVisible(*sense);
    sense->setBounds(col,row,80,65);
    col += 75;

    lowcut = std::make_unique<Rotary>(p, "lowcut", "Low Cut", RotaryLabel::hzHp, false, true);
    addAndMakeVisible(*lowcut);
    lowcut->setBounds(col,row,80,65);
    col += 75;

    highcut = std::make_unique<Rotary>(p, "highcut", "Hi Cut", RotaryLabel::hzLp, false, true);
    addAndMakeVisible(*highcut);
    highcut->setBounds(col,row,80,65);
    col += 75;

    offset = std::make_unique<Rotary>(p, "offset", "Offset", RotaryLabel::audioOffset, true, true);
    addAndMakeVisible(*offset);
    offset->setBounds(col,row,80,65);
    col += 75;

    audioDisplay = std::make_unique<AudioDisplay>(p);
    addAndMakeVisible(*audioDisplay);
    audioDisplay->setBounds(col,row,getWidth() - col - PLUG_PADDING - 80 - 10, 65);

    col = getWidth() - PLUG_PADDING - 80;
    addAndMakeVisible(useSidechain);
    useSidechain.setTooltip("Use sidechain for transient detection");
    useSidechain.setButtonText("Sidechain");
    useSidechain.setComponentID("button");
    useSidechain.setColour(TextButton::buttonColourId, Colour(COLOR_AUDIO));
    useSidechain.setColour(TextButton::buttonOnColourId, Colour(COLOR_AUDIO));
    useSidechain.setColour(TextButton::textColourOnId, Colour(COLOR_BG));
    useSidechain.setColour(TextButton::textColourOffId, Colour(COLOR_AUDIO));
    useSidechain.setBounds(col,row,80,25);
    useSidechain.onClick = [this]() {
        MessageManager::callAsync([this] {
            audioProcessor.useSidechain = !audioProcessor.useSidechain;
            toggleUIComponents();
        });
    };

    addAndMakeVisible(useMonitor);
    useMonitor.setTooltip("Monitor signal used for transient detection");
    useMonitor.setButtonText("Monitor");
    useMonitor.setComponentID("button");
    useMonitor.setColour(TextButton::buttonColourId, Colour(COLOR_AUDIO));
    useMonitor.setColour(TextButton::buttonOnColourId, Colour(COLOR_AUDIO));
    useMonitor.setColour(TextButton::textColourOnId, Colour(COLOR_BG));
    useMonitor.setColour(TextButton::textColourOffId, Colour(COLOR_AUDIO));
    useMonitor.setBounds(col,row+35,80,25);
    useMonitor.onClick = [this]() {
        MessageManager::callAsync([this] {
            audioProcessor.useMonitor = !audioProcessor.useMonitor;
            toggleUIComponents();
        });
    };

    // THIRD ROW
    col = PLUG_PADDING;
    row += 75;
    addAndMakeVisible(paintButton);
    paintButton.setButtonText("Paint");
    paintButton.setComponentID("button");
    paintButton.setBounds(col, row, 75, 25);
    paintButton.onClick = [this]() {
        if (audioProcessor.uimode == UIMode::PaintEdit && audioProcessor.luimode == UIMode::Paint) {
            audioProcessor.setUIMode(UIMode::Normal);
        }
        else {
            audioProcessor.togglePaintMode();
        }
    };
    col += 85;

    addAndMakeVisible(sequencerButton);
    sequencerButton.setButtonText("Seq");
    sequencerButton.setComponentID("button");
    sequencerButton.setBounds(col, row, 75, 25);
    sequencerButton.onClick = [this]() {
        if (audioProcessor.uimode == UIMode::PaintEdit && audioProcessor.luimode == UIMode::Seq) {
            audioProcessor.setUIMode(UIMode::Normal);
        }
        else {
            audioProcessor.toggleSequencerMode();
        }
    };

    col += 85;
    addAndMakeVisible(pointLabel);
    pointLabel.setText("p", dontSendNotification);
    pointLabel.setBounds(col-2,row,25,25);
    pointLabel.setVisible(false);
    col += 35-4;

    addAndMakeVisible(pointMenu);
    pointMenu.setTooltip("Point mode\nRight click points to change mode");
    pointMenu.addSectionHeading("Point Mode");
    pointMenu.addItem("Hold", 1);
    pointMenu.addItem("Curve", 2);
    pointMenu.addItem("S-Curve", 3);
    pointMenu.addItem("Pulse", 4);
    pointMenu.addItem("Wave", 5);
    pointMenu.addItem("Triangle", 6);
    pointMenu.addItem("Stairs", 7);
    pointMenu.addItem("Smooth St", 8);
    pointMenu.addItem("Half Sine", 9);
    pointMenu.setBounds(col, row, 75, 25);
    pointMenu.setSelectedId(audioProcessor.pointMode + 1, dontSendNotification);
    pointMenu.onChange = [this]() {
        MessageManager::callAsync([this]() {
            audioProcessor.pointMode = pointMenu.getSelectedId() - 1;
        });
    };
    col += 85;

    addAndMakeVisible(loopButton);
    loopButton.setTooltip("Toggle continuous play");
    loopButton.setColour(TextButton::buttonColourId, Colours::transparentWhite);
    loopButton.setColour(ComboBox::outlineColourId, Colours::transparentWhite);
    loopButton.setBounds(col, row, 25, 25);
    loopButton.onClick = [this]() {
        MessageManager::callAsync([this] {
            audioProcessor.alwaysPlaying = !audioProcessor.alwaysPlaying;
            repaint();
        });
    };
    col += 35;

    thirdRow.push_back(paintButton);
    thirdRow.push_back(loopButton);
    thirdRow.push_back(sequencerButton);
    thirdRow.push_back(pointLabel);
    thirdRow.push_back(pointMenu);
    thirdRow.push_back(loopButton);


    // THIRD ROW RIGHT
    col = getWidth() - PLUG_PADDING - 60;

    addAndMakeVisible(snapButton);
    snapButton.setTooltip("Toggle snap by using ctrl key");
    snapButton.setButtonText("Snap");
    snapButton.setComponentID("button");
    snapButton.setBounds(col, row, 60, 25);
    snapButton.setClickingTogglesState(true);
    snapAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(audioProcessor.params, "snap", snapButton);

    col -= 60;
    gridSelector = std::make_unique<GridSelector>(p);
    gridSelector.get()->setTooltip("Grid size can also be set using mouse wheel on view");
    addAndMakeVisible(*gridSelector);
    gridSelector->setBounds(col,row,50,25);

    col -= 10+15+5;
    addAndMakeVisible(nudgeRightButton);
    nudgeRightButton.setAlpha(0.f);
    nudgeRightButton.setBounds(col, row, 20, 25);
    nudgeRightButton.onClick = [this]() {
        MessageManager::callAsync([this] {
            if (audioProcessor.uimode == UIMode::Seq) {
                audioProcessor.sequencer->rotateRight();
                return;
            }
            double grid = (double)audioProcessor.getCurrentGrid();
            auto snapshot = audioProcessor.viewPattern->points;
            audioProcessor.viewPattern->rotate(1.0/grid);
            audioProcessor.viewPattern->buildSegments();
            audioProcessor.createUndoPointFromSnapshot(snapshot);
        });
    };

    col -= 10+15-5;
    addAndMakeVisible(nudgeLeftButton);
    nudgeLeftButton.setAlpha(0.f);
    nudgeLeftButton.setBounds(col, row, 20, 25);
    nudgeLeftButton.onClick = [this]() {
        MessageManager::callAsync([this] {
            if (audioProcessor.uimode == UIMode::Seq) {
                audioProcessor.sequencer->rotateLeft();
                return;
            }
            double grid = (double)audioProcessor.getCurrentGrid();
            auto snapshot = audioProcessor.viewPattern->points;
            audioProcessor.viewPattern->rotate(-1.0/grid);
            audioProcessor.viewPattern->buildSegments();
            audioProcessor.createUndoPointFromSnapshot(snapshot);
        });
    };

    col -= 30;
    addAndMakeVisible(redoButton);
    redoButton.setButtonText("redo");
    redoButton.setComponentID("button");
    redoButton.setBounds(col, row, 20, 25);
    redoButton.setAlpha(0.f);
    redoButton.onClick = [this]() {
        MessageManager::callAsync([this] {
            if (audioProcessor.uimode == UIMode::Seq) {
                audioProcessor.sequencer->redo();
            }
            else {
                audioProcessor.viewPattern->redo();
            }
            repaint();
        });
    };

    col -= 35;
    addAndMakeVisible(undoButton);
    undoButton.setButtonText("undo");
    undoButton.setComponentID("button");
    undoButton.setBounds(col, row, 20, 25);
    undoButton.setAlpha(0.f);
    undoButton.onClick = [this]() {
        MessageManager::callAsync([this] {
            if (audioProcessor.uimode == UIMode::Seq) {
                audioProcessor.sequencer->undo();
            }
            else {
                audioProcessor.viewPattern->undo();
            }
            repaint();
        });
    };
    row += 35;

    thirdRow.push_back(snapButton);
    thirdRow.push_back(nudgeRightButton);
    thirdRow.push_back(nudgeLeftButton);
    thirdRow.push_back(redoButton);
    thirdRow.push_back(undoButton);
    thirdRow.push_back(*gridSelector);

    // PAINT TOOL
    col = PLUG_PADDING;
    paintWidget = std::make_unique<PaintToolWidget>(p);
    addAndMakeVisible(*paintWidget);
    paintWidget->setBounds(col,row,PLUG_WIDTH - PLUG_PADDING * 2, 40);

    row += 50;
    col = PLUG_PADDING;
    seqWidget = std::make_unique<SequencerWidget>(p);
    addAndMakeVisible(*seqWidget);
    seqWidget->setBounds(col,row,PLUG_WIDTH - PLUG_PADDING*2, 25*2+10);

    // VIEW
    col = 0;
    row += 50;
    view = std::make_unique<View>(p);
    addAndMakeVisible(*view);
    view->setBounds(col,row,getWidth(), getHeight() - row);

    // ABOUT
    about = std::make_unique<About>();
    addAndMakeVisible(*about);
    about->setBounds(getBounds());
    about->setVisible(false);

    addAndMakeVisible(latencyWarning);
    latencyWarning.setText("Plugin latency has changed, restart playback", dontSendNotification);
    latencyWarning.setColour(Label::backgroundColourId, Colours::black.withAlpha(0.5f));
    latencyWarning.setJustificationType(Justification::centred);
    latencyWarning.setColour(Label::textColourId, Colour(COLOR_ACTIVE));
    latencyWarning.setBounds(view->getBounds().getCentreX() - 150, PLUG_HEIGHT - 20 - 25, 300, 25);

    customLookAndFeel = new CustomLookAndFeel();
    setLookAndFeel(customLookAndFeel);

    init = true;
    resized();
    toggleUIComponents();
}

TIME12AudioProcessorEditor::~TIME12AudioProcessorEditor()
{
    audioProcessor.saveSettings(); // save paint patterns to disk
    setLookAndFeel(nullptr);
    delete customLookAndFeel;
    audioProcessor.params.removeParameterListener("sync", this);
    audioProcessor.params.removeParameterListener("trigger", this);
    audioProcessor.removeChangeListener(this);
}

void TIME12AudioProcessorEditor::changeListenerCallback(ChangeBroadcaster* source)
{
    (void)source;
    MessageManager::callAsync([this] { toggleUIComponents(); });
}

void TIME12AudioProcessorEditor::parameterChanged (const juce::String& parameterID, float newValue)
{
    (void)parameterID;
    (void)newValue;
    MessageManager::callAsync([this]() { toggleUIComponents(); });
};

void TIME12AudioProcessorEditor::toggleUIComponents()
{
    patterns[audioProcessor.pattern->index].get()->setToggleState(true, dontSendNotification);
    auto trigger = (int)audioProcessor.params.getRawParameterValue("trigger")->load();
    auto triggerColor = trigger == 0 ? COLOR_ACTIVE : trigger == 1 ? COLOR_MIDI : COLOR_AUDIO;
    triggerMenu.setColour(ComboBox::arrowColourId, Colour(triggerColor));
    triggerMenu.setColour(ComboBox::textColourId, Colour(triggerColor));
    triggerMenu.setColour(ComboBox::outlineColourId, Colour(triggerColor));
    algoMenu.setVisible(trigger == Trigger::Audio);

    audioSettingsButton.setVisible(trigger == Trigger::Audio);
    if (!audioSettingsButton.isVisible()) {
        audioProcessor.showAudioKnobs = false;
    }

    loopButton.setVisible(trigger > 0);

    int sync = (int)audioProcessor.params.getRawParameterValue("sync")->load();
    bool showAudioKnobs = audioProcessor.showAudioKnobs;

    rateDial->setVisible(sync == 0);
    triggerLabel.setBounds(triggerLabel.getBounds().withX(rateDial->isVisible()
        ? rateDial->getBounds().getRight() + 5
        : syncMenu.getBounds().getRight() + 10
    ));
    triggerMenu.setBounds(triggerMenu.getBounds().withX(triggerLabel.getBounds().getRight() + 10));
    algoMenu.setBounds(algoMenu.getBounds().withX(triggerMenu.getBounds().getRight() + 10));
    audioSettingsButton.setBounds(audioSettingsButton.getBounds().withX(algoMenu.getBounds().getRight() + 10));

    threshold->setVisible(showAudioKnobs);
    sense->setVisible(showAudioKnobs);
    lowcut->setVisible(showAudioKnobs);
    highcut->setVisible(showAudioKnobs);
    offset->setVisible(showAudioKnobs);
    audioDisplay->setVisible(showAudioKnobs);

    useSidechain.setVisible(showAudioKnobs);
    useMonitor.setVisible(showAudioKnobs);
    useSidechain.setToggleState(audioProcessor.useSidechain, dontSendNotification);
    useMonitor.setToggleState(audioProcessor.useMonitor, dontSendNotification);

    for (auto& comp : thirdRow) {
        auto bounds = comp.get().getBounds();
        comp.get().setBounds(bounds.withY(showAudioKnobs ? threshold.get()->getBounds().getBottom() + 10 : patSyncMenu.getBottom() + 10));
    }

    paintWidget->setVisible(audioProcessor.showPaintWidget);
    paintWidget->setBounds(paintWidget->getBounds().withY(paintButton.getBottom() + 10));

    seqWidget->setVisible(audioProcessor.showSequencer);
    seqWidget->setBounds(seqWidget->getBounds().withY(paintWidget->isVisible()
        ? paintWidget->getBounds().getBottom() + 10
        : paintWidget->getBounds().getY()
    ).withWidth(getWidth() - PLUG_PADDING * 2));

    if (seqWidget->isVisible()) {
        view->setBounds(view->getBounds().withTop(seqWidget->getBottom()));
    }
    else if (paintWidget->isVisible()) {
        view->setBounds(view->getBounds().withTop(paintWidget->getBounds().getBottom()));
    }
    else {
        view->setBounds(view->getBounds().withTop(paintWidget->getBounds().getY() - 10));
    }

    latencyWarning.setVisible(audioProcessor.showLatencyWarning);

    auto uimode = audioProcessor.uimode;
    paintButton.setToggleState(uimode == UIMode::Paint || (uimode == UIMode::PaintEdit && audioProcessor.luimode == UIMode::Paint), dontSendNotification);
    paintWidget->toggleUIComponents();

    sequencerButton.setToggleState(audioProcessor.sequencer->isOpen, dontSendNotification);

    repaint();
}

//==============================================================================

void TIME12AudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll(Colour(COLOR_BG));
    auto bounds = getLocalBounds().withTop(view->getBounds().getY() + 10).withHeight(3).toFloat();
    if (audioProcessor.uimode == UIMode::Seq)
        bounds = bounds.withY((float)seqWidget->getBounds().getBottom() + 10);

    // div
    auto grad = ColourGradient(
        Colours::black.withAlpha(0.25f),
        bounds.getTopLeft(),
        Colours::transparentBlack,
        bounds.getBottomLeft(),
        false
    );
    g.setGradientFill(grad);
    g.fillRect(bounds);

    // point icon
    g.setColour(Colour(COLOR_NEUTRAL));
    g.drawEllipse(pointLabel.getBounds().expanded(-2,-2).toFloat(), 1.f);
    g.fillEllipse(pointLabel.getBounds().expanded(-10,-10).toFloat());

    // draw loop play button
    auto trigger = (int)audioProcessor.params.getRawParameterValue("trigger")->load();
    if (trigger != Trigger::Sync) {
        if (audioProcessor.alwaysPlaying) {
            g.setColour(Colours::yellow);
            auto loopBounds = loopButton.getBounds().expanded(-5);
            g.fillRect(loopBounds.removeFromLeft(5));
            loopBounds = loopButton.getBounds().expanded(-5);
            g.fillRect(loopBounds.removeFromRight(5));
        }
        else {
            g.setColour(Colour(0xff00ff00));
            juce::Path triangle;
            auto loopBounds = loopButton.getBounds().expanded(-5);
            triangle.startNewSubPath(0.0f, 0.0f);
            triangle.lineTo(0.0f, (float)loopBounds.getHeight());
            triangle.lineTo((float)loopBounds.getWidth(), loopBounds.getHeight() / 2.f);
            triangle.closeSubPath();
            g.fillPath(triangle, AffineTransform::translation((float)loopBounds.getX(), (float)loopBounds.getY()));
        }
    }

    // draw audio settings button outline
    g.setColour(Colour(COLOR_ACTIVE));
    if (audioSettingsButton.isVisible() && audioProcessor.showAudioKnobs) {
        g.setColour(Colour(COLOR_AUDIO));
        g.fillRoundedRectangle(audioSettingsButton.getBounds().toFloat(), 3.0f);
        drawGear(g, audioSettingsButton.getBounds(), 10, 6, Colour(COLOR_BG), Colour(COLOR_AUDIO));
    }
    else if (audioSettingsButton.isVisible()) {
        drawGear(g, audioSettingsButton.getBounds(), 10, 6, Colour(COLOR_AUDIO), Colour(COLOR_BG));
    }

    // draw rotate pat triangles
    g.setColour(Colour(COLOR_ACTIVE));
    auto triCenter = nudgeLeftButton.getBounds().toFloat().getCentre();
    auto triRadius = 5.f;
    juce::Path nudgeLeftTriangle;
    nudgeLeftTriangle.addTriangle(
        triCenter.translated(-triRadius, 0),
        triCenter.translated(triRadius, -triRadius),
        triCenter.translated(triRadius, triRadius)
    );
    g.fillPath(nudgeLeftTriangle);

    triCenter = nudgeRightButton.getBounds().toFloat().getCentre();
    juce::Path nudgeRightTriangle;
    nudgeRightTriangle.addTriangle(
        triCenter.translated(-triRadius, -triRadius),
        triCenter.translated(-triRadius, triRadius),
        triCenter.translated(triRadius, 0)
    );
    g.fillPath(nudgeRightTriangle);

    // draw undo redo buttons
    auto canUndo = audioProcessor.uimode == UIMode::Seq
        ? !audioProcessor.sequencer->undoStack.empty()
        : !audioProcessor.viewPattern->undoStack.empty();

    auto canRedo = audioProcessor.uimode == UIMode::Seq
        ? !audioProcessor.sequencer->redoStack.empty()
        : !audioProcessor.viewPattern->redoStack.empty();

    drawUndoButton(g, undoButton.getBounds().toFloat(), true, Colour(canUndo ? COLOR_ACTIVE : COLOR_NEUTRAL));
    drawUndoButton(g, redoButton.getBounds().toFloat(), false, Colour(canRedo ? COLOR_ACTIVE : COLOR_NEUTRAL));
}

void TIME12AudioProcessorEditor::drawGear(Graphics& g, Rectangle<int> bounds, float radius, int segs, Colour color, Colour background)
{
    float x = bounds.toFloat().getCentreX();
    float y = bounds.toFloat().getCentreY();
    float oradius = radius;
    float iradius = radius / 3.f;
    float cradius = iradius / 1.5f;
    float coffset = MathConstants<float>::twoPi;
    float inc = MathConstants<float>::twoPi / segs;

    g.setColour(color);
    g.fillEllipse(x-oradius,y-oradius,oradius*2.f,oradius*2.f);

    g.setColour(background);
    for (int i = 0; i < segs; i++) {
        float angle = coffset + i * inc;
        float cx = x + std::cos(angle) * oradius;
        float cy = y + std::sin(angle) * oradius;
        g.fillEllipse(cx - cradius, cy - cradius, cradius * 2, cradius * 2);
    }
    g.fillEllipse(x-iradius, y-iradius, iradius*2.f, iradius*2.f);
}

void TIME12AudioProcessorEditor::drawUndoButton(Graphics& g, juce::Rectangle<float> area, bool invertx, Colour color)
{
        auto bounds = area;
        auto thickness = 2.f;
        float left = bounds.getX();
        float right = bounds.getRight();
        float top = bounds.getCentreY() - 4;
        float bottom = bounds.getCentreY() + 4;
        float centerY = bounds.getCentreY();
        float shaftStart = right - 7;

        Path arrowPath;
        // arrow head
        arrowPath.startNewSubPath(right, centerY);
        arrowPath.lineTo(shaftStart, top);
        arrowPath.startNewSubPath(right, centerY);
        arrowPath.lineTo(shaftStart, bottom);

        // shaft
        float radius = (bottom - centerY);
        arrowPath.startNewSubPath(right, centerY);
        arrowPath.lineTo(left + radius - 1, centerY);

        // semi circle
        arrowPath.startNewSubPath(left + radius, centerY);
        arrowPath.addArc(left, centerY, radius, radius, 2.f * float_Pi, float_Pi);

        if (invertx) {
            AffineTransform flipTransform = AffineTransform::scale(-1.0f, 1.0f)
                .translated(bounds.getWidth(), 0);

            // First move the path to origin, apply transform, then move back
            arrowPath.applyTransform(AffineTransform::translation(-bounds.getPosition()));
            arrowPath.applyTransform(flipTransform);
            arrowPath.applyTransform(AffineTransform::translation(bounds.getPosition()));
        }

        g.setColour(color);
        g.strokePath(arrowPath, PathStrokeType(thickness));
}

void TIME12AudioProcessorEditor::resized()
{
    if (!init) return; // defer resized() call during constructor


    about->setBounds(0,0,getWidth(), getHeight());
    // layout right aligned components and view
    // first row
    auto col = getWidth() - PLUG_PADDING;
    auto bounds = settingsButton->getBounds();
    settingsButton->setBounds(bounds.withX(col - bounds.getWidth()));
    mixDial->setBounds(mixDial->getBounds().withRightX(settingsButton->getBounds().getX() - 10));

    // knobs row
    bounds = useSidechain.getBounds();
    useSidechain.setBounds(bounds.withX(col - bounds.getWidth()));
    bounds = useMonitor.getBounds();
    useMonitor.setBounds(bounds.withX(col - bounds.getWidth()));
    bounds = audioDisplay->getBounds();
    audioDisplay->setBounds(bounds
         .withX(offset->getBounds().getRight())
         .withRight(col - useSidechain.getBounds().getWidth() - 10));

    // third row
    bounds = snapButton.getBounds();
    auto dx = (col - bounds.getWidth()) - bounds.getX();
    snapButton.setBounds(snapButton.getBounds().translated(dx, 0));
    gridSelector->setBounds(gridSelector->getBounds().translated(dx, 0));
    nudgeLeftButton.setBounds(nudgeLeftButton.getBounds().translated(dx, 0));
    nudgeRightButton.setBounds(nudgeRightButton.getBounds().translated(dx, 0));
    redoButton.setBounds(redoButton.getBounds().translated(dx, 0));
    undoButton.setBounds(undoButton.getBounds().translated(dx, 0));

    // view
    bounds = view->getBounds();
    view->setBounds(bounds.withWidth(getWidth()).withHeight(getHeight() - bounds.getY()));

    bounds = seqWidget->getBounds();
    seqWidget->setBounds(bounds.withWidth(getWidth() - PLUG_PADDING * 2));

    bounds = latencyWarning.getBounds();
    latencyWarning.setBounds(bounds
        .withX(view->getBounds().getCentreX() - bounds.getWidth() / 2)
        .withY(getHeight() - 20 - bounds.getHeight())
    );

    audioProcessor.plugWidth = getWidth();
    audioProcessor.plugHeight = getHeight();
}
