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

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

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

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

//==============================================================================
CustomDrumsAudioProcessor::CustomDrumsAudioProcessor()
    : AudioProcessor(BusesProperties()
        .withOutput("Output", juce::AudioChannelSet::stereo(), true)
    ) {

    formatManager.registerBasicFormats();
    wavFormat = formatManager.findFormatForFileExtension("wav");
}

CustomDrumsAudioProcessor::~CustomDrumsAudioProcessor() {

}

void CustomDrumsAudioProcessor::loadFiles() {
    const static std::vector<std::string> files = {
        "bass_wav",
        "crash_wav",
        "floortom_wav",
        "hihat_wav",
        "hihatfoot_wav",
        "hihatopen_wav",
        "ride_wav",
        "sharedrum_wav",
        "sharestick_wav",
        "tom1_wav",
        "tom2_wav",
        "clap_wav"
    };

    for (auto file : files) {
        int dataSize;
        const char* audioData = BinaryData::getNamedResource(file.c_str(), dataSize);
        juce::MemoryInputStream input{ audioData, static_cast<std::size_t>(dataSize), false };
        loadFile(&input);
    }

}

void CustomDrumsAudioProcessor::loadFile(juce::MemoryInputStream* input) {
    int fileIndex = fileBuffers.size();
    auto* reader = wavFormat->createReaderFor(input, false);
    if (reader != nullptr) {
        fileBuffers.push_back({});
        fileBuffers[fileIndex].setSize((int)reader->numChannels, (int)reader->lengthInSamples);  // [4]
        reader->read(&fileBuffers[fileIndex],                                                      // [5]
            0,                                                                //  [5.1]
            (int)reader->lengthInSamples,                                    //  [5.2]
            0,                                                                //  [5.3]
            true,                                                             //  [5.4]
            true);                                                            //  [5.5]
    }

}

//==============================================================================
const juce::String CustomDrumsAudioProcessor::getName() const {
    return JucePlugin_Name;
}

bool CustomDrumsAudioProcessor::acceptsMidi() const {
    return true;
}

bool CustomDrumsAudioProcessor::producesMidi() const {
    return true;
}

bool CustomDrumsAudioProcessor::isMidiEffect() const {
    return false;
}

double CustomDrumsAudioProcessor::getTailLengthSeconds() const {
    return 0.0;
}

int CustomDrumsAudioProcessor::getNumPrograms() {
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
    // so this should be at least 1, even if you're not really implementing programs.
}

int CustomDrumsAudioProcessor::getCurrentProgram() {
    return 0;
}

void CustomDrumsAudioProcessor::setCurrentProgram(int index) {
}

const juce::String CustomDrumsAudioProcessor::getProgramName(int index) {
    return {};
}

void CustomDrumsAudioProcessor::changeProgramName(int index, const juce::String& newName) {
}

//==============================================================================
void CustomDrumsAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    loadFiles();
}

void CustomDrumsAudioProcessor::releaseResources() {
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
    fileBuffers.clear();
    fileQueue.clear();
}

bool CustomDrumsAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const {
    juce::ignoreUnused(layouts);
    return true;
}

void CustomDrumsAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    juce::MidiBuffer processedMidi;

    for (const auto metadata : midiMessages) {
        auto message = metadata.getMessage();
        const auto time = metadata.samplePosition;


        if (message.isNoteOn()) {

            FilePlay play;
            play.position = 0;
            play.fileIndex = message.getNoteNumber() % 12;
            play.volume = message.getFloatVelocity() * message.getFloatVelocity();
            fileQueue.push_back(play);
        }

        processedMidi.addEvent(message, time);
    }

    playSamples(buffer);

    midiMessages.swapWith(processedMidi);
}

//==============================================================================
bool CustomDrumsAudioProcessor::hasEditor() const {
    return true; // (change this to false if you choose to not supply an editor)
}

juce::AudioProcessorEditor* CustomDrumsAudioProcessor::createEditor() {
    return new CustomDrumsAudioProcessorEditor(*this);
}

void CustomDrumsAudioProcessor::playSamples(juce::AudioBuffer<float>& buffer) {
    int numberOfSamples = buffer.getNumSamples();
    buffer.clear();
    for (int i = 0; i < numberOfSamples; i++) {
        for (int j = 0; j < fileQueue.size(); j++) {
            FilePlay& file = fileQueue[j];
            int& position = file.position;
            auto& fileBuffer = fileBuffers[file.fileIndex];
            float volume = file.volume;

            if (position >= fileBuffer.getNumSamples()) {
                fileQueue.erase(fileQueue.begin() + j);
                continue;
            }

            buffer.addSample(0, i, fileBuffer.getSample(0, position) * volume);
            buffer.addSample(1, i, fileBuffer.getSample(1, position) * volume);

            position++;
        }
    }
}

//==============================================================================
void CustomDrumsAudioProcessor::getStateInformation(juce::MemoryBlock& destData) {
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void CustomDrumsAudioProcessor::setStateInformation(const void* data, int sizeInBytes) {
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

//==============================================================================
// This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() {
    return new CustomDrumsAudioProcessor();
}
