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

    prehravac.h
    Created: 20 Jan 2023 10:15:27pm
    Author:  Tomáš Pospíšil

   Tento kód byl sestaven na základě tutoriálů, dostupných na adrese:
   https://docs.juce.com/master/tutorial_playing_sound_files.html a
   https://docs.juce.com/master/tutorial_audio_thumbnail.html.

   This code was built based on tutorials, which can be found on address:
   https://docs.juce.com/master/tutorial_playing_sound_files.html and
   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 class handles the opening of .wav or .aiff file as a source of samples for grains.
* This source is loaded to basic audio player, where the content can be listened. This
* class also contains functions for drag&drop functioonality.
*/

#pragma once

class audioPlayer : public juce::AudioAppComponent,
                    public juce::ChangeListener,
                    public juce::FileDragAndDropTarget,
                    public juce::Timer
{
    
public:

    /*
    * Constructor of this class. It initialise the audio device for reproduction. It sets the
    * state to stopped and sets the tumbnail for proper use.
    */
    audioPlayer() : state(Stopped), thumbnailCache(480), thumbnail(48000, formatManager, thumbnailCache), juce::AudioAppComponent(other) 
    {
        other.initialise(2, 8, nullptr, true);
        audSettings.reset(new juce::AudioDeviceSelectorComponent(other, 0, 2, 0, 8, true, false, true, true));
    }

    /*
    * Enums for changing the state of audio player.
    */
    enum TransportState
    {
        Stopped,
        Starting,
        Playing,
        Stopping,
        Pause
    };


    /*
    * Variables of this class
    */
    int                                                 position{};
    bool                                                bufferPlaying = false;
    bool                                                fileTypeNotSupported = false;
    bool                                                sourceSet = false;

    juce::Label                                         pathToFile;
    juce::TextButton                                    browse;
    juce::TextButton                                    play;
    juce::TextButton                                    stop;

    std::unique_ptr<juce::FileChooser>                  chooser;

    juce::AudioFormatManager                            formatManager;
    std::unique_ptr<juce::AudioFormatReaderSource>      readerSource;
    juce::AudioTransportSource                          transportSource;
    TransportState                                      state;

    juce::AudioThumbnailCache                           thumbnailCache;
    juce::AudioThumbnail                                thumbnail;

    juce::AudioSampleBuffer                             temp;
    juce::AudioSampleBuffer                             fileBuffer;
    juce::AudioSampleBuffer                             grainBuffer;

    juce::AudioDeviceManager other;
    std::unique_ptr <juce::AudioDeviceSelectorComponent> audSettings;

    juce::GroupComponent                                group1;


    /*
    * 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 prepareToPlay(int samplesPerBlockExpected, double sampleRate) override
    {
        transportSource.prepareToPlay(samplesPerBlockExpected, sampleRate);
    }


    /*
    * 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 changeListenerCallback(juce::ChangeBroadcaster* source) override
    {
        if (source == &transportSource)
        {
            if (transportSource.isPlaying())
                changeState(Playing);
        }
    }

    /*
    * This function is called when the file is being loaded. The file is loaded
    * also to the fileBuffer. The file is resampled to 48 kHz to match the samplerate
    * of the project.
    * 
    * @param file       The audio file loaded to this plugin.
    */
    void prepareForPlaying(juce::File file)
    {
        formatManager.registerBasicFormats();
        if (file != juce::File{})
        {
            pathToFile.setText(file.getFullPathName(), juce::dontSendNotification);
            juce::AudioFormatReader* reader = formatManager.createReaderFor(file);

            if (reader != nullptr)
            {
                //----------transportSource----------------------------------------------------
                fileTypeNotSupported = false;

                auto newSource = std::make_unique<juce::AudioFormatReaderSource>(reader, true);
                transportSource.setSource(newSource.get(), 0, nullptr, reader->sampleRate);
                thumbnail.setSource(new juce::FileInputSource(file));
                readerSource.reset(newSource.release());
                
                sourceSet = true;
                changeState(Playing);
                play.setEnabled(true);

                //----------fileBuffer---------------------------------------------------------
                double ratio = (reader->sampleRate / 48000);
                temp.setSize((int)reader->numChannels, (int)reader->lengthInSamples);
                fileBuffer.setSize((int)reader->numChannels, (int)(((double)reader->lengthInSamples) / ratio));

                reader->read(&temp, 0, (int)reader->lengthInSamples, 0, true, true);

                juce::ScopedPointer<juce::LagrangeInterpolator> resampler = new juce::LagrangeInterpolator();

                const float** inputs = temp.getArrayOfReadPointers();
                float** outputs = fileBuffer.getArrayOfWritePointers();

                for (int c = 0; c < fileBuffer.getNumChannels(); c++)
                {
                    resampler->reset();
                    resampler->process(ratio, inputs[c], outputs[c], fileBuffer.getNumSamples());
                }
            }
        }
    }
    
    /*
    * This function is called when user attempts to load a file by the Open button.
    * The file is given by its path to this file.
    * 
    * @param path       System path to file, which is trying to be loaded.
    */
    void 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 isInterestedInFileDrag(const juce::StringArray& files) override
    {
        for (juce::String file : files)
        {
            if (file.contains(".wav") || file.contains(".aiff")) {
                return true;
            }
        }
        fileTypeNotSupported = 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 filesDropped(const juce::StringArray& files, int x, int y) override
    {
        for (juce::String file : files)
        {
            if (isInterestedInFileDrag(file))
            {
                loadFile(file);
            }
        }

    }

    /*
    * 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 changeState(TransportState newState)
    {
        if (state != newState)
        {
            state = newState;

            switch (state)
            {
            case Starting:
                play.setEnabled(false);
                stop.setEnabled(true);
                transportSource.start();
                play.setButtonText("Pause");
                break;
            case Stopping:
                play.setEnabled(true);
                stop.setEnabled(false);
                transportSource.stop();
                changeState(Stopped);
                
                break;
            case Stopped:
                play.setEnabled(true);
                play.setButtonText("Play");
                transportSource.setPosition(0.0);
                break;
            case Playing:
                play.setEnabled(true);
                stop.setEnabled(true);
                break;
            case Pause:
                play.setEnabled(true);
                stop.setEnabled(true);
                transportSource.stop();
                play.setButtonText("Play");
                break;
            default:
                break;
            }
        }
    }
    
    /*
    * This function is called when the Open 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 openButtonClicked()
    {
        chooser = std::make_unique<juce::FileChooser>("Select a 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. When the audio player 
    * is already playing, it pauses the player. The behaviour of the audio player is changed
    * by changeState function.
    */
    void playButtonClicked()
    {
        startTimer(10);
        if(play.getButtonText() == "Pause")
        {
            changeState(Pause);
        }
        else
            changeState(Starting);
    }

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


    /*
    * This function intialise the label in the search bar in GUI.
    */
    void labelInit()
    {
        pathToFile.setText("Browse for the file...", juce::dontSendNotification);
        pathToFile.setColour(juce::Label::textColourId, juce::Colours::lightgrey);
        pathToFile.setColour(juce::Label::backgroundColourId, juce::Colours::darkgrey);
        pathToFile.setFont(15.0f);
    }
};