/*******************************************************************************
*   @author Tomáš Pospíšil
*
*   Tento kód byl sestaven na základì tutoriálu, dostupného na adrese:
*   https://docs.juce.com/master/tutorial_handling_midi_events.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"

//==============================================================================
MidiTestAudioProcessorEditor::MidiTestAudioProcessorEditor (MidiTestAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p), keyboardComponent(keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard),
    startTime(juce::Time::getMillisecondCounterHiRes() * 0.001)
{
    AudioProcessorEditor::setOpaque(true);
    AudioProcessorEditor::addAndMakeVisible(midiInputListLabel);
    midiInputListLabel.setText("MIDI Input:", juce::dontSendNotification);
    midiInputListLabel.attachToComponent(&midiInputList, true);

    AudioProcessorEditor::addAndMakeVisible(midiInputList);
    midiInputList.setTextWhenNoChoicesAvailable("No MIDI Inputs Enabled");
    auto midiInputs = juce::MidiInput::getAvailableDevices();

    juce::StringArray midiInputNames;

    for (juce::MidiDeviceInfo input : midiInputs)
        midiInputNames.add(input.name);

    midiInputList.addItemList(midiInputNames, 1);
    midiInputList.onChange = [this] { setMidiInput(midiInputList.getSelectedItemIndex()); };

    for (juce::MidiDeviceInfo input : midiInputs)
    {
        if (deviceManager.isMidiInputDeviceEnabled(input.identifier))
        {
            setMidiInput(midiInputs.indexOf(input));
            break;
        }
    }

    if (midiInputList.getSelectedId() == 0)
        setMidiInput(0);

    AudioProcessorEditor::addAndMakeVisible(keyboardComponent);
    keyboardState.addListener(this);

    AudioProcessorEditor::addAndMakeVisible(midiMessagesBox);
    midiMessagesBox.setMultiLine(true);
    midiMessagesBox.setReturnKeyStartsNewLine(true);
    midiMessagesBox.setReadOnly(true);
    midiMessagesBox.setScrollbarsShown(true);
    midiMessagesBox.setCaretVisible(false);
    midiMessagesBox.setPopupMenuEnabled(true);
    midiMessagesBox.setColour(juce::TextEditor::backgroundColourId, juce::Colour(0x32ffffff));
    midiMessagesBox.setColour(juce::TextEditor::outlineColourId, juce::Colour(0x1c000000));
    midiMessagesBox.setColour(juce::TextEditor::shadowColourId, juce::Colour(0x16000000));

    AudioProcessorEditor::setSize (900, 800);
}


/*
* Destructor of the MidiTestAudioProcessorEditor class
*/
MidiTestAudioProcessorEditor::~MidiTestAudioProcessorEditor()
{
    keyboardState.removeListener(this);
    deviceManager.removeMidiInputDeviceCallback(juce::MidiInput::getAvailableDevices()[midiInputList.getSelectedItemIndex()].identifier, this);
}
//==============================================================================


/*
* This function is called to set the MIDI input from the MIDI input list. The input device is
* set by its index in the device list. 
* 
* @param index      Index of the input device in the device list.
*/
void MidiTestAudioProcessorEditor::setMidiInput(int index)
{
    juce::Array<juce::MidiDeviceInfo> list = juce::MidiInput::getAvailableDevices();

    deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, this);

    juce::MidiDeviceInfo newInput = list[index];

    if (!deviceManager.isMidiInputDeviceEnabled(newInput.identifier))
        deviceManager.setMidiInputDeviceEnabled(newInput.identifier, true);
    deviceManager.addMidiInputDeviceCallback(newInput.identifier, this);
    midiInputList.setSelectedId(index + 1, juce::dontSendNotification);

    lastInputIndex = index;
}


/*
* This function is called to return the string, which has the description of the MIDI message sent
* by the MIDI device.
* 
* @param m      MIDI message sent by the MIDI device.
*/
juce::String MidiTestAudioProcessorEditor::getMidiMessageDescription(const juce::MidiMessage& m)
{
    if (m.isNoteOn())           return "Note on " + juce::MidiMessage::getMidiNoteName(m.getNoteNumber(), true, true, 3);
    if (m.isNoteOff())          return "Note off " + juce::MidiMessage::getMidiNoteName(m.getNoteNumber(), true, true, 3);
    if (m.isProgramChange())    return "Program change " + juce::String(m.getProgramChangeNumber());
    if (m.isPitchWheel())       return "Pitch wheel " + juce::String(m.getPitchWheelValue());
    if (m.isAftertouch())       return "After touch " + juce::MidiMessage::getMidiNoteName(m.getNoteNumber(), true, true, 3) + ": " + juce::String(m.getAfterTouchValue());
    if (m.isChannelPressure())  return "Channel pressure " + juce::String(m.getChannelPressureValue());
    if (m.isAllNotesOff())      return "All notes off";
    if (m.isAllSoundOff())      return "All sound off";
    if (m.isMetaEvent())        return "Meta event";

    if (m.isController())
    {
        juce::String name(juce::MidiMessage::getControllerName(m.getControllerNumber()));

        if (name.isEmpty())
            name = "[" + juce::String(m.getControllerNumber()) + "]";

        return "Controller " + name + ": " + juce::String(m.getControllerValue());
    }

    return juce::String::toHexString(m.getRawData(), m.getRawDataSize());
}


/*
* This function inserts MIDI message to the midiMessagesBox.
* 
* @param m      String MIDI message.
*/
void MidiTestAudioProcessorEditor::logMessage(const juce::String& m)
{
    midiMessagesBox.moveCaretToEnd();
    midiMessagesBox.insertTextAtCaret(m + juce::newLine);
}


/*
* This function is handeling incoming MIDI message.
* 
* @param source     The source of MIDI messages.
* @param message    The incoming MIDI message.
*/
void MidiTestAudioProcessorEditor::handleIncomingMidiMessage(juce::MidiInput* source, const juce::MidiMessage& message)
{
    const juce::ScopedValueSetter<bool> scopedInputFlag(isAddingFromMidiInput, true);
    keyboardState.processNextMidiEvent(message);
    postMessageToList(message, source->getName());
}


/*
* When the MIDI message NoteOn is sent, this function is called.
* 
* @param midiChannel    The MIDI channel the message is sent on.
* @param midiNoteNumber The number of the MIDI note.
* @param velocity       The velocity with which the key was pressed.
*/
void MidiTestAudioProcessorEditor::handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    if (!isAddingFromMidiInput)
    {
        juce::MidiMessage m = juce::MidiMessage::noteOn(midiChannel, midiNoteNumber, velocity);
        m.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001);
        postMessageToList(m, "On-Screen Keyboard");
    }
}


/*
* When the key is released this function is called.
* 
* @param midiChannel    The MIDI channel the message is sent on.
* @param midiNoteNumber The number of the MIDI note.
* @param velocity       The velocity with which the key was released.
*/
void MidiTestAudioProcessorEditor::handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
    if (!isAddingFromMidiInput)
    {
        juce::MidiMessage m = juce::MidiMessage::noteOff(midiChannel, midiNoteNumber);
        m.setTimeStamp(juce::Time::getMillisecondCounterHiRes() * 0.001);
        postMessageToList(m, "On-Screen Keyboard");
    }
}


/*
* This function posts MIDI message to the midiMeassagesBox.
* 
* @param message    MIDI message, which is supposed to be added to the midiMessagesBox.
* @param source     The source of the MIDI message.         
*/
void MidiTestAudioProcessorEditor::postMessageToList(const juce::MidiMessage& message, const juce::String& source)
{
    (new IncomingMessageCallback(this, message, source))->post();
}


/*
* This function adds the message to the midiMessagesBox. It prints the time from the start of the 
* program along with MIDI message with controler its comming from.
* 
* @param message    MIDI message, which is supposed to be added to the midiMessagesBox.
* @param source     The source of the MIDI message.
*/
void MidiTestAudioProcessorEditor::addMessageToList(const juce::MidiMessage& message, const juce::String& source)
{
    double time = message.getTimeStamp() - startTime;

    int hours = ((int)(time / 3600.0)) % 24;
    int minutes = ((int)(time / 60.0)) % 60;
    int seconds = ((int)time) % 60;
    int millis = ((int)(time * 1000.0)) % 1000;

    juce::String timecode = juce::String::formatted("%02d:%02d:%02d.%03d",
        hours,
        minutes,
        seconds,
        millis);

    juce::String description = getMidiMessageDescription(message);

    juce::String midiMessageString(timecode + "  -  " + description + " (" + source + ")"); // [7]
    logMessage(midiMessageString);
}

//==============================================================================

/*
* This function paints the plugin window.
* 
* @param g      Graphical object, the window of the plugin itself. 
*/
void MidiTestAudioProcessorEditor::paint (juce::Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll(juce::Component::getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
}


/*
* This function sets the position of all components in the plugin window.
*/
void MidiTestAudioProcessorEditor::resized()
{
    midiInputList.setBounds(100, 20, 750, 20);
    keyboardComponent.setBounds(50, 60, 800, 100);
    midiMessagesBox.setBounds(50, 180, 800, 400);
}
