/*******************************************************************************
*   @author Tomáš Pospíšil
*
*   Tento kód byl sestaven na základì tutoriálu, dostupného na adrese:
*   https://docs.juce.com/master/tutorial_playing_sound_files.html a
*   https://docs.juce.com/master/tutorial_audio_thumbnail.html .
*
*  ==============================================================================

   Some of the code is part of the JUCE tutorials.
   Copyright (c) 2020 - Raw Material Software Limited

   The code included in this file is provided under the terms of the ISC license
   http://www.isc.org/downloads/software-support-policy/isc-license. Permission
   To use, copy, modify, and/or distribute this software for any purpose with or
   without fee is hereby granted provided that the above copyright notice and
   this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
   WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
   PURPOSE, ARE DISCLAIMED.

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

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

    This file contains the basic framework code for a JUCE plugin editor.

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

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

//==============================================================================
    BrowsefileAudioProcessorEditor::BrowsefileAudioProcessorEditor(BrowsefileAudioProcessor& p)
    : AudioProcessorEditor(&p), audioProcessor(p), state(Stoped), transportSource(), thumbnailCache(5), thumbnail(2048, formatManager, thumbnailCache), keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard)
{
    juce::Component::addAndMakeVisible(infoLabel);
    labelInit();

    juce::Component::addAndMakeVisible(textButton);
    textButton.setButtonText("BROWSE");
    textButton.setColour(juce::TextButton::buttonColourId, juce::Colour::fromRGB(9, 154, 237));
    textButton.onClick = [this] {browseButtonClicked(); };

    juce::Component::addAndMakeVisible(playButton);
    playButton.setButtonText("PLAY");
    playButton.setEnabled(true);
    playButton.onClick = [this] {playButtonClicked(); };


    juce::Component::addAndMakeVisible(stopButton);
    stopButton.setButtonText("STOP");
    stopButton.setEnabled(false);
    stopButton.onClick = [this] {stopButtonClicked(); };

    transportSource.addChangeListener(this);
    thumbnail.addChangeListener(this);
   

    setAudioChannels(0, 2);

    juce::Component::addAndMakeVisible(group);
    group.setTextLabelPosition(juce::Justification::centredTop);
    group.setText("MIDI input");

    juce::Component::addAndMakeVisible(midiInput);
    midiInput.addItem("MIDI controler", 1);

    juce::Component::addAndMakeVisible(midiInputLabel);
    midiInputLabel.setText("Select MIDI input", juce::dontSendNotification);
    midiInputLabel.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    midiInputLabel.setFont(14.0f);

    juce::Component::addAndMakeVisible(group2);
    group2.setTextLabelPosition(juce::Justification::centredTop);
    group2.setText("Data input");

    juce::Component::addAndMakeVisible(duration);
    duration.setText("Duration", juce::dontSendNotification);
    duration.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    duration.setFont(14.0f);

    juce::Component::addAndMakeVisible(envelope);
    envelope.setText("Envelope", juce::dontSendNotification);
    envelope.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    envelope.setFont(14.0f);

    juce::Component::addAndMakeVisible(density);
    density.setText("Density", juce::dontSendNotification);
    density.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    density.setFont(14.0f);

    juce::Component::addAndMakeVisible(timeOffset);
    timeOffset.setText("Time offset", juce::dontSendNotification);
    timeOffset.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    timeOffset.setFont(14.0f);

    juce::Component::addAndMakeVisible(delka);
    delka.setSliderStyle(Rotary);
    delka.setTextBoxStyle(TextBoxBelow, false, 60, 30);
    delka.setRange(0.0, 100.0, 1);
    delka.setColour(juce::Slider::textBoxTextColourId, juce::Colours::lightgrey);

    juce::Component::addAndMakeVisible(obalka);
    obalka.setSliderStyle(Rotary);
    obalka.setTextBoxStyle(TextBoxBelow, false, 60, 30);
    obalka.setRange(0.0, 3.0, 1);
    obalka.setColour(juce::Slider::textBoxTextColourId, juce::Colours::lightgrey);

    juce::Component::addAndMakeVisible(pocet);
    pocet.setSliderStyle(Rotary);
    pocet.setTextBoxStyle(TextBoxBelow, false, 60, 30);
    pocet.setRange(0.0, 10.0, 1);
    pocet.setColour(juce::Slider::textBoxTextColourId, juce::Colours::lightgrey);

    juce::Component::addAndMakeVisible(offset);
    offset.setSliderStyle(Rotary);
    offset.setTextBoxStyle(TextBoxBelow, false, 60, 30);
    offset.setRange(0.0, 300.0, 1);
    offset.setColour(juce::Slider::textBoxTextColourId, juce::Colours::lightgrey);

    juce::Component::addAndMakeVisible(group3);
    group3.setTextLabelPosition(juce::Justification::centredTop);
    group3.setText("Grain parameters");
    juce::Component::addAndMakeVisible(kanal1);
    kanal1.setButtonText("Channel 1");
    juce::Component::addAndMakeVisible(kanal2);
    kanal2.setButtonText("Channel 2");
    juce::Component::addAndMakeVisible(kanal3);
    kanal3.setButtonText("Channel 3");
    juce::Component::addAndMakeVisible(kanal4);
    kanal4.setButtonText("Channel 4");
    juce::Component::addAndMakeVisible(kanal5);
    kanal5.setButtonText("Channel 5");
    juce::Component::addAndMakeVisible(kanal6);
    kanal6.setButtonText("Channel 6");
    juce::Component::addAndMakeVisible(kanal7);
    kanal7.setButtonText("Channel 7");
    juce::Component::addAndMakeVisible(kanal8);
    kanal8.setButtonText("Channel 8");

    juce::Component::addAndMakeVisible(makeGrain);
    makeGrain.setButtonText("Create Grain");
    makeGrain.setColour(juce::TextButton::buttonColourId, juce::Colour::fromRGB(9, 154, 237));

    juce::Component::addAndMakeVisible(resetGrain);
    resetGrain.setButtonText("Reset Grain");

    juce::Component::addAndMakeVisible(playGrain);
    playGrain.setButtonText("Play Grain");

    juce::Component::addAndMakeVisible(info);
    info.setButtonText("?");
    info.setColour(juce::TextButton::buttonColourId, juce::Colour::fromRGB(255, 153, 51));
    info.setColour(juce::TextButton::textColourOffId, juce::Colours::black);

    juce::Component::addAndMakeVisible(group4);
    group4.setTextLabelPosition(juce::Justification::centredTop);
    group4.setText("Output pattern");
    juce::Component::addAndMakeVisible(hvezda);
    hvezda.setButtonText("Star");
    juce::Component::addAndMakeVisible(kruh);
    kruh.setButtonText("Ring");
    juce::Component::addAndMakeVisible(kruh2);
    kruh2.setButtonText("Ring(moving)");
    juce::Component::addAndMakeVisible(pulkruhL);
    pulkruhL.setButtonText("Semicircle(left)");
    juce::Component::addAndMakeVisible(pulkruhR);
    pulkruhR.setButtonText("Semicircle(right)");
    juce::Component::addAndMakeVisible(hodiny);
    hodiny.setButtonText("Hourglass");
    juce::Component::addAndMakeVisible(showpattern);
    showpattern.setButtonText("SHOW PATTERN");
    showpattern.setColour(juce::TextButton::buttonColourId, juce::Colour::fromRGB(9, 154, 237));

    juce::Component::addAndMakeVisible(group5);
    group5.setTextLabelPosition(juce::Justification::centredTop);
    group5.setText("Pattern visualization");

    juce::Component::addAndMakeVisible(chanel1);
    chanel1.setButtonText("CHANNEL 1");
    juce::Component::addAndMakeVisible(chanel2);
    chanel2.setButtonText("CHANNEL 2");
    juce::Component::addAndMakeVisible(chanel3);
    chanel3.setButtonText("CHANNEL 3");
    juce::Component::addAndMakeVisible(chanel4);
    chanel4.setButtonText("CHANNEL 4");
    juce::Component::addAndMakeVisible(chanel5);
    chanel5.setButtonText("CHANNEL 5");
    juce::Component::addAndMakeVisible(chanel6);
    chanel6.setButtonText("CHANNEL 6");
    juce::Component::addAndMakeVisible(chanel7);
    chanel7.setButtonText("CHANNEL 7");
    juce::Component::addAndMakeVisible(chanel8);
    chanel8.setButtonText("CHANNEL 8");

    juce::Component::addAndMakeVisible(keyboardComponent);

    juce::Component::setSize(1400, 750);

}

/*
* Destructor of the BrowsefileAudioProcessorEditor class, class generated by JUCE framework.
* It shuts down the audio.
*/
BrowsefileAudioProcessorEditor::~BrowsefileAudioProcessorEditor()
{
    shutdownAudio();
}

/*
* This function releases the blocks of data from memory.
*/
void BrowsefileAudioProcessorEditor::releaseResources()
{
    transportSource.releaseResources();
}

/*
* This function is trigered by listener, which handles if the button was clicked.
* When the listener catches some change this function is callled.
*
* @param source     The source which called this function.
*/
void BrowsefileAudioProcessorEditor::changeListenerCallback(juce::ChangeBroadcaster* source)
{
    if (source == &transportSource)
    {
        if (transportSource.isPlaying())
            stateChanged(Starting);
        else
            stateChanged(Stoped);
    }
    if (source == &thumbnail) 
    {
        juce::Component::repaint();
    }
}

/*
* This function is called when the Browse button in GUI is clicked. It calls the function
* which causes the computer to run dialog window to chose the file for audio player.
*/
void BrowsefileAudioProcessorEditor::browseButtonClicked()
{
    chooser = std::make_unique<juce::FileChooser>("Select a Wave file to play...", juce::File{}, "*.wav;*.aiff");
    int chooserFlags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles;

    chooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fileChooser)
        {
            juce::File file = fileChooser.getResult();
            prepareForPlaying(file);
        });
}


/*
* When the play button in GUI is clicked, this function is called. It starts the audio player.
*/
void BrowsefileAudioProcessorEditor::playButtonClicked()
{
    stateChanged(Starting);
}


/*
* This function is called, when the stop button in GUI is clicked. It stops the audio player.
*/
void BrowsefileAudioProcessorEditor::stopButtonClicked()
{
    stateChanged(Stoping);
}


/*
* This function changes the state of the audio player. The state is given by
* the enums defined in this class.
*
* @param newState       The enum that changes the state to new one.
*/
void BrowsefileAudioProcessorEditor::stateChanged(TranssportState newState)
{
    if (newState != state) {
        state = newState;

        switch (state)
        {
        case BrowsefileAudioProcessorEditor::Starting:
            playButton.setEnabled(false);
            stopButton.setEnabled(true);
            transportSource.start();
            break;
        case BrowsefileAudioProcessorEditor::Stoping:
            playButton.setEnabled(true);
            stopButton.setEnabled(false);
            transportSource.stop();
            break;
        case BrowsefileAudioProcessorEditor::Stoped:
            playButton.setEnabled(true);
            transportSource.setPosition(0.0);
            break;
        case BrowsefileAudioProcessorEditor::Playing:
            playButton.setEnabled(true);
            stopButton.setEnabled(false);
            break;
        default:
            break;
        }
    }

}

/*
* When this function is called, it prepares the AudioTransportSource object
* for later process.
*
* @param samplesPerBlockExpected    The nuber of samples expected in this block of samples.
* @param sampleRate                 Expected samplerate of data being played.
*/
void BrowsefileAudioProcessorEditor::prepareToPlay(int samplesPerBlockExpected, double sampleRate)
{
    transportSource.prepareToPlay(samplesPerBlockExpected, sampleRate);
}

/*
* This function copies the samples to the output buffer.
* 
* @param bufferToFill   Output buffer of the plugin. 
*/
void BrowsefileAudioProcessorEditor::getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill)
{
    if (readerSource.get() == nullptr)
    {
        bufferToFill.clearActiveBufferRegion();
        return;
    }
    transportSource.getNextAudioBlock(bufferToFill);
}

/*
* This function is called when the file is being loaded.
* 
* @param file       The audio file loaded to this plugin.
*/
void BrowsefileAudioProcessorEditor::prepareForPlaying(juce::File file) 
{
    formatManager.registerBasicFormats();
    if (file != juce::File{})                                                     
    {
        infoLabel.setText(file.getFullPathName(), juce::dontSendNotification);
        juce::AudioFormatReader* reader = formatManager.createReaderFor(file);

        if (reader != nullptr)
        {
            auto newSource = std::make_unique<juce::AudioFormatReaderSource>(reader, true);   
            transportSource.setSource(newSource.get(), 0, nullptr, reader->sampleRate);       
            stateChanged(Playing);
            playButton.setEnabled(true);  
            thumbnail.setSource(new juce::FileInputSource(file));
            readerSource.reset(newSource.release());                                          
        }
    }
}


/*
* This function is called when user attempts to load a file by the Browse button.
* The file is given by its path to this file.
*
* @param path       System path to file, which is trying to be loaded.
*/
void BrowsefileAudioProcessorEditor::loadFile(juce::String path) {

    juce::File file = juce::File(path);
    prepareForPlaying(file);
}

/*
* This function is called when the user tries to load the file by dragging the
* file to the plugin window. If the file has the required format, then the function
* returns true, otherwise it returns false.
*
* @param files      It is the array of files being draged to the audio player.
*/
bool BrowsefileAudioProcessorEditor::isInterestedInFileDrag(const juce::StringArray& files)
{
    for (juce::String file : files)
    {
        if (file.contains(".wav") || file.contains(".aiff")) {
            return true;
        }
    }
    return false;
}


/*
* This function is called when the file is dropped to the plugin window. If the file
* has the required format, the loadFile function is called.
*
* @param files      Array of files dropped to the plugin window.
* @param x          X coordinate of position where the files were dropped.
* @param y          Y coordinate of position where the files were dropped.
*/
void BrowsefileAudioProcessorEditor::filesDropped(const juce::StringArray& files, int x, int y)
{
    for (juce::String file : files)
    {
        if (isInterestedInFileDrag(file)) 
        {
            loadFile(file);
        }
    }

}


/*
* Function called by repaint function. It paints and repaints all the visual objects. The function
* repaints the cursor of the audio player and paint the thumbnails content.
*
* @param g       Graphical object, the window of the plugin itself.
*/
void BrowsefileAudioProcessorEditor::paint(juce::Graphics& g)
{
    g.fillAll(juce::Component::getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
    g.setColour(juce::Colours::white);
    g.setFont(18.0f);

    juce::Rectangle<int> thumbnailBounds(20, 220, 765, 140);           
    g.setColour(juce::Colours::darkgrey);
    g.fillRect(thumbnailBounds);
    g.setColour(juce::Colours::lightgrey);

    if (thumbnail.getNumChannels() == 0)
        paintIfNoFileLoaded(g, thumbnailBounds);
    else
        paintIfFileLoaded(g, thumbnailBounds);
}


/*
* This function prints the text into thumbnail based on the state of program.
*
* @param g                  Graphical object, the window of the plugin itself.
* @param thumbnailBounds    The boundery of the thumbnail object.
*/
void BrowsefileAudioProcessorEditor::paintIfNoFileLoaded(juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds)
{
    g.drawFittedText("No file loaded. Browse for file or drag the file to this window.", thumbnailBounds, juce::Justification::centred, 1);
}


/*
* If the file is loaded to the plugin, the waveshape representing its content is rendered.
*
* @param g                  Graphical object, the window of the plugin itself.
* @param thumbnailBounds    The boundery of the thumbnail object.
*/
void BrowsefileAudioProcessorEditor::paintIfFileLoaded(juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds)
{
    float audioLength = (float)thumbnail.getTotalLength();
    thumbnail.drawChannels(g,thumbnailBounds,0.0,audioLength,1.8f);
    
    if (audioLength > 0.0f)
    {
        float audioPosition = (float)transportSource.getCurrentPosition();
        float drawPosition = (audioPosition / audioLength) * (float)thumbnailBounds.getWidth() + (float)thumbnailBounds.getX();                                

        g.setColour(juce::Colours::darkred);
        g.drawLine(drawPosition, (float)thumbnailBounds.getY(), drawPosition, (float)thumbnailBounds.getBottom(), 2.0f);
    }
}


/*
* This function reacts on event, when the mouse button is clicked. It controls the cursor which
* sets the position of the AudioTransportSource object.
*
* @param event      The event, which caused the function calling.
*/
void BrowsefileAudioProcessorEditor::mouseDown(const juce::MouseEvent& event)
{
    double duration = transportSource.getLengthInSeconds();
    juce::Rectangle<int> thumbnailBounds(20, 220, 765, 140);            
    if (duration > 0.0)
    {
        float clickPosition = event.position.x;
        double audioPosition = ((clickPosition-20) / (float)thumbnailBounds.getWidth()) * duration;
        transportSource.setPosition(audioPosition);
    }
}


/*
* This function is called everytime the Timer ends the cycle. It is called to repaint objects
* like cursor.
*/
void BrowsefileAudioProcessorEditor::timerCallback()
{
    juce::Component::repaint();
}

/*
* This function sets the positions of all the components in the plugin window.
* Each component has its own position, height and width.
*/
void BrowsefileAudioProcessorEditor::resized()
{
    group.setBounds(5, 35, 795, 75);
    midiInputLabel.setBounds(15, 60, 120, 30);
    midiInput.setBounds(130, 60, 655, 30);
    group2.setBounds(5, 115, 795, 265);
    infoLabel.setBounds(20, 140, 500, 30);
    textButton.setBounds(530, 140, 255, 30);
    playButton.setBounds(20, 180, 378, 30);
    stopButton.setBounds(407, 180, 378, 30);
    group3.setBounds(5, 385, 795, 200);
    duration.setBounds(50, 400, 120, 30);
    envelope.setBounds(190, 400, 120, 30);
    density.setBounds(335, 400, 120, 30);
    timeOffset.setBounds(465, 400, 120, 30);
    delka.setBounds(20, 420, 120,120);
    obalka.setBounds(160, 420, 120, 120);
    pocet.setBounds(300, 420, 120, 120);
    offset.setBounds(440, 420, 120, 120);
    kanal1.setBounds(22, 535, 80, 50);
    kanal2.setBounds(117, 535, 80, 50);
    kanal3.setBounds(212, 535, 80, 50);
    kanal4.setBounds(307, 535, 80, 50);
    kanal5.setBounds(402, 535, 80, 50);
    kanal6.setBounds(497, 535, 80, 50);
    kanal7.setBounds(592, 535, 80, 50);
    kanal8.setBounds(687, 535, 80, 50);
    makeGrain.setBounds(560, 410, 225, 30);
    resetGrain.setBounds(560, 450, 225, 30);
    playGrain.setBounds(560, 510, 225, 30);
    info.setBounds(1355, 5, 40, 25);
    group4.setBounds(805, 35, 590, 85);
    hvezda.setBounds(820, 50, 120, 30);
    pulkruhL.setBounds(960, 50, 120, 30);
    kruh.setBounds(1100, 50, 120, 30);
    hodiny.setBounds(820, 80, 120, 30);
    pulkruhR.setBounds(960, 80, 120, 30);
    kruh2.setBounds(1100, 80, 120, 30);
    showpattern.setBounds(1230, 65, 145, 30);
    group5.setBounds(805, 125, 590, 460);
    chanel1.setBounds(1050, 155, 100, 30);
    chanel5.setBounds(1050, 535, 100, 30);
    chanel7.setBounds(850, 345, 100, 30);
    chanel3.setBounds(1250, 345, 100, 30);
    chanel8.setBounds(905, 235, 100, 30);
    chanel6.setBounds(905, 455, 100, 30);
    chanel2.setBounds(1195, 235, 100, 30);
    chanel4.setBounds(1195, 455, 100, 30);

    keyboardComponent.setBounds(200, 590, 1000, 150);
}

/*
* Function that initialize the search bar next to BROWSE button. 
*/
void BrowsefileAudioProcessorEditor::labelInit() 
{
    infoLabel.setText("Browse for the file...", juce::dontSendNotification);
    infoLabel.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
    infoLabel.setColour(juce::Label::backgroundColourId, juce::Colours::darkgrey);
    infoLabel.setFont(15.0f);
}






