/*
 ==============================================================================
 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

//==============================================================================
/*
*/
class ReflectionsVisualizer : public juce::Component, private juce::Timer
{
    const float mL = 23.0f;
    const float mR = 10.0f;
    const float mT = 7.0f;
    const float mB = 15.0f;

public:
    ReflectionsVisualizer()
    {
        // In your constructor, you should add any child components, and
        // initialise any special settings that your component needs.

        startTimer (10); //TODO avoid using timer
    }
    ~ReflectionsVisualizer() {}

    void paint (juce::Graphics& g) override
    {
        g.setColour (juce::Colours::steelblue.withMultipliedAlpha (0.01f));
        g.fillAll();

        g.setColour (juce::Colours::steelblue.withMultipliedAlpha (0.9f));
        g.strokePath (axes, juce::PathStrokeType (1.0f));
        g.setColour (juce::Colours::steelblue.withMultipliedAlpha (0.8f));
        g.strokePath (dBGrid, juce::PathStrokeType (0.5f));

        g.setColour (juce::Colours::white);
        auto currentFont =
            juce::FontOptions (getLookAndFeel().getTypefaceForFont (juce::FontOptions (12.0f, 2)))
                .withHeight (12.0f);
        g.setFont (currentFont);

        for (int dB = 0; dB >= -60; dB -= 10)
        {
            float yPos = dBToY ((float) dB);
            g.drawText (juce::String (dB),
                        0,
                        yPos - 6,
                        18,
                        12.0f,
                        juce::Justification::right,
                        false);
        }

        int msStep;
        if (xRangeInMs < 80)
            msStep = 5;
        else if (xRangeInMs < 200)
            msStep = 10;
        else
            msStep = 20;

        for (int timeInMs = 0; timeInMs <= xRangeInMs; timeInMs += msStep)
        {
            float xPos = timeToX ((float) timeInMs);
            g.drawText (juce::String (timeInMs),
                        xPos - 15.0f,
                        mT + plotHeight + 2.0f,
                        30,
                        12.0f,
                        juce::Justification::centred,
                        false);
        }

        const float xFactor = 1000.0f / 343.2f;

        if (radiusPtr != nullptr)
        {
            int numRef = juce::roundToInt (numReflPtr->load());

            float gainDb = juce::Decibels::gainToDecibels (gainPtr[0]);
            if (gainDb > -60.0f && gainDb <= 20.0f)
            {
                const float xPos = timeToX (zeroDelay ? 0.0f : radiusPtr[0] * xFactor);
                const float yPos = dBToY (gainDb);
                g.drawLine (xPos, yPos, xPos, mT + plotHeight, 2.0f);
            }

            g.setColour (juce::Colours::white.withMultipliedAlpha (0.5f));

            for (int i = 1; i <= numRef; ++i)
            {
                float gainDb = juce::Decibels::gainToDecibels (gainPtr[i]);
                if (gainDb > -60.0f && gainDb < 20.0f)
                {
                    const float radius = radiusPtr[i] - (zeroDelay ? radiusPtr[0] : 0.0f);
                    const float xPos = timeToX (radius * xFactor);
                    const float yPos = dBToY (gainDb);
                    g.drawLine (xPos, yPos, xPos, mT + plotHeight, 1.5f);
                }
            }
        }
    }

    inline float timeToX (float timeInMs) { return mL + timeInMs / xRangeInMs * plotWidth; }

    inline float dBToY (float gainInDB)
    {
        const float dynRange = -1.0f / 60.0f;
        return mT + dynRange * gainInDB * plotHeight;
    }

    void mouseWheelMove (const juce::MouseEvent& e, const juce::MouseWheelDetails& wheel) override
    {
        const double delta =
            100
            * (std::abs (wheel.deltaX) > std::abs (wheel.deltaY) ? -wheel.deltaX : wheel.deltaY);
        //bool positiveDelta = delta >= 0.0;

        xRangeInMs += juce::roundToInt (delta);
        xRangeInMs = juce::jmin (xRangeInMs, 550);
        xRangeInMs = juce::jmax (xRangeInMs, 40);
    }
    void setDataPointers (float* Gain, float* Radius, std::atomic<float>* NumRefl)
    {
        gainPtr = Gain;
        numReflPtr = NumRefl;
        radiusPtr = Radius;
    }

    void setZeroDelay (const bool shouldBeZeroDelay)
    {
        if (zeroDelay != shouldBeZeroDelay)
        {
            zeroDelay = shouldBeZeroDelay;
            repaint();
        }
    }

    void resized() override
    {
        plotWidth = getLocalBounds().getWidth() - mL - mR;
        plotHeight = getLocalBounds().getHeight() - mT - mB;

        axes.clear();
        //horizontal
        axes.startNewSubPath (timeToX (0.0f) - 2, dBToY (-60.0f));
        axes.lineTo (timeToX (xRangeInMs), dBToY (-60.0f));

        //vertical
        axes.startNewSubPath (timeToX (0.0f), dBToY (-60.0f) + 2.0f);
        axes.lineTo (timeToX (0.0f), mT - 2.0f);

        dBGrid.clear();
        for (int dB = 0; dB >= -50; dB -= 10)
        {
            float yPos = dBToY ((float) dB);
            dBGrid.startNewSubPath (mL - 2.0f, yPos);
            dBGrid.lineTo (mL + plotWidth, yPos);
        }
    }
    void timerCallback() override { repaint(); }

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReflectionsVisualizer)
    juce::Path axes;
    juce::Path dBGrid;
    float plotWidth = 1.0f;
    float plotHeight = 1.0f;
    int xRangeInMs = 100;
    std::atomic<float>* numReflPtr = nullptr;
    float* gainPtr = nullptr;
    float* radiusPtr = nullptr;

    bool zeroDelay = false;
};
