/*
 ==============================================================================
 This file is part of the IEM plug-in suite.
 Author: Daniel Rudrich
 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/>.
 ==============================================================================
 */

#pragma once

#include "../ambisonicTools.h"
#include "TitleBarPaths.h"

#ifdef JUCE_OSC_H_INCLUDED
    #include "../OSC/OSCStatus.h"
#endif

class AlertSymbol : public juce::Component, public juce::TooltipClient
{
public:
    AlertSymbol() : juce::Component()
    {
        warningSign.loadPathFromData (WarningSignData, sizeof (WarningSignData));
        setBufferedToImage (true);
    }

    juce::String getTooltip() override
    {
        return "Not enough channels available \n for your current setting.";
    }

    void paint (juce::Graphics& g) override
    {
        warningSign.applyTransform (
            warningSign.getTransformToScaleToFit (getLocalBounds().toFloat(),
                                                  true,
                                                  juce::Justification::centred));
        g.setColour (juce::Colours::yellow);
        g.fillPath (warningSign);
    }

private:
    juce::Path warningSign;
};

class IOWidget : public juce::Component
{
public:
    IOWidget() : juce::Component()
    {
        addChildComponent (alert);
        alert.setBounds (15, 15, 15, 15);
    }

    virtual const int getComponentSize() = 0;
    virtual void setMaxSize (int maxSize) = 0;

    void setBusTooSmall (bool isBusTooSmall)
    {
        busTooSmall = isBusTooSmall;
        alert.setVisible (isBusTooSmall);
    }

    bool isBusTooSmall() { return busTooSmall; }

private:
    AlertSymbol alert;
    bool busTooSmall = false;
};

class NoIOWidget : public IOWidget
{
public:
    NoIOWidget() {}
    void setMaxSize (int maxSize) override { juce::ignoreUnused (maxSize); }
    const int getComponentSize() override { return 0; }
};

class BinauralIOWidget : public IOWidget
{
public:
    BinauralIOWidget() : IOWidget()
    {
        BinauralPath.loadPathFromData (BinauralPathData, sizeof (BinauralPathData));
        setBufferedToImage (true);
    }

    const int getComponentSize() override { return 30; }
    void setMaxSize (int maxSize) override { juce::ignoreUnused (maxSize); }
    void paint (juce::Graphics& g) override
    {
        BinauralPath.applyTransform (
            BinauralPath
                .getTransformToScaleToFit (0, 0, 30, 30, true, juce::Justification::centred));
        g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
        g.fillPath (BinauralPath);
    }

private:
    juce::Path BinauralPath;
};

template <int maxChannels, bool selectable = true>
class AudioChannelsIOWidget : public IOWidget, private juce::ComboBox::Listener
{
public:
    AudioChannelsIOWidget() : IOWidget()
    {
        WaveformPath.loadPathFromData (WaveformPathData, sizeof (WaveformPathData));
        setBufferedToImage (true);

        if (selectable)
        {
            cbChannels.reset (new juce::ComboBox());
            addAndMakeVisible (cbChannels.get());
            cbChannels->setJustificationType (juce::Justification::centred);
            cbChannels->addSectionHeading ("Number of channels");
            cbChannels->addItem ("Auto", 1);
            for (int i = 1; i <= maxChannels; ++i)
                cbChannels->addItem (juce::String (i), i + 1);
            cbChannels->setBounds (35, 8, 70, 15);
            cbChannels->addListener (this);
        }
    }

    const int getComponentSize() override { return selectable ? 110 : 75; }

    void updateDisplayTextIfNotSelectable()
    {
        if (availableChannels < channelSizeIfNotSelectable)
        {
            displayTextIfNotSelectable =
                juce::String (channelSizeIfNotSelectable) + " (bus too small)";
            setBusTooSmall (true);
        }
        else
        {
            displayTextIfNotSelectable = juce::String (channelSizeIfNotSelectable);
            setBusTooSmall (false);
        }
        repaint();
    }

    void checkIfBusIsTooSmall()
    {
        if (availableChannels < cbChannels->getSelectedId() - 1)
            setBusTooSmall (true);
        else
            setBusTooSmall (false);
    }

    void comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) override
    {
        juce::ignoreUnused (comboBoxThatHasChanged);
        checkIfBusIsTooSmall();
    }

    void setMaxSize (int maxPossibleNumberOfChannels) override
    {
        if (availableChannels != maxPossibleNumberOfChannels)
        {
            availableChannels = maxPossibleNumberOfChannels;
            if (selectable)
            {
                if (maxPossibleNumberOfChannels > 0)
                    cbChannels->changeItemText (
                        1,
                        "Auto (" + juce::String (maxPossibleNumberOfChannels) + ")");
                else
                    cbChannels->changeItemText (1, "(Auto)");
                int currId = cbChannels->getSelectedId();
                if (currId == 0)
                    currId = 1; //bad work around
                int i;
                for (i = 1; i <= maxPossibleNumberOfChannels; ++i)
                {
                    cbChannels->changeItemText (i + 1, juce::String (i));
                }
                for (i = maxPossibleNumberOfChannels + 1; i <= maxChannels; ++i)
                {
                    cbChannels->changeItemText (i + 1, juce::String (i) + " (bus too small)");
                }
                checkIfBusIsTooSmall();

                cbChannels->setText (cbChannels->getItemText (cbChannels->indexOfItemId (currId)));
            }
            else
            {
                updateDisplayTextIfNotSelectable();
            }
        }
    }

    void setSizeIfUnselectable (int newSize)
    {
        if (! selectable && channelSizeIfNotSelectable != newSize)
        {
            channelSizeIfNotSelectable = newSize;
            updateDisplayTextIfNotSelectable();
        }
    }

    juce::ComboBox* getChannelsCbPointer()
    {
        if (selectable)
            return cbChannels.get();
        return nullptr;
    }

    void paint (juce::Graphics& g) override
    {
        WaveformPath.applyTransform (
            WaveformPath
                .getTransformToScaleToFit (0, 0, 30, 30, true, juce::Justification::centred));
        g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
        g.fillPath (WaveformPath);

        if (! selectable)
        {
            g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
            g.setFont (juce::FontOptions (
                           getLookAndFeel().getTypefaceForFont (juce::FontOptions (15.0f, 1)))
                           .withHeight (15.0f));
            g.drawFittedText (displayTextIfNotSelectable,
                              35,
                              0,
                              40,
                              30,
                              juce::Justification::centredLeft,
                              2);
        }
    }

private:
    std::unique_ptr<juce::ComboBox> cbChannels;
    juce::Path WaveformPath;
    int availableChannels { 64 };
    int channelSizeIfNotSelectable = maxChannels;
    juce::String displayTextIfNotSelectable = juce::String (maxChannels);
};

template <int order = 7, bool selectable = true>
class AmbisonicIOWidget : public IOWidget
{
public:
    AmbisonicIOWidget() : IOWidget()
    {
        AmbiLogoPath.loadPathFromData (AmbiLogoPathData, sizeof (AmbiLogoPathData));
        setBufferedToImage (true);

        if (selectable)
        {
            addAndMakeVisible (&cbOrder);
            cbOrder.setJustificationType (juce::Justification::centred);
            cbOrder.setBounds (35, 15, 70, 15);
            updateMaxOrder();
        }
        else
        {
            displayTextIfNotSelectable = getOrderString (order) + " order";
        }

        addAndMakeVisible (&cbNormalization);
        cbNormalization.setJustificationType (juce::Justification::centred);
        cbNormalization.addSectionHeading ("Normalization");
        cbNormalization.addItem ("N3D", 1);
        cbNormalization.addItem ("SN3D", 2);
        cbNormalization.setBounds (35, 0, 70, 15);
    }

    ~AmbisonicIOWidget() override {}

    void setOrderIfUnselectable (int newOrder)
    {
        if (! selectable && orderIfNotSelectable != newOrder)
        {
            orderIfNotSelectable = newOrder;
            updateDisplayTextIfNotSelectable();
        }
    }

    void updateDisplayTextIfNotSelectable()
    {
        if (maxPossibleOrder < orderIfNotSelectable)
        {
            displayTextIfNotSelectable = getOrderString (orderIfNotSelectable) + " (bus too small)";
            setBusTooSmall (true);
        }
        else
        {
            displayTextIfNotSelectable = getOrderString (orderIfNotSelectable) + " order";
            setBusTooSmall (false);
        }
        repaint();
    }

    void updateMaxOrder()
    {
        const int previousIndex = cbOrder.getSelectedItemIndex();
        cbOrder.clear();
        cbOrder.addSectionHeading ("Ambisonic Order");
        cbOrder.addItem ("Auto", 1);
        for (int o = 0; o <= maxOrder; ++o)
            cbOrder.addItem (getOrderString (o), o + 2);

        cbOrder.setSelectedItemIndex (previousIndex);
    }

    void setMaxOrder (int newMaxOrder)
    {
        maxOrder = newMaxOrder;
        updateMaxOrder();
        const int savedMaxPossibleOrder = maxPossibleOrder;
        maxPossibleOrder = -1;
        setMaxSize (savedMaxPossibleOrder);
    }

    const int getComponentSize() override { return 110; }

    /** Sets the maximally available size of the processor for this Widget.
     */
    void setMaxSize (int newMaxPossibleOrder) override
    {
        if (maxPossibleOrder != juce::jmin (newMaxPossibleOrder, maxOrder))
        {
            maxPossibleOrder = juce::jmin (newMaxPossibleOrder, maxOrder);

            if (selectable)
            {
                if (maxPossibleOrder > -1)
                    cbOrder.changeItemText (1, "Auto (" + getOrderString (maxPossibleOrder) + ")");
                else
                    cbOrder.changeItemText (1, "(Auto)");

                int currId = cbOrder.getSelectedId();
                if (currId == 0)
                    currId = 1; //bad work around

                for (int i = 1; i <= maxPossibleOrder; ++i)
                    cbOrder.changeItemText (i + 2, getOrderString (i));

                for (int i = maxPossibleOrder + 1; i <= maxOrder; ++i)
                    cbOrder.changeItemText (i + 2, getOrderString (i) + " (bus too small)");

                DBG (cbOrder.getItemText (cbOrder.indexOfItemId ((currId))));
                cbOrder.setText (cbOrder.getItemText (cbOrder.indexOfItemId ((currId))));
                if (currId - 2 > maxPossibleOrder)
                    setBusTooSmall (true);
                else
                    setBusTooSmall (false);
            }
            else
            {
                updateDisplayTextIfNotSelectable();
            }
        }
    }

    juce::ComboBox* getNormCbPointer() { return &cbNormalization; }
    juce::ComboBox* getOrderCbPointer()
    {
        if (! selectable)
            // There's no Ambisonic Order ComboBox, when order is not selectable!
            jassertfalse;

        return &cbOrder;
    }

    void paint (juce::Graphics& g) override
    {
        AmbiLogoPath.applyTransform (
            AmbiLogoPath
                .getTransformToScaleToFit (0, 0, 30, 30, true, juce::Justification::centred));
        g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
        g.fillPath (AmbiLogoPath);

        if (! selectable)
        {
            g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
            g.setFont (juce::FontOptions (
                getLookAndFeel().getTypefaceForFont (juce::FontOptions (15.0f, 1))));
            g.drawFittedText (displayTextIfNotSelectable,
                              35,
                              15,
                              55,
                              15,
                              juce::Justification::centred,
                              1);
        }
    };

private:
    juce::ComboBox cbNormalization, cbOrder;
    juce::Path AmbiLogoPath;
    int maxOrder = order;
    int orderIfNotSelectable = order;
    int maxPossibleOrder = -1;
    juce::String displayTextIfNotSelectable;
};

class DirectivityIOWidget : public IOWidget
{
public:
    DirectivityIOWidget() : IOWidget()
    {
        DirectivityPath.loadPathFromData (DirectivityPathData, sizeof (DirectivityPathData));
        setBufferedToImage (true);
        orderStrings[0] = juce::String ("0th");
        orderStrings[1] = juce::String ("1st");
        orderStrings[2] = juce::String ("2nd");
        orderStrings[3] = juce::String ("3rd");
        orderStrings[4] = juce::String ("4th");
        orderStrings[5] = juce::String ("5th");
        orderStrings[6] = juce::String ("6th");
        orderStrings[7] = juce::String ("7th");

        addAndMakeVisible (&cbOrder);
        cbOrder.setJustificationType (juce::Justification::centred);
        cbOrder.addSectionHeading ("Directivity Order");
        cbOrder.addItem ("Auto", 1);
        cbOrder.addItem ("0th", 2);
        cbOrder.addItem ("1st", 3);
        cbOrder.addItem ("2nd", 4);
        cbOrder.addItem ("3rd", 5);
        cbOrder.addItem ("4th", 6);
        cbOrder.addItem ("5th", 7);
        cbOrder.addItem ("6th", 8);
        cbOrder.addItem ("7th", 9);
        cbOrder.setBounds (35, 15, 70, 15);

        addAndMakeVisible (&cbNormalization);
        cbNormalization.setJustificationType (juce::Justification::centred);
        cbNormalization.addSectionHeading ("Normalization");
        cbNormalization.addItem ("N3D", 1);
        cbNormalization.addItem ("SN3D", 2);
        cbNormalization.setBounds (35, 0, 70, 15);
    }

    const int getComponentSize() override { return 110; }

    void setMaxSize (int maxPossibleOrder) override
    {
        if (maxPossibleOrder > -1)
            cbOrder.changeItemText (1, "Auto (" + orderStrings[maxPossibleOrder] + ")");
        else
            cbOrder.changeItemText (1, "(Auto)");
        int currId = cbOrder.getSelectedId();
        if (currId == 0)
            currId = 1; //bad work around
        int i;
        for (i = 1; i <= maxPossibleOrder; ++i)
        {
            cbOrder.changeItemText (i + 2, orderStrings[i]);
        }
        for (i = maxPossibleOrder + 1; i <= 7; ++i)
        {
            cbOrder.changeItemText (i + 2, orderStrings[i] + " (bus too small)");
        }
        cbOrder.setText (cbOrder.getItemText (cbOrder.indexOfItemId (currId)));
        if (currId - 2 > maxPossibleOrder)
            setBusTooSmall (true);
        else
            setBusTooSmall (false);
    }

    juce::ComboBox* getNormCbPointer() { return &cbNormalization; }
    juce::ComboBox* getOrderCbPointer() { return &cbOrder; }

    void paint (juce::Graphics& g) override
    {
        DirectivityPath.applyTransform (
            DirectivityPath
                .getTransformToScaleToFit (0, 0, 30, 30, true, juce::Justification::centred));
        g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
        g.fillPath (DirectivityPath);
    }

private:
    juce::String orderStrings[8];
    juce::ComboBox cbNormalization, cbOrder;
    juce::Path DirectivityPath;
};

// ======================================================== TITLEBAR =========================
template <class Tin, class Tout>
class TitleBar : public juce::Component
{
public:
    TitleBar() : juce::Component()
    {
        addAndMakeVisible (&inputWidget);
        addAndMakeVisible (&outputWidget);
    }

    Tin* getInputWidgetPtr() { return &inputWidget; }
    Tout* getOutputWidgetPtr() { return &outputWidget; }

    void setTitle (juce::String newBoldText, juce::String newRegularText)
    {
        boldText = newBoldText;
        regularText = newRegularText;
    }

    void setFont (juce::Typeface::Ptr newBoldFont, juce::Typeface::Ptr newRegularFont)
    {
        boldFont = juce::FontOptions (newBoldFont).withHeight (boldHeight);
        regularFont = juce::FontOptions (newRegularFont).withHeight (regularHeight);
    }

    void resized() override
    {
        const int leftWidth = inputWidget.getComponentSize();
        const int rightWidth = outputWidget.getComponentSize();

        inputWidget.setBounds (getLocalBounds().removeFromLeft (leftWidth).reduced (0, 15));
        outputWidget.setBounds (getLocalBounds().removeFromRight (rightWidth).reduced (0, 15));
    }

    void setMaxSize (std::pair<int, int> inOutSizes)
    {
        inputWidget.setMaxSize (inOutSizes.first);
        outputWidget.setMaxSize (inOutSizes.second);
    }

    void paint (juce::Graphics& g) override
    {
        juce::Rectangle<int> bounds = getLocalBounds();
        const float centreX = bounds.getX() + bounds.getWidth() * 0.5f;
        const float centreY = bounds.getY() + bounds.getHeight() * 0.5f;
        const int leftWidth = inputWidget.getComponentSize();
        const int rightWidth = outputWidget.getComponentSize();

        // Typeset title sting
        juce::AttributedString titleText;
        titleText.append (boldText, boldFont, juce::Colours::white);
        titleText.append (regularText, regularFont, juce::Colours::white);
        titleText.setJustification (juce::Justification::bottom);

        // Create TextLayout of title
        const float maxWidth = static_cast<float> (bounds.getWidth() - rightWidth - leftWidth);
        juce::TextLayout titleTextLayout;
        titleTextLayout.createLayout (juce::AttributedString (titleText), maxWidth);

        // Define title text area
        const float titleWidth = titleTextLayout.getWidth();
        const float titleHeight = titleTextLayout.getHeight();

        juce::Rectangle<float> textArea (0, 0, titleWidth, titleHeight);
        textArea.setCentre (centreX, centreY);

        if (textArea.getX() < leftWidth)
            textArea.setX (leftWidth);
        if (textArea.getRight() > bounds.getRight() - rightWidth)
            textArea.setRight (bounds.getRight() - rightWidth);

        // Draw title
        titleTextLayout.draw (g, textArea);

        // Draw separation line
        g.setColour ((juce::Colours::white).withMultipliedAlpha (0.5));
        g.drawLine (bounds.getX(),
                    bounds.getY() + bounds.getHeight() - 4,
                    bounds.getX() + bounds.getWidth(),
                    bounds.getY() + bounds.getHeight() - 4);
    }

private:
    Tin inputWidget;
    Tout outputWidget;
    juce::FontOptions boldFont = juce::FontOptions (25.f);
    juce::FontOptions regularFont = juce::FontOptions (25.f);
    juce::String boldText = "Bold";
    juce::String regularText = "Regular";

    const float boldHeight = 25.f;
    const float regularHeight = 25.f;
};

class IEMLogo : public juce::Component
{
public:
    IEMLogo() : juce::Component()
    {
        IEMPath.loadPathFromData (IEMpathData, sizeof (IEMpathData));
        url = juce::URL ("https://plugins.iem.at/");
    }

    void paint (juce::Graphics& g) override
    {
        juce::Rectangle<int> bounds = getLocalBounds();
        bounds.removeFromBottom (3);
        bounds.removeFromLeft (1);
        IEMPath.applyTransform (IEMPath.getTransformToScaleToFit (bounds.reduced (2, 2).toFloat(),
                                                                  true,
                                                                  juce::Justification::bottomLeft));

        if (isMouseOver())
        {
            g.setColour (juce::Colour::fromRGB (52, 88, 165));
            g.fillAll();
        }

        g.setColour (isMouseOver() ? juce::Colour::fromRGB (249, 226, 45)
                                   : juce::Colours::white.withMultipliedAlpha (0.5));
        g.fillPath (IEMPath);
    }

    void mouseEnter (const juce::MouseEvent& event) override
    {
        juce::ignoreUnused (event);
        setMouseCursor (juce::MouseCursor (juce::MouseCursor::PointingHandCursor));
        repaint();
    }

    void mouseExit (const juce::MouseEvent& event) override
    {
        juce::ignoreUnused (event);
        setMouseCursor (juce::MouseCursor (juce::MouseCursor::NormalCursor));
        repaint();
    }

    void mouseUp (const juce::MouseEvent& event) override
    {
        juce::ignoreUnused (event);
        if (url.isWellFormed())
            url.launchInDefaultBrowser();
    }

private:
    juce::Path IEMPath;
    juce::URL url;
};

class Footer : public juce::Component
{
public:
    Footer() : juce::Component() { addAndMakeVisible (&iemLogo); }

    void paint (juce::Graphics& g) override
    {
        juce::Rectangle<int> bounds = getLocalBounds();
        g.setColour (juce::Colours::white.withAlpha (0.5f));
        g.setFont (
            juce::FontOptions (getLookAndFeel().getTypefaceForFont (juce::FontOptions (14.0f, 0)))
                .withHeight (14.0f));
        juce::String versionString = "v";

#if JUCE_DEBUG
        versionString = "DEBUG - v";
#endif
        versionString.append (JucePlugin_VersionString, 6);

        g.drawText (versionString,
                    0,
                    0,
                    bounds.getWidth() - 8,
                    bounds.getHeight() - 2,
                    juce::Justification::bottomRight);
    }

    void resized() override { iemLogo.setBounds (0, 0, 40, getHeight()); }

private:
    IEMLogo iemLogo;
};

#ifdef JUCE_OSC_H_INCLUDED
class OSCFooter : public juce::Component
{
public:
    OSCFooter (OSCParameterInterface& oscInterface) : oscStatus (oscInterface)
    {
        addAndMakeVisible (footer);
        addAndMakeVisible (oscStatus);
    }

    void resized() override
    {
        auto bounds = getLocalBounds();
        footer.setBounds (bounds);

        bounds.removeFromBottom (2);
        bounds = bounds.removeFromBottom (16);
        bounds.removeFromLeft (50);
        oscStatus.setBounds (bounds);
    }

private:
    OSCStatus oscStatus;
    Footer footer;
};
#endif
