/*
 ===============================================================================
 
 LUFSMeterAudioProcessorEditor.cpp
 
 
 This file is part of the LUFS Meter audio measurement plugin.
 Copyright 2011-12 by Klangfreund, Samuel Gaehwiler.
 
 -------------------------------------------------------------------------------
 
 The LUFS Meter can be redistributed and/or modified under the terms of the GNU 
 General Public License Version 2, as published by the Free Software Foundation.
 A copy of the license is included with these source files. It can also be found
 at www.gnu.org/licenses.
 
 The LUFS Meter is distributed WITHOUT ANY WARRANTY.
 See the GNU General Public License for more details.
 
 -------------------------------------------------------------------------------
 
 To release a closed-source product which uses the LUFS Meter or parts of it,
 a commercial license is available. Visit www.klangfreund.com/lufsmeter for more
 information.
 
 ===============================================================================
 */


#include "LUFSMeterAudioProcessor.h"
#include "LUFSMeterAudioProcessorEditor.h"


//==============================================================================
LUFSMeterAudioProcessorEditor::LUFSMeterAudioProcessorEditor (LUFSMeterAudioProcessor* ownerFilter)
  : AudioProcessorEditor (ownerFilter),
    momentaryLoudnessValue (var(-300.0)),
    shortTermLoudnessValue (var(-300.0)),
    loudnessRangeStartValue (var(-300.0)),
    loudnessRangeEndValue (var(-300.0)),
    loudnessRangeValue(var(0.0)),
    integratedLoudnessValue (var(-300.0)),
    distanceBetweenLoudnessBarAndTop (10),
    distanceBetweenLoudnessBarAndBottom (32),
    backgroundGridCaption (distanceBetweenLoudnessBarAndTop, 
                           distanceBetweenLoudnessBarAndBottom, 
                           getProcessor()->loudnessBarMinValue, 
                           getProcessor()->loudnessBarMaxValue),
    momentaryLoudnessBar (getProcessor()->loudnessBarMinValue,
                          getProcessor()->loudnessBarMaxValue),
    momentaryLoudnessBarSum (momentaryLoudnessValue,
                             getProcessor()->loudnessBarMinValue,
                             getProcessor()->loudnessBarMaxValue),
    shortTermLoudnessBar (shortTermLoudnessValue,
                          getProcessor()->loudnessBarMinValue, 
                          getProcessor()->loudnessBarMaxValue),
    loudnessRangeBar (loudnessRangeStartValue,
                      loudnessRangeEndValue,
                      getProcessor()->loudnessBarMinValue,
                      getProcessor()->loudnessBarMaxValue),
    integratedLoudnessBar (integratedLoudnessValue,
                           getProcessor()->loudnessBarMinValue, 
                           getProcessor()->loudnessBarMaxValue),
    momentaryLoudnessCaption (String(), "M"),
    shortTermLoudnessCaption (String(), "S"),
    loudnessRangeCaption (String(), "LRA"),
    integratedLoudnessCaption (String(), "I"),
    momentaryLoudnessHistory (momentaryLoudnessValue, getProcessor()->loudnessBarMinValue, getProcessor()->loudnessBarMaxValue),
    shortTermLoudnessHistory (shortTermLoudnessValue, getProcessor()->loudnessBarMinValue, getProcessor()->loudnessBarMaxValue),
    loudnessRangeHistory (loudnessRangeStartValue, loudnessRangeEndValue, getProcessor()->loudnessBarMinValue, getProcessor()->loudnessBarMaxValue),
    integratedLoudnessHistory (integratedLoudnessValue, getProcessor()->loudnessBarMinValue, getProcessor()->loudnessBarMaxValue),
    preferencesPane(getProcessor()->loudnessBarWidth,
                    getProcessor()->loudnessBarMinValue,
                    getProcessor()->loudnessBarMaxValue,
                    getProcessor()->showIntegratedLoudnessHistory,
                    getProcessor()->showLoudnessRangeHistory,
                    getProcessor()->showShortTermLoudnessHistory,
                    getProcessor()->showMomentaryLoudnessHistory)
{
    LookAndFeel::setDefaultLookAndFeel(&lookAndFeelV3);
    
    // Add the background
    addAndMakeVisible (&backgroundGrid);
    addAndMakeVisible (&backgroundGridCaption);
    addAndMakeVisible(&backgroundVerticalLinesAndCaption);
    
    // TEMP:
    // Add a label that will display the current timecode and status..
    //    addAndMakeVisible (&infoLabel);
    //    infoLabel.setColour (Label::textColourId, Colours::green);
    
    Colour momentaryLoudnessColour = Colours::darkgreen;
    Colour momentaryLoudnessSumColour = Colours::darkgreen.darker().darker();
    Colour loudnessRangeColour = Colours::blue.darker();
    Colour loudnessRangeColourTransparent = loudnessRangeColour.withAlpha(0.3f);
    Colour integratedLoudnessColour = Colours::yellow.darker().darker();
    
    
    // Add the meter bars
    momentaryLoudnessBarSum.setColour (momentaryLoudnessSumColour);
    addAndMakeVisible(&momentaryLoudnessBarSum);
    momentaryLoudnessBar.setColour (momentaryLoudnessColour);
    addAndMakeVisible (&momentaryLoudnessBar);
    
    addAndMakeVisible (&shortTermLoudnessBar);

    loudnessRangeBar.setColour (loudnessRangeColourTransparent);
    addAndMakeVisible (&loudnessRangeBar);
    
    integratedLoudnessBar.setColour (integratedLoudnessColour);
    addAndMakeVisible (&integratedLoudnessBar);
    
    
    // Add the numeric values
    momentaryLoudnessNumeric.setColour(momentaryLoudnessColour);
    addAndMakeVisible (&momentaryLoudnessNumeric);
    momentaryLoudnessNumeric.getLoudnessValueObject().referTo(momentaryLoudnessValue);
    
    addAndMakeVisible (&shortTermLoudnessNumeric);
    shortTermLoudnessNumeric.getLoudnessValueObject().referTo(shortTermLoudnessValue);
    
    loudnessRangeNumeric.setColour(loudnessRangeColour);
    addAndMakeVisible (&loudnessRangeNumeric);
    loudnessRangeNumeric.getLoudnessValueObject().referTo(loudnessRangeValue);
    
    integratedLoudnessNumeric.setColour(integratedLoudnessColour);
    addAndMakeVisible (&integratedLoudnessNumeric);
    integratedLoudnessNumeric.getLoudnessValueObject().referTo(integratedLoudnessValue);
    
    
    // Add the captions
    const int fontHeight = 16;
    const Font fontForCaptions (fontHeight);
    const Justification justification (Justification::horizontallyCentred);
    
    momentaryLoudnessCaption.setFont(fontForCaptions);
    momentaryLoudnessCaption.setColour (Label::textColourId, momentaryLoudnessColour);
    // momentaryLoudnessCaption.setColour (Label::backgroundColourId, Colours::red);
    momentaryLoudnessCaption.setJustificationType(justification);
    addAndMakeVisible (&momentaryLoudnessCaption);
    
    shortTermLoudnessCaption.setFont(fontForCaptions);
    shortTermLoudnessCaption.setColour (Label::textColourId, Colours::green);
    shortTermLoudnessCaption.setJustificationType(justification);
    addAndMakeVisible (&shortTermLoudnessCaption);

    loudnessRangeCaption.setFont(fontForCaptions);
    loudnessRangeCaption.setColour (Label::textColourId, loudnessRangeColour);
    loudnessRangeCaption.setJustificationType(justification);
    addAndMakeVisible (&loudnessRangeCaption);
    
    integratedLoudnessCaption.setFont(fontForCaptions);
    integratedLoudnessCaption.setColour (Label::textColourId, integratedLoudnessColour);
    integratedLoudnessCaption.setJustificationType(justification);
    addAndMakeVisible (&integratedLoudnessCaption);
    
    
    // Add the loudness history graphs
    addAndMakeVisible(&loudnessHistoryGroup);
    
    loudnessRangeHistory.setColour (loudnessRangeColourTransparent);
    loudnessRangeHistory.setVisible (bool(getProcessor()->showLoudnessRangeHistory.getValue()));
    loudnessHistoryGroup.addChildComponent(&loudnessRangeHistory);
    
    momentaryLoudnessHistory.setColour(momentaryLoudnessSumColour);
    momentaryLoudnessHistory.setVisible (bool(getProcessor()->showMomentaryLoudnessHistory.getValue()));
    loudnessHistoryGroup.addChildComponent(&momentaryLoudnessHistory);

    shortTermLoudnessHistory.setVisible (bool(getProcessor()->showShortTermLoudnessHistory.getValue()));
    loudnessHistoryGroup.addChildComponent(&shortTermLoudnessHistory);

    integratedLoudnessHistory.setColour (integratedLoudnessColour);
    integratedLoudnessHistory.setVisible (bool(getProcessor()->showIntegratedLoudnessHistory.getValue()));
    loudnessHistoryGroup.addChildComponent(&integratedLoudnessHistory);
    
    
    // Add the reset button
    resetButton.addListener(this);
    resetButton.setButtonText("reset");
    resetButton.setColour(TextButton::buttonColourId, Colours::darkred);
    resetButton.setColour(TextButton::textColourOffId, Colours::lightgrey);
    addAndMakeVisible (&resetButton);
    
    // Add the preferences pane
    addAndMakeVisible (&preferencesPane);
    preferencesPane.setTopLeftPosition(- preferencesPane.getWidthWithoutHandle(), 50);
    
    // Add the triangular resizer component for the bottom-right of the UI.
    addAndMakeVisible (resizer = new ResizableCornerComponent (this, &resizeLimits));
    
    resizeLimits.setMinimumSize(150, 150);
    
    // Set our component's initial size to be the last one that was stored in 
    // the filter's settings.
    setSize (ownerFilter->lastUIWidth,
             ownerFilter->lastUIHeight);
    
    // Listen to some Values
    getProcessor()->loudnessBarWidth.addListener (this);
    getProcessor()->showIntegratedLoudnessHistory.addListener (this);
    getProcessor()->showLoudnessRangeHistory.addListener (this);
    getProcessor()->showShortTermLoudnessHistory.addListener (this);
    getProcessor()->showMomentaryLoudnessHistory.addListener (this);
    getProcessor()->numberOfInputChannels.addListener (this);
    
    momentaryLoudnessHistory.reset();
    shortTermLoudnessHistory.reset();
    loudnessRangeHistory.reset();
    integratedLoudnessHistory.reset();
    
    // Start the timer which will refresh the GUI elements.
    const int refreshIntervalInMilliseconds = 50;
    startTimer (refreshIntervalInMilliseconds);
}

LUFSMeterAudioProcessorEditor::~LUFSMeterAudioProcessorEditor()
{
    if (getProcessor())
    {
        getProcessor()->loudnessBarWidth.removeListener (this);
        getProcessor()->showIntegratedLoudnessHistory.removeListener (this);
        getProcessor()->showLoudnessRangeHistory.removeListener (this);
        getProcessor()->showShortTermLoudnessHistory.removeListener (this);
        getProcessor()->showMomentaryLoudnessHistory.removeListener (this);
    }
}

//==============================================================================
void LUFSMeterAudioProcessorEditor::paint (Graphics& g)
{
    // Draw the background
    g.fillAll(Colours::black);
}

void LUFSMeterAudioProcessorEditor::resized()
{
    // DEB("Height of main component = " + String(getHeight()))
    
    resizeGuiComponents();

    // Some more fix sized components:
    // TEMP
    //infoLabel.setBounds (10, 4, 380, 25);
    
//    resetButton.setBounds(10, getHeight()-35, 80, 25);
    resetButton.setBounds(12, 12, 50, 25);
    
    resizer->setBounds (getWidth() - 16, getHeight() - 16, 16, 16);
    
    getProcessor()->lastUIWidth = getWidth();
    getProcessor()->lastUIHeight = getHeight();
}


// This timer periodically updates the labels.
void LUFSMeterAudioProcessorEditor::timerCallback()
{
//    AudioPlayHead::CurrentPositionInfo newPos (getProcessor()->lastPosInfo);
//    
//    if (lastDisplayedPosition != newPos)
//        displayPositionInfo (newPos);
    
    // momentary loudness values
    // -------------------------
    momentaryLoudnessValue.setValue (getProcessor()->getMomentaryLoudness());

    momentaryLoudnessBar.setLoudness (getProcessor()->getMomentaryLoudnessForIndividualChannels());
    
    /*
    // source:
    const Array<float>& momentaryLoudnessFromEbu128LM = getProcessor()->getMomentaryLoudnessForIndividualChannels();
    // destination:
    Array<var> momentaryLoudness;
    
    
    for (int k = 0; k != momentaryLoudnessFromEbu128LM.size() ; ++k)
    {
        double momentaryLoudnessOfTheKthChannel = double (momentaryLoudnessFromEbu128LM[k]);
        momentaryLoudness.add (momentaryLoudnessOfTheKthChannel);
    }
    */

    // short loudness values
    // ---------------------
    float shortTermLoudness = getProcessor()->getShortTermLoudness();
    jassert(shortTermLoudness > -400);
    shortTermLoudnessValue.setValue(shortTermLoudness);

    // loudness range values
    // ---------------------
    float loudnessRange = getProcessor()->getLoudnessRange();
    jassert(loudnessRange > -400);
    loudnessRangeValue.setValue(loudnessRange);
    
    float loudnessRangeStart = getProcessor()->getLoudnessRangeStart();
    jassert (loudnessRangeStart > -400);
    loudnessRangeStartValue.setValue(loudnessRangeStart);
    
    float loudnessRangeEnd = getProcessor()->getLoudnessRangeEnd();
    jassert (loudnessRangeEnd > -400);
    loudnessRangeEndValue.setValue(loudnessRangeEnd);
    
    // integrated loudness values
    // --------------------------
    float integratedLoudness = getProcessor()->getIntegratedLoudness();
    jassert(integratedLoudness > -400);
    integratedLoudnessValue.setValue(integratedLoudness);
}


void LUFSMeterAudioProcessorEditor::buttonClicked(Button* button)
{
    if (button == &resetButton)
    {
        getProcessor()->ebu128LoudnessMeter.reset();
        momentaryLoudnessHistory.reset();
        shortTermLoudnessHistory.reset();
        loudnessRangeHistory.reset();
        integratedLoudnessHistory.reset();
    }
}

void LUFSMeterAudioProcessorEditor::valueChanged (Value & value)
{
    if (value.refersToSameSourceAs (getProcessor()->loudnessBarWidth) ||
        value.refersToSameSourceAs (getProcessor()->numberOfInputChannels))
            // Because the width of the of the MultiChannelLoudnessBar needs to be a multiple
            // of the numberOfInputChannels.
    {
        resizeGuiComponents();
    }
    
    else if (value.refersToSameSourceAs (getProcessor()->showIntegratedLoudnessHistory))
    {
        integratedLoudnessHistory.setVisible (bool(getProcessor()->showIntegratedLoudnessHistory.getValue()));
    }
    
    else if (value.refersToSameSourceAs (getProcessor()->showLoudnessRangeHistory))
    {
        loudnessRangeHistory.setVisible (bool(getProcessor()->showLoudnessRangeHistory.getValue()));
    }
    
    else if (value.refersToSameSourceAs (getProcessor()->showShortTermLoudnessHistory))
    {
        shortTermLoudnessHistory.setVisible (bool(getProcessor()->showShortTermLoudnessHistory.getValue()));
    }
    
    else if (value.refersToSameSourceAs (getProcessor()->showMomentaryLoudnessHistory))
    {
        momentaryLoudnessHistory.setVisible (bool(getProcessor()->showMomentaryLoudnessHistory.getValue()));
    }
}


LUFSMeterAudioProcessor* LUFSMeterAudioProcessorEditor::getProcessor() const
{
    return static_cast <LUFSMeterAudioProcessor*> (getAudioProcessor());
}


void LUFSMeterAudioProcessorEditor::resizeGuiComponents ()
{
    // Distances
    // ---------
    const int loudnessBarWidth = int(getProcessor()->loudnessBarWidth.getValue()) * -1;
    
    // Ensure that the momentaryLoudnessBarWidth % numberOfChannels = 0.
    int momentaryLoudnessBarWidth = loudnessBarWidth;
    // Determine widthOfIndividualChannel.
    const int numberOfChannels = getProcessor()->getMomentaryLoudnessForIndividualChannels().size();
    if (numberOfChannels != 0)
    {
        const int widthOfIndividualChannel = loudnessBarWidth / numberOfChannels;
           // Integer division.
        momentaryLoudnessBarWidth = numberOfChannels * widthOfIndividualChannel;
    }
    
    const int spaceBetweenBars = jmin (loudnessBarWidth/5, 10);
        // This distance is also used for the border on the right side.
    const int heightOfNumericValues = loudnessBarWidth/3;
    const int heightOfLoudnessCaptions = heightOfNumericValues;
    distanceBetweenLoudnessBarAndBottom = heightOfNumericValues
                                          + heightOfLoudnessCaptions;
    const int loudnessBarBottomPosition = getHeight()
                                       - distanceBetweenLoudnessBarAndBottom;
    const int heightOfLoudnessBar = loudnessBarBottomPosition
                                    - distanceBetweenLoudnessBarAndTop;
    const int loudnessBarNumericTopPosition = getHeight()
                                           - distanceBetweenLoudnessBarAndBottom;
    const int loudnessBarCaptionTopPosition = getHeight()
                                           - heightOfLoudnessCaptions;
    const int backgroundGridCaptionWidth = 35;
    
    // Background Grid
    backgroundGrid.setBounds(0,
                    distanceBetweenLoudnessBarAndTop,
                    getWidth(),
                    heightOfLoudnessBar);

    // Font for the loudnessCaptions
    const int fontHeight = heightOfLoudnessCaptions;
    const Font fontForCaptions (fontHeight);
    
    // Momentary Loudness
    const int momentaryLoudnessBarX = getWidth() - spaceBetweenBars 
                                                 - momentaryLoudnessBarWidth;
    momentaryLoudnessBarSum.setBounds(momentaryLoudnessBarX,
                                      distanceBetweenLoudnessBarAndTop,
                                      momentaryLoudnessBarWidth,
                                      heightOfLoudnessBar);
    momentaryLoudnessBar.setBounds(momentaryLoudnessBarX,
                                   distanceBetweenLoudnessBarAndTop,
                                   momentaryLoudnessBarWidth,
                                   heightOfLoudnessBar);
    momentaryLoudnessNumeric.setBounds (momentaryLoudnessBarX,
                                        loudnessBarNumericTopPosition,
                                        momentaryLoudnessBarWidth,
                                        heightOfNumericValues);
    momentaryLoudnessCaption.setBounds(momentaryLoudnessBarX,
                                       loudnessBarCaptionTopPosition,
                                       momentaryLoudnessBarWidth,
                                       heightOfLoudnessCaptions);
    momentaryLoudnessCaption.setFont(fontForCaptions);
    
    // Short Term Loudness
    const int shortTermLoudnessBarX = momentaryLoudnessBarX - spaceBetweenBars - loudnessBarWidth;
    shortTermLoudnessBar.setBounds(shortTermLoudnessBarX,
                                   distanceBetweenLoudnessBarAndTop,
                                   loudnessBarWidth,
                                   heightOfLoudnessBar);
    shortTermLoudnessNumeric.setBounds (shortTermLoudnessBarX,
                                        loudnessBarNumericTopPosition,
                                        loudnessBarWidth,
                                        heightOfNumericValues);
    shortTermLoudnessCaption.setBounds(shortTermLoudnessBarX,
                                       loudnessBarCaptionTopPosition,
                                       loudnessBarWidth,
                                       heightOfLoudnessCaptions);
    shortTermLoudnessCaption.setFont(fontForCaptions);
    
    // Loudness Range
    const int loudnessRangeBarX = shortTermLoudnessBarX - spaceBetweenBars - loudnessBarWidth;
    loudnessRangeBar.setBounds (loudnessRangeBarX,
                                distanceBetweenLoudnessBarAndTop,
                                loudnessBarWidth,
                                heightOfLoudnessBar);
    loudnessRangeNumeric.setBounds (loudnessRangeBarX,
                                    loudnessBarNumericTopPosition,
                                    loudnessBarWidth,
                                    heightOfNumericValues);
    loudnessRangeCaption.setBounds (loudnessRangeBarX,
                                    loudnessBarCaptionTopPosition,
                                    loudnessBarWidth,
                                    heightOfLoudnessCaptions);
    loudnessRangeCaption.setFont(fontForCaptions);
    
    // Integrated Loudness
    const int integratedLoudnessBarX = loudnessRangeBarX - spaceBetweenBars - loudnessBarWidth;
    integratedLoudnessBar.setBounds(integratedLoudnessBarX,
                                    distanceBetweenLoudnessBarAndTop,
                                    loudnessBarWidth,
                                    heightOfLoudnessBar);
    integratedLoudnessNumeric.setBounds (integratedLoudnessBarX,
                                         loudnessBarNumericTopPosition,
                                         loudnessBarWidth,
                                         heightOfNumericValues);
    integratedLoudnessCaption.setBounds(integratedLoudnessBarX,
                                        loudnessBarCaptionTopPosition,
                                        loudnessBarWidth,
                                        heightOfLoudnessCaptions);
    integratedLoudnessCaption.setFont(fontForCaptions);
    
    // Background grid caption
    const int backgroundGridCaptionX = integratedLoudnessBarX - spaceBetweenBars - backgroundGridCaptionWidth;
    backgroundGridCaption.setBounds(backgroundGridCaptionX, 0, backgroundGridCaptionWidth, loudnessBarBottomPosition + 32);
    
    // Background vertical lines and caption
    backgroundVerticalLinesAndCaption.setBounds(0, distanceBetweenLoudnessBarAndTop, jmax(backgroundGridCaptionX, 0), loudnessBarBottomPosition + 32 - distanceBetweenLoudnessBarAndTop);
    
    // Loudness history
    loudnessHistoryGroup.setBounds(0,
                                   distanceBetweenLoudnessBarAndTop,
                                   jmax(backgroundGridCaptionX, 0),
                                   heightOfLoudnessBar);
}

// quick-and-dirty function to format a timecode string
static const String timeToTimecodeString (const double seconds)
{
    const double absSecs = fabs (seconds);
    
    const int hours = (int) (absSecs / (60.0 * 60.0));
    const int mins  = ((int) (absSecs / 60.0)) % 60;
    const int secs  = ((int) absSecs) % 60;
    
    String s;
    if (seconds < 0)
        s = "-";
    
    s << String (hours).paddedLeft ('0', 2) << ":"
    << String (mins).paddedLeft ('0', 2) << ":"
    << String (secs).paddedLeft ('0', 2) << ":"
    << String (roundToInt (absSecs * 1000) % 1000).paddedLeft ('0', 3);
    
    return s;
}

#if 0
// quick-and-dirty function to format a bars/beats string
static const String ppqToBarsBeatsString (double ppq, double /*lastBarPPQ*/, int numerator, int denominator)
{
    if (numerator == 0 || denominator == 0)
        return "1|1|0";
    
    const int ppqPerBar = (numerator * 4 / denominator);
    const double beats  = (fmod (ppq, ppqPerBar) / ppqPerBar) * numerator;
    
    const int bar    = ((int) ppq) / ppqPerBar + 1;
    const int beat   = ((int) beats) + 1;
    const int ticks  = ((int) (fmod (beats, 1.0) * 960.0));
    
    String s;
    s << bar << '|' << beat << '|' << ticks;
    return s;
}
#endif

// Updates the text in our position label.
void LUFSMeterAudioProcessorEditor::displayPositionInfo (const AudioPlayHead::CurrentPositionInfo& pos)
{
    lastDisplayedPosition = pos;
    String displayText;
    displayText.preallocateBytes (128);
    
    displayText << String (pos.bpm, 2) << " bpm, "
    << pos.timeSigNumerator << '/' << pos.timeSigDenominator
    << "  -  " << timeToTimecodeString (pos.timeInSeconds);
    //    << "  -  " << ppqToBarsBeatsString (pos.ppqPosition, pos.ppqPositionOfLastBarStart,
    //                                        pos.timeSigNumerator, pos.timeSigDenominator);
    
    if (pos.isRecording)
        displayText << "  (recording)";
    else if (pos.isPlaying)
        displayText << "  (playing)";
    
    //infoLabel.setText (displayText, false);
}
