/*
    ------------------------------------------------------------------

    This file is part of the Open Ephys GUI
    Copyright (C) 2013 Open Ephys

    ------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "GenericProcessor.h"
#include "../UI/UIComponent.h"

GenericProcessor::GenericProcessor(const String& name_) : AccessClass(),
    sourceNode(0), destNode(0), isEnabled(true), wasConnected(false),
    nextAvailableChannel(0), saveOrder(-1), loadOrder(-1), currentChannel(-1),
     parametersAsXml(nullptr),  name(name_), paramsWereLoaded(false)
{
}

GenericProcessor::~GenericProcessor()
{
}

AudioProcessorEditor* GenericProcessor::createEditor()
{
    editor = new GenericEditor(this, true);
    return editor;
}

Parameter& GenericProcessor::getParameterByName(String name_)
{
    // doesn't work
    for (int i = 0; i < getNumParameters(); i++)
    {

        Parameter& p =  parameters.getReference(i);
        const String parameterName = p.getName();

        if (parameterName.compare(name_) == 0) // fails at this point
            return p;//parameters.getReference(i);
    }

    static Parameter nullParam = Parameter("VOID", false, -1);

    return nullParam;

}

Parameter& GenericProcessor::getParameterReference(int parameterIndex)
{

    return parameters.getReference(parameterIndex);

}

void GenericProcessor::setParameter(int parameterIndex, float newValue)
{
    editor->updateParameterButtons(parameterIndex);
    std::cout << "Setting parameter" << std::endl;

    if (currentChannel >= 0)
    {
        Parameter& p =  parameters.getReference(parameterIndex);
        p.setValue(newValue, currentChannel);
    }

}

const String GenericProcessor::getParameterName(int parameterIndex)
{
    Parameter& p=parameters.getReference(parameterIndex);
    return p.getName();
}

const String GenericProcessor::getParameterText(int parameterIndex)
{
    Parameter& p = parameters.getReference(parameterIndex);
    return p.getDescription();
}

var GenericProcessor::getParameterVar(int parameterIndex, int parameterChannel)
{
    Parameter& p=parameters.getReference(parameterIndex);
    return p.operator[](parameterChannel);
}

void GenericProcessor::prepareToPlay(double sampleRate_, int estimatedSamplesPerBlock)
{

}

void GenericProcessor::releaseResources()
{
    // use the disable() function instead
    // releaseResources() is called by Juce at unpredictable times
    // disable() is only called by the ProcessorGraph at the end of acquisition
}

int GenericProcessor::getNextChannel(bool increment)
{
    int chan = nextAvailableChannel;

    //std::cout << "Next channel: " << chan << ", num inputs: " << getNumInputs() << std::endl;

    if (increment)
        nextAvailableChannel++;

    if (chan < getNumInputs())
        return chan;
    else
        return -1;

}

void GenericProcessor::resetConnections()
{

    nextAvailableChannel = 0;

    wasConnected = false;
}

void GenericProcessor::setNumSamples(MidiBuffer& events, int sampleIndex)
{

    uint8 data[2];

    data[0] = BUFFER_SIZE; 	// most-significant byte
    data[1] = nodeId; 		// least-significant byte

    events.addEvent(data, 		// spike data
                    2, 			// total bytes
                    sampleIndex); // sample index

}

int GenericProcessor::getNumSamples(MidiBuffer& events)
{

    int numRead = 0;

    if (events.getNumEvents() > 0)
    {

        // int m = events.getNumEvents();

        //std::cout << getName() << " received " << m << " events." << std::endl;

        MidiBuffer::Iterator i(events);
        MidiMessage message(0xf4);

        int samplePosition = -5;

        while (i.getNextEvent(message, samplePosition))
        {

            const uint8* dataptr = message.getRawData();

            if (*dataptr == BUFFER_SIZE)
            {
                numRead = message.getTimeStamp();
            }
        }
    }

    return numRead;
}

void GenericProcessor::setSourceNode(GenericProcessor* sn)
{
    //std::cout << "My name is " << getName() << ". Setting source node." << std::endl;

    if (!isSource())
    {
        //	std::cout << " I am not a source." << std::endl;

        if (sn != 0)
        {

            //	std::cout << " The source is not blank." << std::endl;

            if (!sn->isSink())
            {
                //		std::cout << " The source is not a sink." << std::endl;

                if (sourceNode != sn)
                {

                    //			std::cout << " The source is new and named " << sn->getName() << std::endl;

                    if (this->isMerger())
                        setMergerSourceNode(sn);
                    else
                        sourceNode = sn;

                    sn->setDestNode(this);

                }
                else
                {
                    //			std::cout << "  The source node is not new." << std::endl;
                }
            }
            else
            {
                //		std::cout << " The source is a sink." << std::endl;
                sourceNode = 0;
            }

        }
        else
        {
            //		std::cout << " The source is blank." << std::endl;
            sourceNode = 0;
        }
    }
    else
    {
        //	std::cout << " I am a source. I can't have a source node." << std::endl;

        if (sn != 0)
            sn->setDestNode(this);
    }
}


void GenericProcessor::setDestNode(GenericProcessor* dn)
{


    //	std::cout << "My name is " << getName() << ". Setting dest node." << std::endl;

    if (!isSink())
    {
        //	std::cout << "  I am not a sink." << std::endl;

        if (dn != 0)
        {
            //		std::cout << "  The dest node is not blank." << std::endl;

            if (!dn->isSource())
            {

                //		std::cout << "  The dest node is not a source." << std::endl;

                if (destNode != dn)
                {
                    //		std::cout << "  The dest node is new and named " << dn->getName() << std::endl;
                    //
                    if (this->isSplitter())
                        setSplitterDestNode(dn);
                    else
                        destNode = dn;

                    dn->setSourceNode(this);

                }
                else
                {
                    //		std::cout << "  The dest node is not new." << std::endl;
                }
            }
            else
            {

                //	std::cout << "  The dest node is a source." << std::endl;

                destNode = 0;
            }
        }
        else
        {
            //	std::cout << "  The dest node is blank." << std::endl;

            destNode = 0;
        }
    }
    else
    {

        //std::cout << "  I am a sink, I can't have a dest node." << std::endl;
        //if (dn != 0)
        //	dn->setSourceNode(this);
    }
}

void GenericProcessor::clearSettings()
{
    settings.originalSource = 0;
    settings.numInputs = 0;
    settings.numOutputs = 0;
    settings.sampleRate = getDefaultSampleRate();

    recordStatus.clear();

    for (int i = 0; i < channels.size(); i++)
    {
        recordStatus.add(channels[i]->getRecordState());
    }

    channels.clear();
    eventChannels.clear();

}

void GenericProcessor::update()
{

    std::cout << getName() << " updating settings." << std::endl;

    clearSettings();

    if (sourceNode != 0)
    {
        // everything is inherited except numOutputs
        settings = sourceNode->settings;
        settings.numInputs = settings.numOutputs;
        settings.numOutputs = settings.numInputs;

        for (int i = 0; i < sourceNode->channels.size(); i++)
        {
            Channel* sourceChan = sourceNode->channels[i];
            Channel* ch = new Channel(*sourceChan);
            ch->setProcessor(this);
            ch->bitVolts = ch->bitVolts*getDefaultBitVolts();

            if (i < recordStatus.size())
            {
                ch->setRecordState(recordStatus[i]);
            }

            channels.add(ch);
        }

        for (int i = 0; i < sourceNode->eventChannels.size(); i++)
        {
            Channel* sourceChan = sourceNode->eventChannels[i];
            Channel* ch = new Channel(*sourceChan);
            ch->sampleRate = getDefaultSampleRate();
            ch->bitVolts = getDefaultBitVolts();
            eventChannels.add(ch);
        }

    }
    else
    {

        settings.numOutputs = getDefaultNumOutputs();
        settings.sampleRate = getDefaultSampleRate();

        for (int i = 0; i < getNumOutputs(); i++)
        {
            Channel* ch = new Channel(this, i);
            ch->sampleRate = getDefaultSampleRate();
            ch->bitVolts = getDefaultBitVolts();

             if (i < recordStatus.size())
            {
                ch->setRecordState(recordStatus[i]);
            }

            channels.add(ch);
        }

    }

    if (this->isSink())
    {
        settings.numOutputs = 0;
    }

    updateSettings(); // custom settings code

    // required for the ProcessorGraph to know the
    // details of this processor:
    setPlayConfigDetails(getNumInputs(),  // numIns
                         getNumOutputs(), // numOuts
                         44100.0,         // sampleRate
                         128);            // blockSize

    editor->update(); // update editor settings

}

// bool GenericProcessor::recordStatus(int chan)
// {

// 	return getEditor()->getRecordStatus(chan);//recordChannels[chan];


// }

// bool GenericProcessor::audioStatus(int chan)
// {

// 	return getEditor()->getAudioStatus(chan);//recordChannels[chan];


// }

// void GenericProcessor::generateDefaultChannelNames(StringArray& names)
// {
// 	names.clear();

// 	for (int i = 0; i < settings.numOutputs; i++)
// 	{
// 		String channelName = "CH";
// 		channelName += (i+1);
// 		names.add(channelName);
// 	}

// }

void GenericProcessor::enableEditor()
{

    GenericEditor* ed = getEditor();

    if (ed != 0)
        ed->startAcquisition();

}

void GenericProcessor::disableEditor()
{

    GenericEditor* ed = getEditor();

    if (ed != nullptr)
        ed->stopAcquisition();
}


int GenericProcessor::checkForEvents(MidiBuffer& midiMessages)
{

    if (midiMessages.getNumEvents() > 0)
    {

        // int m = midiMessages.getNumEvents();
        //std::cout << m << " events received by node " << getNodeId() << std::endl;

        MidiBuffer::Iterator i(midiMessages);
        MidiMessage message(0xf4);

        int samplePosition = 0;
        i.setNextSamplePosition(samplePosition);

        while (i.getNextEvent(message, samplePosition))
        {

            const uint8* dataptr = message.getRawData();

            handleEvent(*dataptr, message, samplePosition);

        }

    }

    return -1;

}

void GenericProcessor::addEvent(MidiBuffer& eventBuffer,
                                uint8 type,
                                int sampleNum,
                                uint8 eventId,
                                uint8 eventChannel,
                                uint8 numBytes,
                                uint8* eventData)
{
    uint8* data = new uint8[4+numBytes];

    data[0] = type;    // event type
    data[1] = nodeId;  // processor ID automatically added
    data[2] = eventId; // event ID
    data[3] = eventChannel; // event channel
    memcpy(data + 4, eventData, numBytes);


    //std::cout << 4 + numBytes << std::endl;

    eventBuffer.addEvent(data, 		// raw data
                         4 + numBytes, // total bytes
                         sampleNum);     // sample index

    //if (type == TTL)
    //	std::cout << "Adding event for channel " << (int) eventChannel << " with ID " << (int) eventId << std::endl;

}

// void GenericProcessor::unpackEvent(int type,
// 								   MidiMessage& event)
// {


// }

void GenericProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& eventBuffer)
{

    int nSamples = getNumSamples(eventBuffer); // removes first value from midimessages

    process(buffer, eventBuffer, nSamples);

    setNumSamples(eventBuffer, nSamples); // adds it back,
    // even if it's unchanged

}


/////// ---- LOADING AND SAVING ---- //////////


void GenericProcessor::saveToXml(XmlElement* parentElement)
{

    parentElement->setAttribute("NodeId", nodeId);

    saveCustomParametersToXml(parentElement);

    // loop through the channels

    for (int i = 0; i < channels.size(); i++)
    {

        saveChannelParametersToXml(parentElement, i);

        // channelName = String(i);
        // channelChildNode = parentElement->createNewChildElement("CHANNEL");
        // channelChildNode->setAttribute("name", channelName);
        // saveParametersToChannelsXml(channelChildNode, i);
    }

    for (int i = 0; i < eventChannels.size(); i++)
    {
        saveChannelParametersToXml(parentElement, i, true);

        // channelName=/**String("EventCh:")+*/String(i);
        // channelChildNode = parentElement->createNewChildElement("EVENTCHANNEL");
        // channelChildNode->setAttribute("name", channelName);
        // saveParametersToChannelsXml(channelChildNode, i);
    }

    // Save editor parameters:
    XmlElement* editorChildNode = parentElement->createNewChildElement("EDITOR");
    getEditor()->saveEditorParameters(editorChildNode);

}

void GenericProcessor::saveCustomParametersToXml(XmlElement* parentElement)
{

}

void GenericProcessor::saveChannelParametersToXml(XmlElement* parentElement, int channelNumber, bool isEventChannel)
{

    if (!isEventChannel)
    {
        XmlElement* channelInfo = parentElement->createNewChildElement("CHANNEL");
        channelInfo->setAttribute("name", String(channelNumber));
        channelInfo->setAttribute("number", channelNumber);

        bool p, r, a;

        getEditor()->getChannelSelectionState(channelNumber, &p, &r, &a);

        XmlElement* selectionState = channelInfo->createNewChildElement("SELECTIONSTATE");
        selectionState->setAttribute("param",p);
        selectionState->setAttribute("record",r);
        selectionState->setAttribute("audio",a);

        saveCustomChannelParametersToXml(channelInfo, channelNumber);

    }
    else
    {

        XmlElement* channelInfo = parentElement->createNewChildElement("EVENTCHANNEL");
        channelInfo->setAttribute("name", String(channelNumber));
        channelInfo->setAttribute("number", channelNumber);

        saveCustomChannelParametersToXml(channelInfo, channelNumber, true);

    }

    // deprecated parameter configuration:
    //std::cout <<"Creating Parameters" << std::endl;
    // int maxsize = parameters.size();
    // String parameterName;
    // String parameterValue;
    // XmlElement* parameterChildNode;

    // // save any attributes that belong to "Parameter" objects
    // for (int n = 0; n < maxsize; n++)
    // {
    //     parameterName = getParameterName(n);

    //     parameterChildNode = channelParent->createNewChildElement("PARAMETER");
    //     parameterChildNode->setAttribute("name", parameterName);

    //     var parameterVar = getParameterVar(n, channelNumber-1);
    //     parameterValue = parameterVar.toString();
    //     parameterChildNode->addTextElement(parameterValue);
    // }

}

void GenericProcessor::saveCustomChannelParametersToXml(XmlElement* channelInfo, int channelNum, bool isEventChannel)
{


}


void GenericProcessor::loadFromXml()
{

    std::cout << "Loading parameters for " << name << std::endl;

    if (!paramsWereLoaded)
    {

        if (parametersAsXml != nullptr)
        {
            // use parametersAsXml to restore state
            loadCustomParametersFromXml();

            forEachXmlChildElement(*parametersAsXml, xmlNode)
            {
                if (xmlNode->hasTagName("CHANNEL"))
                {

                    loadChannelParametersFromXml(xmlNode);
                }
                else if (xmlNode->hasTagName("EVENTCHANNEL"))
                {

                    loadChannelParametersFromXml(xmlNode, true);

                }
                else if (xmlNode->hasTagName("EDITOR"))
                {
                    getEditor()->loadEditorParameters(xmlNode);
                }

            }
        }

    }

    paramsWereLoaded = true;

}

void GenericProcessor::loadChannelParametersFromXml(XmlElement* channelInfo, bool isEventChannel)
{

    int channelNum = channelInfo->getIntAttribute("number");

    if (!isEventChannel)
    {

        forEachXmlChildElement(*channelInfo, subNode)
        {
            if (subNode->hasTagName("SELECTIONSTATE"))
            {

                getEditor()->setChannelSelectionState(channelNum - 1,
                                                      subNode->getBoolAttribute("param"),
                                                      subNode->getBoolAttribute("record"),
                                                      subNode->getBoolAttribute("audio"));
            }
        }
    }

    loadCustomChannelParametersFromXml(channelInfo, isEventChannel);

}



void GenericProcessor::loadCustomParametersFromXml()
{

}

void GenericProcessor::loadCustomChannelParametersFromXml(XmlElement* channelInfo, bool isEventChannel)
{

}

const String GenericProcessor::unusedNameString("xxx-UNUSED-OPEN-EPHYS-xxx");