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

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 "LfpDisplayCanvas.h"

#include <math.h>

using namespace LfpDisplayNodeAlpha;



#pragma mark - LfpDisplayCanvas -

LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) :
     timebase(1.0f), displayGain(1.0f),   timeOffset(0.0f),
    processor(processor_)
{

    nChans = processor->getNumInputs();
    std::cout << "Setting num inputs on LfpDisplayCanvas to " << nChans << std::endl;

    displayBuffer = processor->getDisplayBufferAddress();
    displayBufferSize = displayBuffer->getNumSamples();
    std::cout << "Setting displayBufferSize on LfpDisplayCanvas to " << displayBufferSize << std::endl;

    screenBuffer = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP);
    screenBuffer->clear();

    screenBufferMin = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP);
    screenBufferMin->clear();
    screenBufferMean = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP);
    screenBufferMean->clear();
    screenBufferMax = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP);
    screenBufferMax->clear();

    viewport = new LfpViewport(this);
    lfpDisplay = new LfpDisplay(this, viewport);
    timescale = new LfpTimescale(this, lfpDisplay);
    options = new LfpDisplayOptions(this, timescale, lfpDisplay, processor);

    lfpDisplay->options = options;

    timescale->setTimebase(timebase);

    viewport->setViewedComponent(lfpDisplay, false);
    viewport->setScrollBarsShown(true, false);

    scrollBarThickness = viewport->getScrollBarThickness();

    isChannelEnabled.insertMultiple(0,true,10000); // max 10k channels

    //viewport->getVerticalScrollBar()->addListener(this->scrollBarMoved(viewport->getVerticalScrollBar(), 1.0));

    addAndMakeVisible(viewport);
    addAndMakeVisible(timescale);
    addAndMakeVisible(options);

    lfpDisplay->setNumChannels(nChans);

    resizeSamplesPerPixelBuffer(nChans);

    TopLevelWindow::getTopLevelWindow(0)->addKeyListener(this);

    optionsDrawerIsOpen = false;

}

LfpDisplayCanvas::~LfpDisplayCanvas()
{

    // de-allocate 3d-array samplesPerPixel [nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL];

    //for(int i=0;i<nChans;i++)
    //{
    //    for(int j=0;j<MAX_N_SAMP;j++)
    //    {
    //        free(samplesPerPixel[i][j]);
    //    }
    //    free(samplesPerPixel[i]);
    // }
    // free(samplesPerPixel);

    samplesPerPixel.clear();
    
    TopLevelWindow::getTopLevelWindow(0)->removeKeyListener(this);
}

void LfpDisplayCanvas::resizeSamplesPerPixelBuffer(int numCh)
{
    // allocate samplesPerPixel, behaves like float samplesPerPixel[nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]
    //samplesPerPixel = (float***)malloc(nChans * sizeof(float **));

    // 3D array: dimensions channels x samples x samples per pixel
    samplesPerPixel.clear();
    samplesPerPixel.resize(numCh);

    //for(int i = 0; i < numCh; i++)
    //{
        //std::vector< std::vector<float>> v1;
    //    samplesPerPixel[i].resize(MAX_N_SAMP);
        //samplesPerPixel.push_back(v1);
        //samplesPerPixel[i] = (float**)malloc(MAX_N_SAMP * sizeof(float*));

    //    for(int j = 0; j < MAX_N_SAMP; j++)
    //    {
            //std::vector<float> v2;
            //v2.resize(MAX_N_SAMP_PER_PIXEL);
    //        samplesPerPixel[i][j].resize(MAX_N_SAMP_PER_PIXEL);
    //        //samplesPerPixel[i][j] = (float*)malloc(MAX_N_SAMP_PER_PIXEL*sizeof(float));
    //    }
   //}
}

void LfpDisplayCanvas::toggleOptionsDrawer(bool isOpen)
{
    optionsDrawerIsOpen = isOpen;
    auto viewportPosition = viewport->getViewPositionY();   // remember viewport position
    resized();
    viewport->setViewPosition(0, viewportPosition);         // return viewport position
}

void LfpDisplayCanvas::resized()
{

    timescale->setBounds(leftmargin,0,getWidth()-scrollBarThickness-leftmargin,30);
    viewport->setBounds(0,30,getWidth(),getHeight()-90);

    if (nChans > 0)
    {
        if (lfpDisplay->getSingleChannelState())
            lfpDisplay->setChannelHeight(viewport->getHeight(),false);
        std::cout << "resizing canvas" << std::endl;
        lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size());
    }
    else
    {
        lfpDisplay->setBounds(0, 0, getWidth(), getHeight());
    }

    if (optionsDrawerIsOpen)
        options->setBounds(0, getHeight()-200, getWidth(), 200);
    else
        options->setBounds(0, getHeight()-55, getWidth(), 55);

}

void LfpDisplayCanvas::resizeToChannels(bool respectViewportPosition)
{
    lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size());
    
    // if param is flagged, move the viewport scroll back to same relative position before
    // resize took place
    if (!respectViewportPosition) return;
    
    // get viewport scroll position as ratio against lfpDisplay's dims
    // so that we can set scrollbar back to rough position before resize
    // (else viewport scrolls back to top after resize)
    const double yPositionRatio = viewport->getViewPositionY() / (double)lfpDisplay->getHeight();
    const double xPositionRatio = viewport->getViewPositionX() / (double)lfpDisplay->getWidth();
    
    viewport->setViewPosition(lfpDisplay->getWidth() * xPositionRatio,
                              lfpDisplay->getHeight() * yPositionRatio);
}

void LfpDisplayCanvas::beginAnimation()
{
    std::cout << "Beginning animation." << std::endl;

    displayBufferSize = displayBuffer->getNumSamples();

    for (int i = 0; i < screenBufferIndex.size(); i++)
    {
        screenBufferIndex.set(i,0);
    }

    startCallbacks();
}

void LfpDisplayCanvas::endAnimation()
{
    std::cout << "Ending animation." << std::endl;

    stopCallbacks();
}

void LfpDisplayCanvas::update()
{
    nChans = jmax(processor->getNumInputs(), 0);

    resizeSamplesPerPixelBuffer(nChans);

    sampleRate.clear();
    screenBufferIndex.clear();
    lastScreenBufferIndex.clear();
    displayBufferIndex.clear();
    
    options->setEnabled(nChans != 0);
    // must manually ensure that overlapSelection propagates up to canvas
    channelOverlapFactor = options->selectedOverlapValue.getFloatValue();

    for (int i = 0; i <= nChans; i++) // extra channel for events
    {
		if (processor->getNumInputs() > 0)
		{
			if (i < nChans)
				sampleRate.add(processor->getDataChannel(i)->getSampleRate());
			else
			{
				//Since for now the canvas only supports one event channel, find the first TTL one and use that as sampleRate.
				//This is a bit hackish and should be fixed for proper multi-ttl-channel support
				for (int c = 0; c < processor->getTotalEventChannels(); c++)
				{
					if (processor->getEventChannel(c)->getChannelType() == EventChannel::TTL)
					{
						sampleRate.add(processor->getEventChannel(c)->getSampleRate());
					}
				}
			}
		}
		else
        {
			sampleRate.add(30000);
        }
        
       // std::cout << "Sample rate for ch " << i << " = " << sampleRate[i] << std::endl; 
        displayBufferIndex.add(0);
        screenBufferIndex.add(0);
        lastScreenBufferIndex.add(0);
    }

    if (nChans != lfpDisplay->getNumChannels())
    {
        std::cout << "Setting num inputs on LfpDisplayCanvas to " << nChans << std::endl;

        refreshScreenBuffer();

        lfpDisplay->setNumChannels(nChans); // add an extra channel for events

        // update channel names
        for (int i = 0; i < processor->getNumInputs(); i++)
        {

            String chName = processor->getDataChannel(i)->getName();

            //std::cout << chName << std::endl;

            lfpDisplay->channelInfo[i]->setName(chName);
            lfpDisplay->setEnabledState(isChannelEnabled[i], i);

        }
        
//        if (nChans > 0)
//            lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight());
//        else
        if (nChans == 0) lfpDisplay->setBounds(0, 0, getWidth(), getHeight());
        else {
            lfpDisplay->rebuildDrawableChannelsList();
            lfpDisplay->setBounds(0, 0, getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight());
        }
        
        resized();
    }
    else
    {
        for (int i = 0; i < processor->getNumInputs(); i++)
        {
            lfpDisplay->channels[i]->updateType();
            lfpDisplay->channelInfo[i]->updateType();
        }
        
        if (nChans > 0)
            lfpDisplay->rebuildDrawableChannelsList();
    }
    

}



int LfpDisplayCanvas::getChannelHeight()
{
    //return spreads[spreadSelection->getSelectedId()-1].getIntValue();
    return options->getChannelHeight();
    
}


void LfpDisplayCanvas::setParameter(int param, float val)
{
    // not used for anything, since LfpDisplayCanvas is not a processor
}


void LfpDisplayCanvas::refreshState()
{
    // called when the component's tab becomes visible again

    for (int i = 0; i <= displayBufferIndex.size(); i++) // include event channel
    {

        displayBufferIndex.set(i, processor->getDisplayBufferIndex(i));
        screenBufferIndex.set(i,0);
    }

}

void LfpDisplayCanvas::refreshScreenBuffer()
{

    for (int i = 0; i < screenBufferIndex.size(); i++)
        screenBufferIndex.set(i,0);

    screenBuffer->clear();
    screenBufferMin->clear();
    screenBufferMean->clear();
    screenBufferMax->clear();

}

void LfpDisplayCanvas::updateScreenBuffer()
{

    // copy new samples from the displayBuffer into the screenBuffer
    int maxSamples = lfpDisplay->getWidth() - leftmargin;

	ScopedLock displayLock(*processor->getMutex());

    for (int channel = 0; channel <= nChans; channel++) // pull one extra channel for event display
    {

        if (screenBufferIndex[channel] >= maxSamples) // wrap around if we reached right edge before
            screenBufferIndex.set(channel, 0);

         // hold these values locally for each channel - is this a good idea?
        int sbi = screenBufferIndex[channel];
        int dbi = displayBufferIndex[channel];
        
        lastScreenBufferIndex.set(channel,sbi);

        int index = processor->getDisplayBufferIndex(channel);

        int nSamples =  index - dbi; // N new samples (not pixels) to be added to displayBufferIndex

        if (nSamples < 0) // buffer has reset to 0 -- xxx 2do bug: this shouldnt happen because it makes the range/histogram display not work properly/look off for one pixel
        {
            nSamples = (displayBufferSize - dbi) + index +1;
           //  std::cout << "nsamples 0 " ;
        }

        //if (channel == 15 || channel == 16)
        //     std::cout << channel << " " << sbi << " " << dbi << " " << nSamples << std::endl;


        float ratio = sampleRate[channel] * timebase / float(getWidth() - leftmargin - scrollBarThickness); // samples / pixel
        // this number is crucial: converting from samples to values (in px) for the screen buffer
        int valuesNeeded = (int) float(nSamples) / ratio; // N pixels needed for this update

        if (sbi + valuesNeeded > maxSamples)  // crop number of samples to fit canvas width
        {
            valuesNeeded = maxSamples - sbi;
        }
        float subSampleOffset = 0.0;

        dbi %= displayBufferSize; // make sure we're not overshooting
        int nextPos = (dbi + 1) % displayBufferSize; //  position next to displayBufferIndex in display buffer to copy from

//         if (channel == 0)
//             std::cout << "Channel " 
//                       << channel << " : " 
//                       << sbi << " : " 
//                       << index << " : " 
//                       << dbi << " : " 
//                       << valuesNeeded << " : " 
//                       << ratio 
//                                     << std::endl;
        
        if (valuesNeeded > 0 && valuesNeeded < 1000000)
        {
            for (int i = 0; i < valuesNeeded; i++) // also fill one extra sample for line drawing interpolation to match across draws
            {
                //If paused don't update screen buffers, but update all indexes as needed
                if (!lfpDisplay->isPaused)
                {
                    float gain = 1.0;
                    float alpha = (float) subSampleOffset;
                    float invAlpha = 1.0f - alpha;

                    screenBuffer->clear(channel, sbi, 1);
                    screenBufferMean->clear(channel, sbi, 1);
                    screenBufferMin->clear(channel, sbi, 1);
                    screenBufferMax->clear(channel, sbi, 1);

                    dbi %= displayBufferSize; // just to be sure
                    
                    // update continuous data channels
                    if (channel != nChans)
                    {
                        // interpolate between two samples with invAlpha and alpha
                        screenBuffer->addFrom(channel, // destChannel
                                              sbi, // destStartSample
                                              displayBuffer->getReadPointer(channel, dbi), // source
                                              1, // numSamples
                                              invAlpha*gain); // gain


                        screenBuffer->addFrom(channel, // destChannel
                                              sbi, // destStartSample
                                              displayBuffer->getReadPointer(channel, nextPos), // source
                                              1, // numSamples
                                              alpha*gain); // gain
                    }

                    // same thing again, but this time add the min,mean, and max of all samples in current pixel
                    float sample_min   =  10000000;
                    float sample_max   = -10000000;
                    float sample_mean  =  0;
                    
                    int nextpix = (dbi +(int)ratio +1) % (displayBufferSize+1); //  position to next pixels index
                    
                    if (nextpix <= dbi) { // at the end of the displaybuffer, this can occur and it causes the display to miss one pixel woth of sample - this circumvents that
                    //    std::cout << "np " ;
                        nextpix=dbi;
                    }
                   
                    for (int j = dbi; j < nextpix; j++)
                    {
                        
                        float sample_current = displayBuffer->getSample(channel, j);
                        sample_mean = sample_mean + sample_current;

                        if (sample_min>sample_current)
                        {
                            sample_min=sample_current;
                        }

                        if (sample_max<sample_current)
                        {
                            sample_max=sample_current;
                        }
                       
                    }
                    
                    // update event channel
                    if (channel == nChans)
                    {
                        screenBuffer->setSample(channel, sbi, sample_max);
                    }
                    
                    // similarly, for each pixel on the screen, we want a list of all values so we can draw a histogram later
                    // for simplicity, we'll just do this as 2d array, samplesPerPixel[px][samples]
                    // with an additional array sampleCountPerPixel[px] that holds the N samples per pixel
                    if (channel < nChans) // we're looping over one 'extra' channel for events above, so make sure not to loop over that one here
                        {
                            int c = 0;
                            for (int j = dbi; j < nextpix && c < MAX_N_SAMP_PER_PIXEL; j++)
                            {
                                float sample_current = displayBuffer->getSample(channel, j);
                                samplesPerPixel[channel][sbi][c]=sample_current;
                                c++;
                            }
                            if (c>0){
                                sampleCountPerPixel[sbi]=c-1; // save count of samples for this pixel
                            }else{
                                sampleCountPerPixel[sbi]=0;
                            }
                            sample_mean = sample_mean/c;
                            screenBufferMean->addSample(channel, sbi, sample_mean*gain);
                            
                            screenBufferMin->addSample(channel, sbi, sample_min*gain);
                            screenBufferMax->addSample(channel, sbi, sample_max*gain);
                        }
                    sbi++;
                }
            
                subSampleOffset += ratio;
                
                while (subSampleOffset >= 1.0)
                {
                    if (++dbi > displayBufferSize)
                        dbi = 0;
                    
                    nextPos = (dbi + 1) % displayBufferSize;
                    subSampleOffset -= 1.0;
                }
                
            }
            
            // update values after we're done
            screenBufferIndex.set(channel, sbi);
            displayBufferIndex.set(channel, dbi);
        }

    }

}

const float LfpDisplayCanvas::getXCoord(int chan, int samp)
{
    return samp;
}

int LfpDisplayCanvas::getNumChannels()
{
    return nChans;
}

int LfpDisplayCanvas::getNumChannelsVisible()
{
    return lfpDisplay->drawableChannels.size();
}

int LfpDisplayCanvas::getChannelSubprocessorIdx(int channel)
{
    return processor->getDataChannel(channel)->getSubProcessorIdx();
}

const float LfpDisplayCanvas::getYCoord(int chan, int samp)
{
    return *screenBuffer->getReadPointer(chan, samp);
}

const float LfpDisplayCanvas::getYCoordMean(int chan, int samp)
{
    return *screenBufferMean->getReadPointer(chan, samp);
}
const float LfpDisplayCanvas::getYCoordMin(int chan, int samp)
{
    return *screenBufferMin->getReadPointer(chan, samp);
}
const float LfpDisplayCanvas::getYCoordMax(int chan, int samp)
{
    return *screenBufferMax->getReadPointer(chan, samp);
}

std::array<float, MAX_N_SAMP_PER_PIXEL> LfpDisplayCanvas::getSamplesPerPixel(int chan, int px)
{
    return samplesPerPixel[chan][px];
}
const int LfpDisplayCanvas::getSampleCountPerPixel(int px)
{
    return sampleCountPerPixel[px];
}

float LfpDisplayCanvas::getMean(int chan)
{
    float total = 0.0f;
    float numPts = 0;

    float sample = 0.0f;
    for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10)
    {
        sample = *screenBuffer->getReadPointer(chan, samp);
        total += sample;
        numPts++;
    }

    //std::cout << sample << std::endl;

    return total / numPts;
}

float LfpDisplayCanvas::getStd(int chan)
{
    float std = 0.0f;

    float mean = getMean(chan);
    float numPts = 1;

    for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10)
    {
        std += pow((*screenBuffer->getReadPointer(chan, samp) - mean),2);
        numPts++;
    }

    return sqrt(std / numPts);

}

bool LfpDisplayCanvas::getInputInvertedState()
{
    return options->getInputInvertedState(); //invertInputButton->getToggleState();
}

bool LfpDisplayCanvas::getDisplaySpikeRasterizerState()
{
    return options->getDisplaySpikeRasterizerState();
}

bool LfpDisplayCanvas::getDrawMethodState()
{
    
    return options->getDrawMethodState(); //drawMethodButton->getToggleState();
}

int LfpDisplayCanvas::getChannelSampleRate(int channel)
{
    return sampleRate[channel];
}

void LfpDisplayCanvas::setDrawableSampleRate(float samplerate)
{
//    std::cout << "setting the drawable sample rate in the canvas" << std::endl;
    lfpDisplay->setDisplayedSampleRate(samplerate);
}

void LfpDisplayCanvas::setDrawableSubprocessor(int idx)
{
    lfpDisplay->setDisplayedSubprocessor(idx);
}

void LfpDisplayCanvas::redraw()
{
    fullredraw=true;
    repaint();
    refresh();
}


void LfpDisplayCanvas::paint(Graphics& g)
{
    
    //std::cout << "Painting" << std::endl;

    //g.setColour(Colour(0,0,0)); // for high-precision per-pixel density display, make background black for better visibility
    g.setColour(lfpDisplay->backgroundColour); //background color
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setGradientFill(ColourGradient(Colour(50,50,50),0,0,
                                     Colour(25,25,25),0,30,
                                     false));

    g.fillRect(0, 0, getWidth()-scrollBarThickness, 30);

    g.setColour(Colours::black);

    g.drawLine(0,30,getWidth()-scrollBarThickness,30);

    g.setColour(Colour(25,25,60)); // timing grid color

    int w = getWidth()-scrollBarThickness-leftmargin;

    for (int i = 0; i < 10; i++)
    {
        if (i == 5 || i == 0)
            g.drawLine(w/10*i+leftmargin,timescale->getHeight(),w/10*i+leftmargin,getHeight()-60-timescale->getHeight(),3.0f);
        else
            g.drawLine(w/10*i+leftmargin,timescale->getHeight(),w/10*i+leftmargin,getHeight()-60-timescale->getHeight(),1.0f);
    }

    g.drawLine(0,getHeight()-60,getWidth(),getHeight()-60,3.0f);


}

void LfpDisplayCanvas::refresh()
{

    updateScreenBuffer();

    lfpDisplay->refresh(); // redraws only the new part of the screen buffer

}

bool LfpDisplayCanvas::keyPressed(const KeyPress& key)
{
    if (key.getKeyCode() == key.spaceKey)
    {
        options->togglePauseButton();
        
        return true;
    }

    return false;
}

bool LfpDisplayCanvas::keyPressed(const KeyPress& key, Component* orig)
{
    if (getTopLevelComponent() == orig && isVisible())
    {
        return keyPressed(key);
    }
    return false;
}

void LfpDisplayCanvas::saveVisualizerParameters(XmlElement* xml)
{

    options->saveParameters(xml);
}


void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml)
{
    options->loadParameters(xml);

}



#pragma mark - ShowHideOptionsButton -
// =============================================================


ShowHideOptionsButton::ShowHideOptionsButton(LfpDisplayOptions* options) : Button("Button")
{
    setClickingTogglesState(true);
}
ShowHideOptionsButton::~ShowHideOptionsButton()
{

}

void ShowHideOptionsButton::paintButton(Graphics& g, bool, bool) 
{   
    g.setColour(Colours::white);

    Path p;

    float h = getHeight();
    float w = getWidth();

    if (getToggleState())
    {
        p.addTriangle(0.5f*w, 0.2f*h,
                      0.2f*w, 0.8f*h,
                      0.8f*w, 0.8f*h);
    }
    else
    {
        p.addTriangle(0.8f*w, 0.8f*h,
                      0.2f*w, 0.5f*h,
                      0.8f*w, 0.2f*h);
    }

    PathStrokeType pst = PathStrokeType(1.0f, PathStrokeType::curved, PathStrokeType::rounded);

    g.strokePath(p, pst);
}



#pragma mark - LfpDisplayOptions -
// -------------------------------------------------------------

LfpDisplayOptions::LfpDisplayOptions(LfpDisplayCanvas* canvas_, LfpTimescale* timescale_, 
                                     LfpDisplay* lfpDisplay_, LfpDisplayNode* processor_)
    : canvas(canvas_),
      lfpDisplay(lfpDisplay_),
      timescale(timescale_),
      processor(processor_),
      selectedChannelType(DataChannel::HEADSTAGE_CHANNEL),
      labelFont("Default", 13.0f, Font::plain),
      labelColour(100, 100, 100)
{
    // draw the colour scheme options
    // TODO: (kelly) this might be better as a modal window
    colourSchemeOptionLabel = new Label("colorSchemeOptionLabel", "Color Scheme");
    colourSchemeOptionLabel->setFont(labelFont);
    colourSchemeOptionLabel->setColour(Label::textColourId, labelColour);
    addAndMakeVisible(colourSchemeOptionLabel);
    
    StringArray colourSchemeNames = lfpDisplay->getColourSchemeNameArray();
    colourSchemeOptionSelection = new ComboBox("colorSchemeOptionSelection");
    colourSchemeOptionSelection->addItemList(colourSchemeNames, 1);
    colourSchemeOptionSelection->setEditableText(false);
    colourSchemeOptionSelection->addListener(this);
    colourSchemeOptionSelection->setSelectedId(1, dontSendNotification);
    addAndMakeVisible(colourSchemeOptionSelection);
    
    if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements())
        addAndMakeVisible(lfpDisplay->getColourSchemePtr());
    
 //Ranges for neural data
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("25");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("50");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("100");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("250");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("400");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("500");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("750");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("1000");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("2000");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("5000");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("10000");
	voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("15000");
	selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] = 8;
	rangeGain[DataChannel::HEADSTAGE_CHANNEL] = 1; //uV
	rangeSteps[DataChannel::HEADSTAGE_CHANNEL] = 10;
    rangeUnits.add("uV");
    typeNames.add("DATA");

    UtilityButton* tbut;
    tbut = new UtilityButton("DATA",Font("Small Text", 9, Font::plain));
    tbut->setEnabledState(true);
    tbut->setCorners(false,false,false,false);
    tbut->addListener(this);
    tbut->setClickingTogglesState(true);
    tbut->setRadioGroupId(100,dontSendNotification);
    tbut->setToggleState(true,dontSendNotification);
    addAndMakeVisible(tbut);
    typeButtons.add(tbut);
    
    //Ranges for AUX/accelerometer data
	voltageRanges[DataChannel::AUX_CHANNEL].add("25");
	voltageRanges[DataChannel::AUX_CHANNEL].add("50");
	voltageRanges[DataChannel::AUX_CHANNEL].add("100");
	voltageRanges[DataChannel::AUX_CHANNEL].add("250");
	voltageRanges[DataChannel::AUX_CHANNEL].add("400");
	voltageRanges[DataChannel::AUX_CHANNEL].add("500");
	voltageRanges[DataChannel::AUX_CHANNEL].add("750");
	voltageRanges[DataChannel::AUX_CHANNEL].add("1000");
	voltageRanges[DataChannel::AUX_CHANNEL].add("2000");
    //voltageRanges[DataChannel::AUX_CHANNEL].add("5000");
	selectedVoltageRange[DataChannel::AUX_CHANNEL] = 9;
	rangeGain[DataChannel::AUX_CHANNEL] = 0.001; //mV
	rangeSteps[DataChannel::AUX_CHANNEL] = 10;
    rangeUnits.add("mV");
    typeNames.add("AUX");
    
    tbut = new UtilityButton("AUX",Font("Small Text", 9, Font::plain));
    tbut->setEnabledState(true);
    tbut->setCorners(false,false,false,false);
    tbut->addListener(this);
    tbut->setClickingTogglesState(true);
    tbut->setRadioGroupId(100,dontSendNotification);
    tbut->setToggleState(false,dontSendNotification);
    addAndMakeVisible(tbut);
    typeButtons.add(tbut);

    //Ranges for ADC data
     voltageRanges[DataChannel::ADC_CHANNEL].add("0.01");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.05");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.1");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("0.5");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("1.0");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("2.0");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("5.0");
	 voltageRanges[DataChannel::ADC_CHANNEL].add("10.0");
	 selectedVoltageRange[DataChannel::ADC_CHANNEL] = 8;
	 rangeGain[DataChannel::ADC_CHANNEL] = 1; //V
	 rangeSteps[DataChannel::ADC_CHANNEL] = 0.1; //in V
    rangeUnits.add("V");
    typeNames.add("ADC");

    tbut = new UtilityButton("ADC",Font("Small Text", 9, Font::plain));
    tbut->setEnabledState(true);
    tbut->setCorners(false,false,false,false);
    tbut->addListener(this);
    tbut->setClickingTogglesState(true);
    tbut->setRadioGroupId(100,dontSendNotification);
    tbut->setToggleState(false,dontSendNotification);
    addAndMakeVisible(tbut);
    typeButtons.add(tbut);

	selectedVoltageRangeValues[DataChannel::HEADSTAGE_CHANNEL] = voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1];
	selectedVoltageRangeValues[DataChannel::AUX_CHANNEL] = voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1];
	selectedVoltageRangeValues[DataChannel::ADC_CHANNEL] = voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1];
    
    
    
    // init channel display skipping options
    channelDisplaySkipOptions.add("All");
    channelDisplaySkipOptions.add("2");
    channelDisplaySkipOptions.add("4");
    channelDisplaySkipOptions.add("8");
    channelDisplaySkipOptions.add("16");
    channelDisplaySkipOptions.add("32");
    channelDisplaySkipOptions.add("64");
    selectedChannelDisplaySkip = 1;
    selectedChannelDisplaySkipValue = channelDisplaySkipOptions[selectedChannelDisplaySkip - 1];
    
    channelDisplaySkipSelection = new ComboBox("Channel Skip");
    channelDisplaySkipSelection->addItemList(channelDisplaySkipOptions, 1);
    channelDisplaySkipSelection->setSelectedId(selectedChannelDisplaySkip, sendNotification);
    channelDisplaySkipSelection->setEditableText(false);
    channelDisplaySkipSelection->addListener(this);
    addAndMakeVisible(channelDisplaySkipSelection);
    
    channelDisplaySkipLabel = new Label("Channel Display Skip", "Ch. Skip");
    channelDisplaySkipLabel->setFont(labelFont);
    channelDisplaySkipLabel->setColour(Label::textColourId, labelColour);
    addAndMakeVisible(channelDisplaySkipLabel);
    
    
    
    // init spike raster options
    spikeRasterSelectionOptions = {"Off", "-50", "-100", "-150", "-200", "-300", "-400", "-500"};
    selectedSpikeRasterThreshold = 1;
    selectedSpikeRasterThresholdValue = spikeRasterSelectionOptions[selectedSpikeRasterThreshold - 1];
    
    spikeRasterSelection = new ComboBox("spikeRasterSelection");
    spikeRasterSelection->addItemList(spikeRasterSelectionOptions, 1);
    spikeRasterSelection->setSelectedId(selectedSpikeRasterThreshold, dontSendNotification);
    spikeRasterSelection->setEditableText(true);
    spikeRasterSelection->addListener(this);
    addAndMakeVisible(spikeRasterSelection);
    
    spikeRasterLabel = new Label("spikeRasterLabel", "Spike Raster Thresh.");
    spikeRasterLabel->setFont(labelFont);
    spikeRasterLabel->setColour(Label::textColourId, labelColour);
    addAndMakeVisible(spikeRasterLabel);
    
    
    
    // init median offset plotting
    medianOffsetPlottingLabel = new Label("Median Offset Correction", "Median Offset Correction");
    medianOffsetPlottingLabel->setFont(labelFont);
    medianOffsetPlottingLabel->setColour(Label::textColourId, labelColour);
    addAndMakeVisible(medianOffsetPlottingLabel);
    
    medianOffsetPlottingButton = new UtilityButton("0", labelFont);
    medianOffsetPlottingButton->setRadius(5.0f);
    medianOffsetPlottingButton->setEnabledState(true);
    medianOffsetPlottingButton->setCorners(true, true, true, true);
    medianOffsetPlottingButton->addListener(this);
    medianOffsetPlottingButton->setClickingTogglesState(true);
    medianOffsetPlottingButton->setToggleState(false, sendNotification);
    addAndMakeVisible(medianOffsetPlottingButton);

    
    
    // init show/hide options button
    showHideOptionsButton = new ShowHideOptionsButton(this);
    showHideOptionsButton->addListener(this);
    addAndMakeVisible(showHideOptionsButton);

    // init timebases options
    timebases.add("0.25");
    timebases.add("0.5");
    timebases.add("1.0");
    timebases.add("2.0");
    timebases.add("3.0");
    timebases.add("4.0");
    timebases.add("5.0");
    timebases.add("10.0");
    timebases.add("20.0");
    selectedTimebase = 4;
    selectedTimebaseValue = timebases[selectedTimebase-1];

    spreads.add("10");
    spreads.add("20");
    spreads.add("30");
    spreads.add("40");
    spreads.add("50");
    spreads.add("60");
    spreads.add("70");
    spreads.add("80");
    spreads.add("90");
    spreads.add("100");
    selectedSpread = 5;
    selectedSpreadValue = spreads[selectedSpread-1];


    overlaps.add("0.5");
    overlaps.add("0.75");
    overlaps.add("1");
    overlaps.add("2");
    overlaps.add("3");
    overlaps.add("4");
    overlaps.add("5");
    selectedOverlap = 4;
    selectedOverlapValue = overlaps[selectedOverlap-1];

    saturationThresholds.add("0.5");
    saturationThresholds.add("100");
    saturationThresholds.add("1000");
    saturationThresholds.add("5000");
    saturationThresholds.add("6389");
    
    selectedSaturation = 5;
    selectedSaturationValue = saturationThresholds[selectedSaturation-1];
    
    
    colorGroupings.add("1");
    colorGroupings.add("2");
    colorGroupings.add("4");
    colorGroupings.add("8");
    colorGroupings.add("16");


    rangeSelection = new ComboBox("Voltage range");
	rangeSelection->addItemList(voltageRanges[DataChannel::HEADSTAGE_CHANNEL], 1);
	rangeSelection->setSelectedId(selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL], sendNotification);
    rangeSelection->setEditableText(true);
    rangeSelection->addListener(this);
    addAndMakeVisible(rangeSelection);


    timebaseSelection = new ComboBox("Timebase");
    timebaseSelection->addItemList(timebases, 1);
    timebaseSelection->setSelectedId(selectedTimebase, sendNotification);
    timebaseSelection->setEditableText(true);
    timebaseSelection->addListener(this);
    addAndMakeVisible(timebaseSelection);


    spreadSelection = new ComboBox("Spread");
    spreadSelection->addItemList(spreads, 1);
    spreadSelection->setSelectedId(selectedSpread,sendNotification);
    spreadSelection->addListener(this);
    spreadSelection->setEditableText(true);
    addAndMakeVisible(spreadSelection);

    overlapSelection = new ComboBox("Overlap");
    overlapSelection->addItemList(overlaps, 1);
    overlapSelection->setSelectedId(selectedOverlap,sendNotification);
    overlapSelection->addListener(this);
    overlapSelection->setEditableText(true);
    addAndMakeVisible(overlapSelection);
    
    saturationWarningSelection = new ComboBox("Sat.Warn");
    saturationWarningSelection->addItemList(saturationThresholds, 1);
    saturationWarningSelection->setSelectedId(selectedSaturation,sendNotification);
    saturationWarningSelection->addListener(this);
    saturationWarningSelection->setEditableText(true);
    addAndMakeVisible(saturationWarningSelection);
    
    
    colorGroupingSelection = new ComboBox("Color Grouping");
    colorGroupingSelection->addItemList(colorGroupings, 1);
    colorGroupingSelection->setSelectedId(1,sendNotification);
    colorGroupingSelection->addListener(this);
    addAndMakeVisible(colorGroupingSelection);

    invertInputButton = new UtilityButton("Invert", Font("Small Text", 13, Font::plain));
    invertInputButton->setRadius(5.0f);
    invertInputButton->setEnabledState(true);
    invertInputButton->setCorners(true, true, true, true);
    invertInputButton->addListener(this);
    invertInputButton->setClickingTogglesState(true);
    invertInputButton->setToggleState(false, sendNotification);
    addAndMakeVisible(invertInputButton);
    
    // toggle button to reverse the order of channels
    reverseChannelsDisplayButton = new UtilityButton("0", labelFont);
    reverseChannelsDisplayButton->setRadius(5.0f);
    reverseChannelsDisplayButton->setEnabledState(true);
    reverseChannelsDisplayButton->setCorners(true, true, true, true);
    reverseChannelsDisplayButton->addListener(this);
    reverseChannelsDisplayButton->setClickingTogglesState(true);
    reverseChannelsDisplayButton->setToggleState(lfpDisplay->getChannelsReversed(), sendNotification);
    addAndMakeVisible(reverseChannelsDisplayButton);
    
    reverseChannelsDisplayLabel = new Label("Rev. Channels", "Rev. Channels");
    reverseChannelsDisplayLabel->setFont(labelFont);
    reverseChannelsDisplayLabel->setColour(Label::textColourId, labelColour);
    addAndMakeVisible(reverseChannelsDisplayLabel);
    

    //button for controlling drawing algorithm - old line-style or new per-pixel style
    drawMethodButton = new UtilityButton("DrawMethod", Font("Small Text", 13, Font::plain));
    drawMethodButton->setRadius(5.0f);
    drawMethodButton->setEnabledState(true);
    drawMethodButton->setCorners(true, true, true, true);
    drawMethodButton->addListener(this);
    drawMethodButton->setClickingTogglesState(true);
    drawMethodButton->setToggleState(false, sendNotification);
    addAndMakeVisible(drawMethodButton);
    
    // two sliders for the two histogram components of the supersampled plotting mode
    // todo: rename these
    brightnessSliderA = new Slider();
    brightnessSliderA->setRange (0, 1);
    brightnessSliderA->setTextBoxStyle(Slider::NoTextBox, false, 50,30);
    brightnessSliderA->addListener(this);
    addAndMakeVisible (brightnessSliderA);
    
    brightnessSliderB = new Slider;
    brightnessSliderB->setRange (0, 1);
    brightnessSliderB->setTextBoxStyle(Slider::NoTextBox, false, 50,30);
    brightnessSliderB->addListener(this);
    addAndMakeVisible (brightnessSliderB);
    
    sliderALabel = new Label("Brightness","Brightness");
    sliderALabel->setFont(Font("Small Text", 13, Font::plain));
    sliderALabel->setColour(Label::textColourId,Colour(150,150,150));
    addAndMakeVisible(sliderALabel);
    
    sliderBLabel = new Label("Min. brightness","Min. brightness");
    sliderBLabel->setFont(Font("Small Text", 13, Font::plain));
    sliderBLabel->setColour(Label::textColourId,Colour(150,150,150));
    addAndMakeVisible(sliderBLabel);
    
    
    //ScopedPointer<UtilityButton> drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display
    drawClipWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain));
    drawClipWarningButton->setRadius(5.0f);
    drawClipWarningButton->setEnabledState(true);
    drawClipWarningButton->setCorners(true, true, true, true);
    drawClipWarningButton->addListener(this);
    drawClipWarningButton->setClickingTogglesState(true);
    drawClipWarningButton->setToggleState(false, sendNotification);
    addAndMakeVisible(drawClipWarningButton);
    
    
    //ScopedPointer<UtilityButton> drawSaturateWarningButton; // optionally raise hell if the actual data is saturating
    drawSaturateWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain));
    drawSaturateWarningButton->setRadius(5.0f);
    drawSaturateWarningButton->setEnabledState(true);
    drawSaturateWarningButton->setCorners(true, true, true, true);
    drawSaturateWarningButton->addListener(this);
    drawSaturateWarningButton->setClickingTogglesState(true);
    drawSaturateWarningButton->setToggleState(false, sendNotification);
    addAndMakeVisible(drawSaturateWarningButton);
    

    //button for pausing the display - works by skipping buffer updates. This way scrolling etc still works
    pauseButton = new UtilityButton("Pause", Font("Small Text", 13, Font::plain));
    pauseButton->setRadius(5.0f);
    pauseButton->setEnabledState(true);
    pauseButton->setCorners(true, true, true, true);
    pauseButton->addListener(this);
    pauseButton->setClickingTogglesState(true);
    pauseButton->setToggleState(false, sendNotification);
    addAndMakeVisible(pauseButton);

    // add event display-specific controls (currently just an enable/disable button)
    for (int i = 0; i < 8; i++)
    {

        EventDisplayInterface* eventOptions = new EventDisplayInterface(lfpDisplay, canvas, i);
        eventDisplayInterfaces.add(eventOptions);
        addAndMakeVisible(eventOptions);
        eventOptions->setBounds(500+(floor(i/2)*20), getHeight()-20-(i%2)*20, 40, 20);

        lfpDisplay->setEventDisplayState(i,true);

    }

	lfpDisplay->setRange(voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::HEADSTAGE_CHANNEL]
		, DataChannel::HEADSTAGE_CHANNEL);
	lfpDisplay->setRange(voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::ADC_CHANNEL]
		, DataChannel::ADC_CHANNEL);
	lfpDisplay->setRange(voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::AUX_CHANNEL]
		, DataChannel::AUX_CHANNEL);
    
}

LfpDisplayOptions::~LfpDisplayOptions()
{

}

void LfpDisplayOptions::resized()
{
    rangeSelection->setBounds(5,getHeight()-30,80,25);
    timebaseSelection->setBounds(175,getHeight()-30,60,25);
    
    spreadSelection->setBounds(5,getHeight()-90,60,25);
    
    overlapSelection->setBounds(100,getHeight()-90,60,25);

    drawClipWarningButton->setBounds(175,getHeight()-89,20,20);
    drawSaturateWarningButton->setBounds(325, getHeight()-89, 20, 20);
    
    colorGroupingSelection->setBounds(400,getHeight()-90,60,25);

    invertInputButton->setBounds(35,getHeight()-190,100,22);
    drawMethodButton->setBounds(35,getHeight()-160,100,22);

    pauseButton->setBounds(450,getHeight()-50,50,44);
    
    
    // Reverse Channels Display
    reverseChannelsDisplayButton->setBounds(pauseButton->getRight() + 5,
                                 getHeight() - 50,
                                 20,
                                 20);
    reverseChannelsDisplayLabel->setBounds(reverseChannelsDisplayButton->getRight(),
                                           reverseChannelsDisplayButton->getY(),
                                           120,
                                           22);
    
    // Channel Display Skip Selector
    channelDisplaySkipSelection->setBounds(reverseChannelsDisplayButton->getX(),
                                           reverseChannelsDisplayButton->getBottom(),
                                           60,
                                           25);
    channelDisplaySkipLabel->setBounds(channelDisplaySkipSelection->getRight(),
                                       channelDisplaySkipSelection->getY() + 2,
                                       100,
                                       22);
    
    // Median Offset Plotting Button
    medianOffsetPlottingButton->setBounds(reverseChannelsDisplayLabel->getRight() + 5,
                                          reverseChannelsDisplayButton->getY(),
                                          20,
                                          20);
    medianOffsetPlottingLabel->setBounds(medianOffsetPlottingButton->getRight(),
                                         medianOffsetPlottingButton->getY(),
                                         150,
                                         22);
    
    
    // Spike raster plotting button
    spikeRasterSelection->setBounds(medianOffsetPlottingButton->getX(),
                                    medianOffsetPlottingButton->getBottom(),
                                    60,
                                    25);
    spikeRasterLabel->setBounds(spikeRasterSelection->getRight(),
                                spikeRasterSelection->getY(),
                                120,
                                22);
    
    
    // Saturation Warning Selection
    saturationWarningSelection->setBounds(250, getHeight()-90, 60, 25);
    
    
    for (int i = 0; i < 8; i++)
    {
        eventDisplayInterfaces[i]->setBounds(300+(floor(i/2)*20), getHeight()-40+(i%2)*20, 40, 20); // arrange event channel buttons in two rows
        eventDisplayInterfaces[i]->repaint();
    }
    
    brightnessSliderA->setBounds(170,getHeight()-190,100,22);
    sliderALabel->setBounds(270, getHeight()-190, 180, 22);
    brightnessSliderA->setValue(0.9); //set default value
    
    brightnessSliderB->setBounds(170,getHeight()-160,100,22);
    sliderBLabel->setBounds(270, getHeight()-160, 180, 22);
    brightnessSliderB->setValue(0.1); //set default value

    showHideOptionsButton->setBounds (getWidth() - 28, getHeight() - 28, 20, 20);
    
    int bh = 25/typeButtons.size();
    for (int i = 0; i < typeButtons.size(); i++)
    {
        typeButtons[i]->setBounds(95,getHeight()-30+i*bh,50,bh);
    }
    
    
    colourSchemeOptionLabel->setBounds(medianOffsetPlottingButton->getX(),
                                       getHeight()-190,
                                       100,
                                       22);
    colourSchemeOptionSelection->setBounds(colourSchemeOptionLabel->getRight(),
                                           colourSchemeOptionLabel->getY(),
                                           80,
                                           25);
    
    // set the size of the active colour scheme's options, if it has configurable options
    if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements())
    {
        lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(),
                                                    colourSchemeOptionLabel->getBottom(),
                                                    200,
                                                    110);
    }
}

void LfpDisplayOptions::paint(Graphics& g)
{
    int row1 = 55;
    int row2 = 110;

    g.fillAll(Colours::black);
    g.setFont(Font("Default", 16, Font::plain));

    g.setColour(Colour(100,100,100));

    g.drawText("Range("+ rangeUnits[selectedChannelType] +")",5,getHeight()-row1,300,20,Justification::left, false);
    g.drawText("Timebase(s)",160,getHeight()-row1,300,20,Justification::left, false);
    g.drawText("Size(px)",5,getHeight()-row2,300,20,Justification::left, false);
    g.drawText("Clip",100,getHeight()-row2,300,20,Justification::left, false);
    g.drawText("Warn",168,getHeight()-row2,300,20,Justification::left, false);
    
    g.drawText("Sat. Warning",225,getHeight()-row2,300,20,Justification::left, false);

    g.drawText("Color grouping",365,getHeight()-row2,300,20,Justification::left, false);

    g.drawText("Event disp.",300,getHeight()-row1,300,20,Justification::left, false);

    if(canvas->drawClipWarning)
    {
        g.setColour(Colours::white);
        g.fillRoundedRectangle(173,getHeight()-90-1,24,24,6.0f);
    }
    
    if(canvas->drawSaturationWarning)
    {
        g.setColour(Colours::red);
        g.fillRoundedRectangle(323,getHeight()-90-1,24,24,6.0f);
    }
    
}

int LfpDisplayOptions::getChannelHeight()
{
    return (int)spreadSelection->getText().getIntValue();
}


bool LfpDisplayOptions::getDrawMethodState()
{
    
    return drawMethodButton->getToggleState();
}

bool LfpDisplayOptions::getInputInvertedState()
{
    return invertInputButton->getToggleState();
}

bool LfpDisplayOptions::getDisplaySpikeRasterizerState()
{
//    return spikeRasterButton->getToggleState();
    return false;
}

void LfpDisplayOptions::setDisplaySpikeRasterizerState(bool isEnabled)
{
//    spikeRasterButton->setToggleState(isEnabled, dontSendNotification);
    
//    if (isEnabled) medianOffsetPlottingButton->setToggleState(true, sendNotification);
}

void LfpDisplayOptions::setRangeSelection(float range, bool canvasMustUpdate)
{
    if (canvasMustUpdate)
    {
        rangeSelection->setText(String(range/rangeGain[selectedChannelType]), sendNotification); 
    }
    else
    {
        rangeSelection->setText(String(range/rangeGain[selectedChannelType]),dontSendNotification);
        
        selectedVoltageRange[selectedChannelType] = rangeSelection->getSelectedId();
        selectedVoltageRangeValues[selectedChannelType] = rangeSelection->getText();

        canvas->repaint();
        canvas->refresh();
    }

}

void LfpDisplayOptions::setSpreadSelection(int spread, bool canvasMustUpdate, bool deferDisplayRefresh)
{
    
    if (canvasMustUpdate)
    {
        spreadSelection->setText(String(spread),sendNotification);
    }
    else
    {
        spreadSelection->setText(String(spread),dontSendNotification);
        selectedSpread = spreadSelection->getSelectedId();
        selectedSpreadValue = spreadSelection->getText();

        if (!deferDisplayRefresh)
        {
            canvas->repaint();
            canvas->refresh();
        }
    }
}

void LfpDisplayOptions::togglePauseButton(bool sendUpdate)
{
    pauseButton->setToggleState(!pauseButton->getToggleState(), sendUpdate ? sendNotification : dontSendNotification);
}

void LfpDisplayOptions::buttonClicked(Button* b)
{
    if (b == invertInputButton)
    {
        lfpDisplay->setInputInverted(b->getToggleState());
        return;
    }
    if (b == reverseChannelsDisplayButton)
    {
        lfpDisplay->setChannelsReversed(b->getToggleState());
        return;
    }
    if (b == medianOffsetPlottingButton)
    {
        if (lfpDisplay->getSpikeRasterPlotting())
        {
            medianOffsetPlottingButton->setToggleState(true, dontSendNotification);
        }
        else
        {
            lfpDisplay->setMedianOffsetPlotting(b->getToggleState());
        }
        return;
    }
    if (b == drawMethodButton)
    {
        lfpDisplay->setDrawMethod(b->getToggleState()); // this should be done the same way as drawClipWarning - or the other way around.
        
        return;
    }
    if (b == drawClipWarningButton)
    {
        canvas->drawClipWarning = b->getToggleState();
        canvas->redraw();
        return;
    }
    if (b == drawSaturateWarningButton)
    {
        canvas->drawSaturationWarning = b->getToggleState();
        canvas->redraw();
        return;
    }
    
    if (b == pauseButton)
    {
        lfpDisplay->isPaused = b->getToggleState();
        return;
    }

    if (b == showHideOptionsButton)
    {
        canvas->toggleOptionsDrawer(b->getToggleState());
    }

    int idx = typeButtons.indexOf((UtilityButton*)b);

    if ((idx >= 0) && (b->getToggleState()))
    {
        for (int i = 0; i < processor->getNumInputs(); i++)
        {
            if (lfpDisplay->channels[i]->getSelected())
            {
                lfpDisplay->channels[i]->deselect();
                lfpDisplay->channels[i]->repaint();
            }
        } 

        setSelectedType((DataChannel::DataChannelTypes) idx, false);
    }

}

void LfpDisplayOptions::setTimebaseAndSelectionText(float timebase)
{
    canvas->timebase = timebase;
    
    if (canvas->timebase) // if timebase != 0
    {
        if (canvas->timebase < timebases[0].getFloatValue())
        {
            timebaseSelection->setSelectedId(1, dontSendNotification);
            canvas->timebase = timebases[0].getFloatValue();
        }
        else if (canvas->timebase > timebases[timebases.size()-1].getFloatValue())
        {
            timebaseSelection->setSelectedId(timebases.size(), dontSendNotification);
            canvas->timebase = timebases[timebases.size()-1].getFloatValue();
        }
        else{
            timebaseSelection->setText(String(canvas->timebase, 1), dontSendNotification);
        }
    }
    else
    {
        if (selectedSpread == 0)
        {
            timebaseSelection->setText(selectedTimebaseValue, dontSendNotification);
            canvas->timebase = selectedTimebaseValue.getFloatValue();
        }
        else
        {
            timebaseSelection->setSelectedId(selectedTimebase,dontSendNotification);
            canvas->timebase = timebases[selectedTimebase-1].getFloatValue();
        }
        
    }
}


void LfpDisplayOptions::comboBoxChanged(ComboBox* cb)
{
    if (canvas->getNumChannels() == 0) return;
    
    if (cb == channelDisplaySkipSelection)
    {
        const int skipAmt = pow(2, cb->getSelectedId() - 1);
        lfpDisplay->setChannelDisplaySkipAmount(skipAmt);
    }
    else if (cb == spikeRasterSelection)
    {
        // if custom value
        if (cb->getSelectedId() == 0)
        {
            auto val = fabsf(cb->getText().getFloatValue());
            
            if (val == 0) // if value is zero, just disable plotting and set text to "Off"
            {
                cb->setSelectedItemIndex(0, dontSendNotification);
                lfpDisplay->setSpikeRasterPlotting(false);
                return;
            }
            
            if (val > 500)
            {
                val = 500;
            }
            
            val *= -1;
            
            spikeRasterSelection->setText(String(val), dontSendNotification);
            lfpDisplay->setSpikeRasterThreshold(val);
            medianOffsetPlottingButton->setToggleState(true, dontSendNotification);
            lfpDisplay->setMedianOffsetPlotting(true);
            lfpDisplay->setSpikeRasterPlotting(true);
        }
        else if (cb->getSelectedItemIndex() == 0) // if "Off"
        {
            lfpDisplay->setSpikeRasterPlotting(false);
            return;
        }
        else
        {
            auto val = cb->getText().getFloatValue();
            
            lfpDisplay->setSpikeRasterThreshold(val);
            medianOffsetPlottingButton->setToggleState(true, dontSendNotification);
            lfpDisplay->setMedianOffsetPlotting(true);
            lfpDisplay->setSpikeRasterPlotting(true);
        }
    }
    else if (cb == colourSchemeOptionSelection)
    {
        // hide the old colour scheme config options if they are displayed
        if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements())
            removeChildComponent(lfpDisplay->getColourSchemePtr());
        
        // change the active colour scheme ptr
        lfpDisplay->setActiveColourSchemeIdx(cb->getSelectedId()-1);
        
        // show the new colour scheme's config options if has any
        
        if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements())
        {
            lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(),
                                                        colourSchemeOptionLabel->getBottom(),
                                                        200,
                                                        110);
            addAndMakeVisible(lfpDisplay->getColourSchemePtr());
        }
        
        // update the lfpDisplay's colors and redraw
        lfpDisplay->setColors();
        canvas->redraw();
    }
    else if (cb == timebaseSelection)
    {
        if (cb->getSelectedId())
        {
            canvas->timebase = timebases[cb->getSelectedId()-1].getFloatValue();
        }
        else
        {
            setTimebaseAndSelectionText(cb->getText().getFloatValue());
        }
    }
    else if (cb == rangeSelection)
    {
        if (cb->getSelectedId())
        {
        lfpDisplay->setRange(voltageRanges[selectedChannelType][cb->getSelectedId()-1].getFloatValue()*rangeGain[selectedChannelType]
            ,selectedChannelType);
        }
        else
        {
            float vRange = cb->getText().getFloatValue();
            if (vRange)
            {
                if (vRange < voltageRanges[selectedChannelType][0].getFloatValue())
                {
                    cb->setSelectedId(1,dontSendNotification);
                    vRange = voltageRanges[selectedChannelType][0].getFloatValue();
                }
                else if (vRange > voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue())
                {
                   // cb->setSelectedId(voltageRanges[selectedChannelType].size(),dontSendNotification);
                   // vRange = voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue();
                }
                else
                {
                    if (rangeGain[selectedChannelType] > 1)
                        cb->setText(String(vRange,1),dontSendNotification);
                    else
                        cb->setText(String(vRange),dontSendNotification);
                }
                lfpDisplay->setRange(vRange*rangeGain[selectedChannelType],selectedChannelType);
            }
            else
            {
                if (selectedVoltageRange[selectedChannelType])
                    cb->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification);
                else
                    cb->setSelectedId(selectedVoltageRange[selectedChannelType],dontSendNotification);
            }
        }
        selectedVoltageRange[selectedChannelType] = cb->getSelectedId();
        selectedVoltageRangeValues[selectedChannelType] = cb->getText();
        //std::cout << "Setting range to " << voltageRanges[cb->getSelectedId()-1].getFloatValue() << std::endl;
        canvas->redraw();
    }
    else if (cb == spreadSelection)
    {
        
        if (cb->getSelectedId())
        {
            if (lfpDisplay->getSingleChannelState())
            {
                lfpDisplay->cacheNewChannelHeight(spreads[cb->getSelectedId()-1].getIntValue());
            }
            else
            {
                lfpDisplay->setChannelHeight(spreads[cb->getSelectedId()-1].getIntValue());
                resized();
            }
        }
        else
        {
            int spread = cb->getText().getIntValue();
            if (spread)
            {
                if (spread < spreads[0].getFloatValue())
                {
                    cb->setSelectedId(1,dontSendNotification);
                    spread = spreads[0].getFloatValue();
                }
                else if (spread > spreads[spreads.size()-1].getFloatValue())
                {
                    cb->setSelectedId(spreads.size(),dontSendNotification);
                    spread = spreads[spreads.size()-1].getFloatValue();
                }
                else
                {
                    cb->setText(String(spread),dontSendNotification);
                }
                
                // if single channel focus is on, cache the value
                if (lfpDisplay->getSingleChannelState())
                {
                    lfpDisplay->cacheNewChannelHeight(spread);
                }
                else
                {
                    lfpDisplay->setChannelHeight(spread);
                    canvas->resized();
                }
            }
            else
            {
                if (selectedSpread == 0)
                    cb->setText(selectedSpreadValue,dontSendNotification);
                else
                    cb->setSelectedId(selectedSpread,dontSendNotification);
            }
        }
        selectedSpread = cb->getSelectedId();
        selectedSpreadValue = cb->getText();

        if (!lfpDisplay->getSingleChannelState()) canvas->redraw();
        //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl;
    }
    else if (cb == saturationWarningSelection)
    {
        if (cb->getSelectedId())
        {
            selectedSaturationValueFloat = (saturationThresholds[cb->getSelectedId()-1].getFloatValue());
        }
        else
        {
            selectedSaturationValueFloat = cb->getText().getFloatValue();
            if (selectedSaturationValueFloat)
            {
                 std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl;
                if (selectedSaturationValueFloat < 0)
                {
                    cb->setSelectedId(1,dontSendNotification);
                    selectedSaturationValueFloat = saturationThresholds[0].getFloatValue();
                }
                else
                {
                  //  cb->setText(String(selectedSaturationValueFloat),dontSendNotification);
                }
            }
            else
            {
               // cb->setSelectedId(1,dontSendNotification);
                //selectedSaturationValueFloat = saturationThresholds[0].getFloatValue();

            }
        }
        canvas->redraw();

        std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl;
    }
    else if (cb == overlapSelection)
    {
        if (cb->getSelectedId())
        {
            canvas->channelOverlapFactor = (overlaps[cb->getSelectedId()-1].getFloatValue());
            canvas->resized();
        }
        else
        {
            float overlap = cb->getText().getFloatValue();
            if (overlap)
            {
                if (overlap < overlaps[0].getFloatValue())
                {
                    cb->setSelectedId(1,dontSendNotification);
                    overlap = overlaps[0].getFloatValue();
                }
                else if (overlap > overlaps[overlaps.size()-1].getFloatValue())
                {
                    cb->setSelectedId(overlaps.size(),dontSendNotification);
                    overlap = overlaps[overlaps.size()-1].getFloatValue();
                }
                else
                {
                    cb->setText(String(overlap),dontSendNotification);
                }
                canvas->channelOverlapFactor= overlap;
                canvas->resized();
            }
            else
            {
                if (selectedSpread == 0)
                    cb->setText(selectedSpreadValue,dontSendNotification);
                else
                    cb->setSelectedId(selectedSpread,dontSendNotification);
            }
        }
        selectedSpread = cb->getSelectedId();
        selectedSpreadValue = cb->getText();
        lfpDisplay->setChannelHeight( lfpDisplay->getChannelHeight());
        canvas->redraw();
        //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl;
    }

    else if (cb == colorGroupingSelection)
    {
        // set color grouping here
        lfpDisplay->setColorGrouping(colorGroupings[cb->getSelectedId()-1].getIntValue());// so that channel colors get re-assigned
        canvas->redraw();
    }

    timescale->setTimebase(canvas->timebase);
}


void LfpDisplayOptions::sliderValueChanged(Slider* sl)
{
    if (sl == brightnessSliderA)
        canvas->histogramParameterA = sl->getValue();

    if (sl == brightnessSliderB)
        canvas->histogramParameterB = sl->getValue();

    
    canvas->fullredraw=true;
    //repaint();
    canvas->refresh();

}

void LfpDisplayOptions::sliderEvent(Slider* sl) {}

DataChannel::DataChannelTypes LfpDisplayOptions::getChannelType(int n)
{
    if (n < processor->getNumInputs())
        return processor->getDataChannel(n)->getChannelType();
    else
        return DataChannel::HEADSTAGE_CHANNEL;
}

DataChannel::DataChannelTypes LfpDisplayOptions::getSelectedType()
{
    return selectedChannelType;
}

void LfpDisplayOptions::setSelectedType(DataChannel::DataChannelTypes type, bool toggleButton)
{
    if (selectedChannelType == type)
        return; //Nothing to do here
    selectedChannelType = type;
    rangeSelection->clear(dontSendNotification);
    rangeSelection->addItemList(voltageRanges[type],1);

    int id = selectedVoltageRange[type];
    if (id)
        rangeSelection->setSelectedId(id,sendNotification);
    else
        rangeSelection->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification);
    
    repaint(5,getHeight()-55,300,100);

    if (toggleButton)
        typeButtons[type]->setToggleState(true,dontSendNotification);
}

String LfpDisplayOptions::getTypeName(DataChannel::DataChannelTypes type)
{
    return typeNames[type];
}

int LfpDisplayOptions::getRangeStep(DataChannel::DataChannelTypes type)
{
    return rangeSteps[type];
}



void LfpDisplayOptions::saveParameters(XmlElement* xml)
{
    // TODO: (kelly) add savers for:
    //      - channel reverse
    //      - channel zoom slider
    //      - channel display skip
    std::cout << "Saving lfp display params" << std::endl;

    XmlElement* xmlNode = xml->createNewChildElement("LFPDISPLAY");

    lfpDisplay->reactivateChannels();

    xmlNode->setAttribute("Range",selectedVoltageRangeValues[0]+","+selectedVoltageRangeValues[1]+
        ","+selectedVoltageRangeValues[2]);
    xmlNode->setAttribute("Timebase",timebaseSelection->getText());
    xmlNode->setAttribute("Spread",spreadSelection->getText());
    xmlNode->setAttribute("colorGrouping",colorGroupingSelection->getSelectedId());
    xmlNode->setAttribute("isInverted",invertInputButton->getToggleState());
    xmlNode->setAttribute("drawMethod",drawMethodButton->getToggleState());

    int eventButtonState = 0;

    for (int i = 0; i < 8; i++)
    {
        if (lfpDisplay->eventDisplayEnabled[i])
        {
            eventButtonState += (1 << i);
        }
    }

    lfpDisplay->reactivateChannels();

    xmlNode->setAttribute("EventButtonState", eventButtonState);

    String channelDisplayState = "";

    for (int i = 0; i < canvas->nChans; i++)
    {
        if (lfpDisplay->getEnabledState(i))
        {
            channelDisplayState += "1";
        }
        else
        {
            channelDisplayState += "0";
        }
        //std::cout << channelDisplayState;
    }

    //std::cout << std::endl;


    xmlNode->setAttribute("ChannelDisplayState", channelDisplayState);

    xmlNode->setAttribute("ScrollX",canvas->viewport->getViewPositionX());
    xmlNode->setAttribute("ScrollY",canvas->viewport->getViewPositionY());
}


void LfpDisplayOptions::loadParameters(XmlElement* xml)
{
    // TODO: (kelly) add loaders for:
    //      - channel reverse
    //      - channel zoom slider
    //      - channel display skip
    forEachXmlChildElement(*xml, xmlNode)
    {
        if (xmlNode->hasTagName("LFPDISPLAY"))
        {
            StringArray ranges;
            ranges.addTokens(xmlNode->getStringAttribute("Range"),",",String::empty);
            selectedVoltageRangeValues[0] = ranges[0];
            selectedVoltageRangeValues[1] = ranges[1];
            selectedVoltageRangeValues[2] = ranges[2];
            selectedVoltageRange[0] = voltageRanges[0].indexOf(ranges[0])+1;
            selectedVoltageRange[1] = voltageRanges[1].indexOf(ranges[1])+1;
            selectedVoltageRange[2] = voltageRanges[2].indexOf(ranges[2])+1;
            rangeSelection->setText(ranges[0]);

            timebaseSelection->setText(xmlNode->getStringAttribute("Timebase"));
            spreadSelection->setText(xmlNode->getStringAttribute("Spread"));
            if (xmlNode->hasAttribute("colorGrouping"))
            {
                colorGroupingSelection->setSelectedId(xmlNode->getIntAttribute("colorGrouping"));
            }
            else
            {
                colorGroupingSelection->setSelectedId(1);
            }

            invertInputButton->setToggleState(xmlNode->getBoolAttribute("isInverted", true), sendNotification);

            drawMethodButton->setToggleState(xmlNode->getBoolAttribute("drawMethod", true), sendNotification);

            canvas->viewport->setViewPosition(xmlNode->getIntAttribute("ScrollX"),
                                      xmlNode->getIntAttribute("ScrollY"));

            int eventButtonState = xmlNode->getIntAttribute("EventButtonState");

            for (int i = 0; i < 8; i++)
            {
                lfpDisplay->eventDisplayEnabled[i] = (eventButtonState >> i) & 1;

                eventDisplayInterfaces[i]->checkEnabledState();
            }

            String channelDisplayState = xmlNode->getStringAttribute("ChannelDisplayState");

            for (int i = 0; i < channelDisplayState.length(); i++)
            {

                if (channelDisplayState.substring(i,i+1).equalsIgnoreCase("1"))
                {
                    //std::cout << "LfpDisplayCanvas enabling channel " << i << std::endl;
                    //lfpDisplay->enableChannel(true, i);
                    canvas->isChannelEnabled.set(i,true); //lfpDisplay->enableChannel(true, i);
                }
                else
                {
                    //lfpDisplay->enableChannel(false, i);
                    canvas->isChannelEnabled.set(i,false);
                }


            }
        }
    }

}


#pragma mark - LfpTimescale -
// -------------------------------------------------------------

LfpTimescale::LfpTimescale(LfpDisplayCanvas* c, LfpDisplay* lfpDisplay)
    : canvas(c)
    , lfpDisplay(lfpDisplay)
{

    font = Font("Default", 16, Font::plain);
}

LfpTimescale::~LfpTimescale()
{

}

void LfpTimescale::paint(Graphics& g)
{

    g.setFont(font);

    g.setColour(Colour(100,100,100));

    const String timeScaleUnitLabel = (timebase >= 2)?("s:"):("ms:");
    g.drawText(timeScaleUnitLabel,5,0,100,getHeight(),Justification::left, false);

    const int steps = labels.size() + 1;
    for (int i = 0; i < steps; i++)
    {
        
        // TODO: (kelly) added an extra spatial dimension to the timeline ticks, may be overkill
        if (i == 0)
        {
            g.drawLine(1,
                       0,
                       1,
                       getHeight(),
                       3.0f);
        }
        if (i != 0 && i % 4 == 0)
        {
            g.drawLine(getWidth()/steps*i,
                       0,
                       getWidth()/steps*i,
                       getHeight(),
                       3.0f);
        }
        else if (i != 0 && i % 2 == 0)
        {
            g.drawLine(getWidth()/steps*i,
                       getHeight(),
                       getWidth()/steps*i,
                       getHeight() / 2,
                       3.0f);
        }
        else
        {
            g.drawLine(getWidth()/steps*i,
                       getHeight(),
                       getWidth()/steps*i,
                       3 * getHeight()/4,
                       2.0f);
        }
        

        if (i != 0 && i % 2 == 0)
            g.drawText(labels[i-1],getWidth()/steps*i+3,0,100,getHeight(),Justification::left, false);

    }
}

void LfpTimescale::mouseUp(const MouseEvent &e)
{
    if (e.mods.isLeftButtonDown())
    {
        lfpDisplay->trackZoomInfo.isScrollingX = false;
    }
}

void LfpTimescale::resized()
{
    setTimebase(timebase);
}

void LfpTimescale::mouseDrag(const juce::MouseEvent &e)
{
    if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold
    {
        if (e.mods.isCommandDown())  // CTRL + drag -> change channel spacing
        {
            // init state in our track zooming info struct
            if (!lfpDisplay->trackZoomInfo.isScrollingX)
            {
                lfpDisplay->trackZoomInfo.isScrollingX = true;
                lfpDisplay->trackZoomInfo.timescaleStartScale = timebase;
            }

            float timescale = lfpDisplay->trackZoomInfo.timescaleStartScale;
            float dTimescale=0;
            int dragDeltaX = (e.getScreenPosition().getX() - e.getMouseDownScreenX()); // invert so drag up -> scale up

//            std::cout << dragDeltaX << std::endl;
            if (dragDeltaX > 0)
            {
                dTimescale = 0.01 * dragDeltaX;
            }
            else
            {
                // TODO: (kelly) change this to scale appropriately for -dragDeltaX
                if (timescale > 0.25)
                    dTimescale = 0.01 * dragDeltaX;
            }
            
            if (timescale >= 1) // accelerate scrolling for large ranges
                dTimescale *= 4;
            
            if (timescale >= 5)
                dTimescale *= 4;
            
            if (timescale >= 10)
                dTimescale *= 4;
            
            // round dTimescale to the nearest 0.005 sec
            dTimescale = ((dTimescale + (0.005/2)) / 0.005) * 0.005;
            
            float newTimescale = timescale+dTimescale;
            
            if (newTimescale < 0.25) newTimescale = 0.250;
            if (newTimescale > 20) newTimescale = 20;
            
            // don't bother updating if the new timebase is the same as the old (if clipped, for example)
            if (timescale != newTimescale)
            {
                lfpDisplay->options->setTimebaseAndSelectionText(newTimescale);
                setTimebase(canvas->timebase);
            }
        }
    }
}

void LfpTimescale::setTimebase(float t)
{
    timebase = t;

    labels.clear();
    
    const int minWidth = 60;
    labelIncrement = 0.025f;
    
    
    while (getWidth() != 0 &&                                   // setTimebase can be called before LfpTimescale has width
           getWidth() / (timebase / labelIncrement) < minWidth) // so, if width is 0 then don't iterate for scale factor
    {
//        std::cout << getWidth() / (timebase / labelIncrement) << " is smaller than minimum width, calculating new step size" << std::endl;
        if (labelIncrement < 0.2)
            labelIncrement *= 2;
        else
            labelIncrement += 0.2;
    }
    
    for (float i = labelIncrement; i < timebase; i += labelIncrement)
    {
        String labelString = String(i * ((timebase >= 2)?(1):(1000.0f)));
        labels.add(labelString.substring(0,6));
    }

    repaint();

}



#pragma mark - LfpDisplay -
// ---------------------------------------------------------------

LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v)
    : singleChan(-1)
    , canvas(c)
    , viewport(v)
    , channelsReversed(false)
    , displaySkipAmt(0)
    , m_SpikeRasterPlottingFlag(false)
{
    perPixelPlotter = new PerPixelBitmapPlotter(this);
    supersampledPlotter = new SupersampledBitmapPlotter(this);
    
//    colorScheme = new LfpDefaultColourScheme();
    colourSchemeList.add(new LfpDefaultColourScheme(this, canvas));
    colourSchemeList.add(new LfpMonochromaticColourScheme(this, canvas));
    colourSchemeList.add(new LfpGradientColourScheme(this, canvas));
    
    activeColourScheme = 0;
    
    
    plotter = perPixelPlotter;
    m_MedianOffsetPlottingFlag = false;
    
    totalHeight = 0;
    colorGrouping=1;

    range[0] = 1000;
    range[1] = 500;
    range[2] = 500000;

    addMouseListener(this, true);

    // hue cycle
    //for (int i = 0; i < 15; i++)
    //{
    //    channelColours.add(Colour(float(sin((3.14/2)*(float(i)/15))),float(1.0),float(1),float(1.0)));
    //}
    
//    setBufferedToImage(true); // TODO: (kelly) test

    backgroundColour = Colour(0,18,43);
    
    //hand-built palette
    channelColours.add(Colour(224,185,36));
    channelColours.add(Colour(214,210,182));
    channelColours.add(Colour(243,119,33));
    channelColours.add(Colour(186,157,168));
    channelColours.add(Colour(237,37,36));
    channelColours.add(Colour(179,122,79));
    channelColours.add(Colour(217,46,171));
    channelColours.add(Colour(217, 139,196));
    channelColours.add(Colour(101,31,255));
    channelColours.add(Colour(141,111,181));
    channelColours.add(Colour(48,117,255));
    channelColours.add(Colour(184,198,224));
    channelColours.add(Colour(116,227,156));
    channelColours.add(Colour(150,158,155));
    channelColours.add(Colour(82,173,0));
    channelColours.add(Colour(125,99,32));

    isPaused=false;

}

LfpDisplay::~LfpDisplay()
{
//    deleteAllChildren();
}



int LfpDisplay::getNumChannels()
{
    return numChans;
}



int LfpDisplay::getColorGrouping()
{
    return colorGrouping;
}

void LfpDisplay::setColorGrouping(int i)
{
    colorGrouping=i;
    getColourSchemePtr()->setColourGrouping(i);
    setColors(); // so that channel colors get re-assigned

}

LfpChannelColourScheme * LfpDisplay::getColourSchemePtr()
{
    return colourSchemeList[activeColourScheme];
}

void LfpDisplay::setNumChannels(int numChannels)
{
    numChans = numChannels;
    

//    deleteAllChildren();
    removeAllChildren();

    channels.clear();
    channelInfo.clear();
    drawableChannels.clear();

    totalHeight = 0;
    cachedDisplayChannelHeight = canvas->getChannelHeight();

    for (int i = 0; i < numChans; i++)
    {
        //std::cout << "Adding new display for channel " << i << std::endl;

        LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, options, i);

        //lfpChan->setColour(channelColours[i % channelColours.size()]);
        lfpChan->setRange(range[options->getChannelType(i)]);
        lfpChan->setChannelHeight(canvas->getChannelHeight());
        
        addAndMakeVisible(lfpChan);

        channels.add(lfpChan);

        LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, options, i);

        //lfpInfo->setColour(channelColours[i % channelColours.size()]);
        lfpInfo->setRange(range[options->getChannelType(i)]);
        lfpInfo->setChannelHeight(canvas->getChannelHeight());
        lfpInfo->setSubprocessorIdx(canvas->getChannelSubprocessorIdx(i));

        addAndMakeVisible(lfpInfo);

        channelInfo.add(lfpInfo);
        
        drawableChannels.add(LfpChannelTrack{
            lfpChan,
            lfpInfo
        });

		savedChannelState.add(true);

        totalHeight += lfpChan->getChannelHeight();

    }

    setColors();
    

    std::cout << "TOTAL HEIGHT = " << totalHeight << std::endl;

}

void LfpDisplay::setColors()
{
    for (int i = 0; i < drawableChannels.size(); i++)
    {

//        channels[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]);
//        channelInfo[i]->setColour(channelColours[(int(i/colorGrouping)+1)  % channelColours.size()]);
        drawableChannels[i].channel->setColour(getColourSchemePtr()->getColourForIndex(i));
        drawableChannels[i].channelInfo->setColour(getColourSchemePtr()->getColourForIndex(i));
    }

}

void LfpDisplay::setActiveColourSchemeIdx(int index)
{
    activeColourScheme = index;
}

int LfpDisplay::getActiveColourSchemeIdx()
{
    return activeColourScheme;
}

int LfpDisplay::getNumColourSchemes()
{
    return colourSchemeList.size();
}

StringArray LfpDisplay::getColourSchemeNameArray()
{
    StringArray nameList;
    for (auto scheme : colourSchemeList)
        nameList.add(scheme->getName());
    
    return nameList;
}

int LfpDisplay::getTotalHeight()
{
    return totalHeight;
}

void LfpDisplay::resized()
{
    int totalHeight = 0;
    std::cout << "channelOverlapFactor " << canvas->channelOverlapFactor << std::endl;
    
    for (int i = 0; i < drawableChannels.size(); i++)
    {
        
        LfpChannelDisplay* disp = drawableChannels[i].channel;
        
        if (disp->getHidden()) continue;
        
        disp->setBounds(canvas->leftmargin,
                        totalHeight-(disp->getChannelOverlap()*canvas->channelOverlapFactor)/2,
                        getWidth(),
                        disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor));
        
        disp-> resized();
        
        LfpChannelDisplayInfo* info = drawableChannels[i].channelInfo;
        
        info->setBounds(0,
//                        totalHeight-disp->getChannelHeight()/4,
                        totalHeight-disp->getChannelHeight() + (disp->getChannelOverlap()*canvas->channelOverlapFactor)/4.0,
                        canvas->leftmargin + 50,
                        disp->getChannelHeight());
//                        disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor));
        
        totalHeight += disp->getChannelHeight();
        
    }

    canvas->fullredraw = true; //issue full redraw
    if (singleChan != -1)
        viewport->setViewPosition(Point<int>(0,singleChan*getChannelHeight()));

  

    lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false);
    
    //inititalize black background
    Graphics gLfpChannelBitmap(lfpChannelBitmap);
    gLfpChannelBitmap.setColour(Colour(0,0,0)); //background color
    gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight());

    
    canvas->fullredraw = true;
    
    refresh();
    // std::cout << "Total height: " << totalHeight << std::endl;

}

void LfpDisplay::paint(Graphics& g)
{

    g.drawImageAt(lfpChannelBitmap, canvas->leftmargin,0);
    
}


void LfpDisplay::refresh()
{
    
    // X-bounds of this update
    int fillfrom = canvas->lastScreenBufferIndex[0];
    int fillto = (canvas->screenBufferIndex[0]);
    
    if (fillfrom<0){fillfrom=0;};
    if (fillto>lfpChannelBitmap.getWidth()){fillto=lfpChannelBitmap.getWidth();};
    
    int topBorder = viewport->getViewPositionY();
    int bottomBorder = viewport->getViewHeight() + topBorder;

    // clear appropriate section of the bitmap --
    // we need to do this before each channel draws its new section of data into lfpChannelBitmap
    Graphics gLfpChannelBitmap(lfpChannelBitmap);
    gLfpChannelBitmap.setColour(backgroundColour); //background color

    if (canvas->fullredraw)
    {
        gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight());
    } else {
        gLfpChannelBitmap.setColour(backgroundColour); //background color

        gLfpChannelBitmap.fillRect(fillfrom,0, (fillto-fillfrom)+1, getHeight());
    };
    
    
    for (int i = 0; i < numChans; i++)
//    for (int i = 0; i < drawableChannels.size(); ++i)
    {

        int componentTop = channels[i]->getY();
        int componentBottom = channels[i]->getHeight() + componentTop;

        if ((topBorder <= componentBottom && bottomBorder >= componentTop)) // only draw things that are visible
        {
            if (canvas->fullredraw)
            {
                channels[i]->fullredraw = true;
                
                channels[i]->pxPaint();
                channelInfo[i]->repaint();
                
            }
            else
            {
                 channels[i]->pxPaint(); // draws to lfpChannelBitmap
                
                 // it's not clear why, but apparently because the pxPaint() in a child component of LfpDisplay, we also need to issue repaint() calls for each channel, even though there's nothin to repaint there. Otherwise, the repaint call in LfpDisplay::refresh(), a few lines down, lags behind the update line by ~60 px. This could ahev something to do with teh reopaint message passing in juce. In any case, this seemingly redundant repaint here seems to fix the issue.
                
                 // we redraw from 0 to +2 (px) relative to the real redraw window, the +1 draws the vertical update line
                 channels[i]->repaint(fillfrom, 0, (fillto-fillfrom)+2, channels[i]->getHeight());
                
                
            }
            //std::cout << i << std::endl;
        }

    }

    if (fillfrom == 0 && singleChan != -1)
    {
        channelInfo[singleChan]->repaint();
    }
    
    
    if (canvas->fullredraw)
    {
        repaint(0,topBorder,getWidth(),bottomBorder-topBorder);
    }else{
        //repaint(fillfrom, topBorder, (fillto-fillfrom)+1, bottomBorder-topBorder); // doesntb seem to be needed and results in duplicate repaint calls
    }
    
    canvas->fullredraw = false;
    
}



void LfpDisplay::setRange(float r, DataChannel::DataChannelTypes type)
{
    range[type] = r;
    
    if (channels.size() > 0)
    {

        for (int i = 0; i < numChans; i++)
        {
            if (channels[i]->getType() == type)
                channels[i]->setRange(range[type]);
        }
        canvas->fullredraw = true; //issue full redraw
    }
}

int LfpDisplay::getRange()
{
    return getRange(options->getSelectedType());
}

int LfpDisplay::getRange(DataChannel::DataChannelTypes type)
{
    for (int i=0; i < numChans; i++)
    {
        if (channels[i]->getType() == type)
            return channels[i]->getRange();
    }
    return 0;
}


void LfpDisplay::setChannelHeight(int r, bool resetSingle)
{
    if (!getSingleChannelState()) cachedDisplayChannelHeight = r;
    
    for (int i = 0; i < numChans; i++)
    {
        channels[i]->setChannelHeight(r);
        channelInfo[i]->setChannelHeight(r);
    }
    if (resetSingle && singleChan != -1)
    {
        //std::cout << "width " <<  getWidth() << " numchans  " << numChans << " height " << getChannelHeight() << std::endl;
        setSize(getWidth(),drawableChannels.size()*getChannelHeight());
        viewport->setScrollBarsShown(true,false);
        viewport->setViewPosition(Point<int>(0,singleChan*r));
        singleChan = -1;
        for (int n = 0; n < numChans; n++)
        {
			channelInfo[n]->setEnabledState(savedChannelState[n]);
        }
    }

    resized();

}

void LfpDisplay::setInputInverted(bool isInverted)
{

    for (int i = 0; i < numChans; i++)
    {
        channels[i]->setInputInverted(isInverted);
    }

    resized();

}

void LfpDisplay::setDrawMethod(bool isDrawMethod)
{
    for (int i = 0; i < numChans; i++)
    {
        channels[i]->setDrawMethod(isDrawMethod);
    }
    
    if (isDrawMethod)
    {
        plotter = supersampledPlotter;
    }
    else
    {
        plotter = perPixelPlotter;
    }
    
    resized();

}


int LfpDisplay::getChannelHeight()
{
//    return cachedDisplayChannelHeight;
    return drawableChannels[0].channel->getChannelHeight();
//    return channels[0]->getChannelHeight();
}

void LfpDisplay::cacheNewChannelHeight(int r)
{
    cachedDisplayChannelHeight = r;
}

float LfpDisplay::getDisplayedSampleRate()
{
    return drawableSampleRate;
}

// Must manually call rebuildDrawableChannelsList after this is set, typically will happen
// already as a result of some other procedure
void LfpDisplay::setDisplayedSampleRate(float samplerate)
{
//    std::cout << "Setting the displayed samplerate for LfpDisplayCanvas to " << samplerate << std::endl;
    drawableSampleRate = samplerate;
}

int LfpDisplay::getDisplayedSubprocessor()
{
    return drawableSubprocessorIdx;
}

void LfpDisplay::setDisplayedSubprocessor(int subProcessorIdx)
{
    drawableSubprocessorIdx = subProcessorIdx;
}

bool LfpDisplay::getChannelsReversed()
{
    return channelsReversed;
}

void LfpDisplay::setChannelsReversed(bool state)
{
    if (state == channelsReversed) return; // bail early, in case bookkeeping error
    
    channelsReversed = state;
    
    if (getSingleChannelState()) return; // don't reverse if single channel
    
    // reverse channels that are currently in drawableChannels
    for (size_t i = 0, j = drawableChannels.size() - 1, len = drawableChannels.size()/2;
         i < len;
         i++, j--)
    {
        // remove channel and info components from front and back
        // moving toward middle
        removeChildComponent(drawableChannels[i].channel);
        removeChildComponent(drawableChannels[j].channel);
        removeChildComponent(drawableChannels[i].channelInfo);
        removeChildComponent(drawableChannels[j].channelInfo);
        
        // swap front and back, moving towards middle
        drawableChannels.swap(i, j);
        
        // also swap coords
        {
            const auto channelBoundsA = drawableChannels[i].channel->getBounds();
            const auto channelInfoBoundsA = drawableChannels[i].channelInfo->getBounds();
            
            drawableChannels[i].channel->setBounds(drawableChannels[j].channel->getBounds());
            drawableChannels[i].channelInfo->setBounds(drawableChannels[j].channelInfo->getBounds());
            drawableChannels[j].channel->setBounds(channelBoundsA);
            drawableChannels[j].channelInfo->setBounds(channelInfoBoundsA);
        }
    }
    
    
    // remove middle component if odd number of channels
    if (drawableChannels.size() % 2 != 0)
    {
        removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channel);
        removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channelInfo);
    }
    
    // add the channels and channel info again
    for (size_t i = 0, len = drawableChannels.size(); i < len; i++)
    {
        
        if (!drawableChannels[i].channel->getHidden())
        {
            addAndMakeVisible(drawableChannels[i].channel);
            addAndMakeVisible(drawableChannels[i].channelInfo);
        }
        
        // flag this to update the waveforms
        drawableChannels[i].channel->fullredraw = true;
    }
    
    // necessary to overwrite lfpChannelBitmap's display
    refresh();
}

int LfpDisplay::getChannelDisplaySkipAmount()
{
    return displaySkipAmt;
}

void LfpDisplay::setChannelDisplaySkipAmount(int skipAmt)
{
    displaySkipAmt = skipAmt;
    
    if (!getSingleChannelState())
        rebuildDrawableChannelsList();
    
    canvas->redraw();
}

bool LfpDisplay::getMedianOffsetPlotting()
{
    return m_MedianOffsetPlottingFlag;
}

void LfpDisplay::setMedianOffsetPlotting(bool isEnabled)
{
    m_MedianOffsetPlottingFlag = isEnabled;
}

bool LfpDisplay::getSpikeRasterPlotting()
{
    return m_SpikeRasterPlottingFlag;
}

void LfpDisplay::setSpikeRasterPlotting(bool isEnabled)
{
    m_SpikeRasterPlottingFlag = isEnabled;
}

float LfpDisplay::getSpikeRasterThreshold()
{
    return m_SpikeRasterThreshold;
}

void LfpDisplay::setSpikeRasterThreshold(float thresh)
{
    m_SpikeRasterThreshold = thresh;
}

void LfpDisplay::mouseWheelMove(const MouseEvent&  e, const MouseWheelDetails&   wheel)
{

    //std::cout << "Mouse wheel " <<  e.mods.isCommandDown() << "  " << wheel.deltaY << std::endl;
    //TODO Changing ranges with the wheel is currently broken. With multiple ranges, most
    //of the wheel range code needs updating
    
    
    if (e.mods.isCommandDown() && singleChan == -1)  // CTRL + scroll wheel -> change channel spacing
    {
        int h = getChannelHeight();
        int hdiff=0;
        
        // std::cout << wheel.deltaY << std::endl;
        
        if (wheel.deltaY > 0)
        {
            hdiff = 2;
        }
        else
        {
            if (h > 5)
                hdiff = -2;
        }

        if (abs(h) > 100) // accelerate scrolling for large ranges
            hdiff *= 3;
        
        int newHeight = h+hdiff;
        
        // constrain the spread resizing to max and min values;
        if (newHeight < trackZoomInfo.minZoomHeight)
        {
            newHeight = trackZoomInfo.minZoomHeight;
            hdiff = 0;
        }
        else if (newHeight > trackZoomInfo.maxZoomHeight)
        {
            newHeight = trackZoomInfo.maxZoomHeight;
            hdiff = 0;
        }

        setChannelHeight(newHeight);
        int oldX=viewport->getViewPositionX();
        int oldY=viewport->getViewPositionY();

        setBounds(0,0,getWidth()-0, getChannelHeight()*drawableChannels.size()); // update height so that the scrollbar is correct

        int mouseY=e.getMouseDownY(); // should be y pos relative to inner viewport (0,0)
        int scrollBy = (mouseY/h)*hdiff*2;// compensate for motion of point under current mouse position
        viewport->setViewPosition(oldX,oldY+scrollBy); // set back to previous position plus offset

        options->setSpreadSelection(newHeight); // update combobox
        
        canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw
    }
    else
    {
        if (e.mods.isAltDown())  // ALT + scroll wheel -> change channel range (was SHIFT but that clamps wheel.deltaY to 0 on OSX for some reason..)
        {
            int h = getRange();
            
            
            int step = options->getRangeStep(options->getSelectedType());
                       
            // std::cout << wheel.deltaY << std::endl;
            
            if (wheel.deltaY > 0)
            {
                setRange(h+step,options->getSelectedType());
            }
            else
            {
                if (h > step+1)
                    setRange(h-step,options->getSelectedType());
            }

            options->setRangeSelection(h); // update combobox
            canvas->fullredraw = true; //issue full redraw - scrolling without modifier doesnt require a full redraw
            
        }
        else    // just scroll
        {
            //  passes the event up to the viewport so the screen scrolls
            if (viewport != nullptr && e.eventComponent == this) // passes only if it's not a listening event
                viewport->mouseWheelMove(e.getEventRelativeTo(canvas), wheel);

        }
      

    }
       //refresh(); // doesn't seem to be needed now that channels daraw to bitmap

}

void LfpDisplay::toggleSingleChannel(int chan)
{
    if (!getSingleChannelState())
    {
        
        std::cout << "Single channel on (" << chan << ")" << std::endl;
        singleChan = chan;
        
        int newHeight = viewport->getHeight();
        LfpChannelTrack lfpChannelTrack{drawableChannels[chan].channel, drawableChannels[chan].channelInfo};
        lfpChannelTrack.channelInfo->setEnabledState(true);
        lfpChannelTrack.channelInfo->setSingleChannelState(true);
        
        removeAllChildren();
        
        // disable unused channels
        for (int i = 0; i < getNumChannels(); i++)
        {
            if (i != chan)
            {
                drawableChannels[i].channel->setEnabledState(false);
            }
        }
        
        // update drawableChannels, give only the single channel to focus on
        Array<LfpChannelTrack> channelsToDraw{lfpChannelTrack};
        drawableChannels = channelsToDraw;
        
        addAndMakeVisible(lfpChannelTrack.channel);
        addAndMakeVisible(lfpChannelTrack.channelInfo);
        
        // set channel height and position (so that we allocate the smallest
        // necessary image size for drawing)
        setChannelHeight(newHeight, false);
        
        lfpChannelTrack.channel->setTopLeftPosition(canvas->leftmargin, 0);
        lfpChannelTrack.channelInfo->setTopLeftPosition(0, 0);
        setSize(getWidth(), getChannelHeight());
        
        viewport->setViewPosition(0, 0);

    }
//    else if (chan == singleChan || chan == -2)
    else
    {
        std::cout << "Single channel off" << std::endl;
        for (int n = 0; n < numChans; n++)
        {
            channelInfo[n]->setSingleChannelState(false);
        }
        
        setChannelHeight(cachedDisplayChannelHeight);

        reactivateChannels();
        rebuildDrawableChannelsList();
    }
}

void LfpDisplay::reactivateChannels()
{

    for (int n = 0; n < channels.size(); n++)
       setEnabledState(savedChannelState[n], n);

}

void LfpDisplay::rebuildDrawableChannelsList()
{
    
    if (displaySkipAmt != 0) removeAllChildren(); // start with clean slate
    
    Array<LfpChannelTrack> channelsToDraw;
    drawableChannels = Array<LfpDisplay::LfpChannelTrack>();
    
    // iterate over all channels and select drawable ones
    for (size_t i = 0, drawableChannelNum = 0; i < channels.size(); i++)
    {
//        std::cout << "\tchannel " << i << " has subprocessor index of "  << channelInfo[i]->getSubprocessorIdx() << std::endl;
        // if channel[i] is not sourced from the correct subprocessor, then hide it and continue
        if (channelInfo[i]->getSubprocessorIdx() != getDisplayedSubprocessor())
        {
            channels[i]->setHidden(true);
            channelInfo[i]->setHidden(true);
            continue;
        }
        
        if (displaySkipAmt == 0 || (i % displaySkipAmt == 0)) // no skips, add all channels
        {
            channels[i]->setHidden(false);
            channelInfo[i]->setHidden(false);
            
            channelInfo[i]->setDrawableChannelNumber(drawableChannelNum++);
            channelInfo[i]->resized(); // to update the conditional drawing of enableButton and channel num
            
            channelsToDraw.add(LfpDisplay::LfpChannelTrack{
                channels[i],
                channelInfo[i]
            });
            
            addAndMakeVisible(channels[i]);
            addAndMakeVisible(channelInfo[i]);
        }
        else // skip some channels
        {
//            if (i % (displaySkipAmt) == 0) // add these channels
//            {
//                channels[i]->setHidden(false);
//                channelInfo[i]->setHidden(false);
//                
//                channelsToDraw.add(LfpDisplay::LfpChannelTrack{
//                    channels[i],
//                    channelInfo[i]
//                });
//                
//                addAndMakeVisible(channels[i]);
//                addAndMakeVisible(channelInfo[i]);
//            }
//            else // but not these
//            {
                channels[i]->setHidden(true);
                channelInfo[i]->setHidden(true);
                
                removeChildComponent(channels[i]);
                removeChildComponent(channelInfo[i]);
//            }
        }
    }
    
    // check if channels should be added to drawableChannels in reverse
    if (getChannelsReversed())
    {
        for (int i = channelsToDraw.size() - 1; i >= 0; --i)
        {
            drawableChannels.add(channelsToDraw[i]);
        }
    }
    else
    {
        for (int i = 0; i < channelsToDraw.size(); ++i)
        {
            drawableChannels.add(channelsToDraw[i]);
        }
    }
    
    // this guards against an exception where the editor sets the drawable samplerate
    // before the lfpDisplay is fully initialized
    if (getHeight() > 0 && getWidth() > 0)
    {
        canvas->resizeToChannels();
    }
    
    setColors();
}

LfpBitmapPlotter * const LfpDisplay::getPlotterPtr() const
{
    return plotter;
}

bool LfpDisplay::getSingleChannelState()
{
    //if (singleChan < 0) return false;
    //else return true;
    return singleChan >= 0;
}


void LfpDisplay::mouseDown(const MouseEvent& event)
{
    //int y = event.getMouseDownY(); //relative to each channel pos
    MouseEvent canvasevent = event.getEventRelativeTo(viewport);
    int y = canvasevent.getMouseDownY() + viewport->getViewPositionY(); // need to account for scrolling
    int x = canvasevent.getMouseDownX();

    int dist = 0;
    int mindist = 10000;
    int closest = 5;
    for (int n = 0; n < drawableChannels.size(); n++) // select closest instead of relying on eventComponent
    {
        drawableChannels[n].channel->deselect();

        int cpos = (drawableChannels[n].channel->getY() + (drawableChannels[n].channel->getHeight()/2));
        dist = int(abs(y - cpos));

//        std::cout << "Mouse down at " << y << " pos is "<< cpos << " n: " << n << "  dist " << dist << std::endl;

        if (dist < mindist)
        {
            mindist = dist-1;
            closest = n;
        }
    }

    drawableChannels[closest].channel->select();
    options->setSelectedType(drawableChannels[closest].channel->getType());

    if (event.mods.isRightButtonDown()) { // if right click
        PopupMenu channelMenu = channels[closest]->getOptions();
        const int result = channelMenu.show();
        drawableChannels[closest].channel->changeParameter(result);
    }
    else // if left click
    {
//    if (singleChan != -1)
        if (event.getNumberOfClicks() == 2) {
            toggleSingleChannel(closest);
        }
        
        if (getSingleChannelState())
        {
            
            //        std::cout << "singleChan = " << singleChan << " " << y << " " << drawableChannels[0].channel->getHeight() << " " << getRange() << std::endl;
            //channelInfo[singleChan]->updateXY(
            drawableChannels[0].channelInfo->updateXY(
                                                      float(x)/getWidth()*canvas->timebase,
                                                      (-(float(y)-viewport->getViewPositionY())/viewport->getViewHeight()*float(getRange()))+float(getRange()/2)
                                                      );
        }
    }

//    canvas->fullredraw = true;//issue full redraw

//    refresh();

}


bool LfpDisplay::setEventDisplayState(int ch, bool state)
{
    eventDisplayEnabled[ch] = state;
    return eventDisplayEnabled[ch];
}


bool LfpDisplay::getEventDisplayState(int ch)
{
    return eventDisplayEnabled[ch];
}


void LfpDisplay::setEnabledState(bool state, int chan, bool updateSaved)
{
    if (chan < numChans)
    {
        channels[chan]->setEnabledState(state);
        channelInfo[chan]->setEnabledState(state);

        if (updateSaved)
            savedChannelState.set(chan, state);

        canvas->isChannelEnabled.set(chan, state);
    }
}

bool LfpDisplay::getEnabledState(int chan)
{
    if (chan < numChans)
    {
        return channels[chan]->getEnabledState();
    }

    return false;
}



#pragma mark - LfpChannelDisplay -
// ------------------------------------------------------------------

LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, LfpDisplayOptions* o, int channelNumber)
    : canvas(c)
    , display(d)
    , options(o)
    , isSelected(false)
    , chan(channelNumber)
    , drawableChan(channelNumber)
    , channelOverlap(300)
    , channelHeight(40)
    , range(1000.0f)
    , isEnabled(true)
    , inputInverted(false)
    , canBeInverted(true)
    , drawMethod(false)
    , isHidden(false)
{


    name = String(channelNumber+1); // default is to make the channelNumber the name


    channelHeightFloat = (float) channelHeight;

    channelFont = Font("Default", channelHeight*0.6, Font::plain);

    lineColour = Colour(255,255,255);

    type = options->getChannelType(channelNumber);
    typeStr = options->getTypeName(type);

}

LfpChannelDisplay::~LfpChannelDisplay()
{

}

void LfpChannelDisplay::resized()
{
    // all of this will likely need to be moved into the sharedLfpDisplay image in the lfpDisplay, not here
    // now that the complete height is know, the sharedLfpDisplay image that we'll draw the pixel-wise lfp plot to needs to be resized
    //lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false);
    std::cout << "[channel " << chan << "] " << getPosition().toString() << std::endl;
}


void LfpChannelDisplay::updateType()
{
    type = options->getChannelType(chan);
    typeStr = options->getTypeName(type);
}

void LfpChannelDisplay::setEnabledState(bool state)
{

    //if (state)
    //std::cout << "Setting channel " << name << " to true." << std::endl;
    //else
    //std::cout << "Setting channel " << name << " to false." << std::endl;

    isEnabled = state;

}

void LfpChannelDisplay::setHidden(bool isHidden_)
{
    isHidden = isHidden_;
    isEnabled = !isHidden;
}

void LfpChannelDisplay::pxPaint()
{
    if (!isEnabled) return; // return early if THIS display is not enabled
    
    Image::BitmapData bdLfpChannelBitmap(display->lfpChannelBitmap, 0,0, display->lfpChannelBitmap.getWidth(), display->lfpChannelBitmap.getHeight());
    
    int center = getHeight()/2;
    
    // max and min of channel in absolute px coords for event displays etc - actual data might be drawn outside of this range
    int jfrom_wholechannel= (int) (getY()+center-channelHeight/2)+1 +0 ;
    int jto_wholechannel= (int) (getY()+center+channelHeight/2) -0;
    
    //int jfrom_wholechannel_almost= (int) (getY()+center-channelHeight/3)+1 +0 ; // a bit less tall, for saturation warnings
    //int jto_wholechannel_almost= (int) (getY()+center+channelHeight/3) -0;
    
    // max and min of channel, this is the range where actual data is drawn
    int jfrom_wholechannel_clip= (int) (getY()+center-(channelHeight)*canvas->channelOverlapFactor)+1  ;
    int jto_wholechannel_clip  = (int) (getY()+center+(channelHeight)*canvas->channelOverlapFactor) -0;
    
    if (jfrom_wholechannel<0) {jfrom_wholechannel=0;};
    if (jto_wholechannel >= display->lfpChannelBitmap.getHeight()) {jto_wholechannel=display->lfpChannelBitmap.getHeight()-1;};
    
    // draw most recent drawn sample position
    if (canvas->screenBufferIndex[chan]+1 <= display->lfpChannelBitmap.getWidth())
        for (int k=jfrom_wholechannel; k<=jto_wholechannel; k+=2) // draw line
            bdLfpChannelBitmap.setPixelColour(canvas->screenBufferIndex[chan]+1,k, Colours::yellow);
    
    
    bool clipWarningHi =false; // keep track if something clipped in the display, so we can draw warnings after the data pixels are done
    bool clipWarningLo =false;
    
    bool saturateWarningHi =false; // similar, but for saturating the amplifier, not just the display - make this warning very visible
    bool saturateWarningLo =false;
    
    // pre compute some colors for later so we dont do it once per pixel.
    Colour lineColourBright = lineColour.withMultipliedBrightness(2.0f);
    //Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f).withMultipliedBrightness(0.3f);
    Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f*canvas->histogramParameterB).withMultipliedBrightness(canvas->histogramParameterB);
    
    
    int stepSize = 1;
    int from = 0; // for vertical line drawing in the LFP data
    int to = 0;
    
    int ifrom = canvas->lastScreenBufferIndex[chan] - 1; // need to start drawing a bit before the actual redraw window for the interpolated line to join correctly
    
    if (ifrom < 0)
        ifrom = 0;
    
    int ito = canvas->screenBufferIndex[chan] +0;
    
    if (fullredraw)
    {
        ifrom = 0; //canvas->leftmargin;
        ito = getWidth()-stepSize;
        fullredraw = false;
    }
    
    bool drawWithOffsetCorrection = display->getMedianOffsetPlotting();
    
    LfpBitmapPlotterInfo plotterInfo; // hold and pass plotting info for each plotting method class
    
    
    for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion
    {
        if (i < display->lfpChannelBitmap.getWidth())
        {
            //draw zero line
            int m = getY()+center;
            
            if(m > 0 & m < display->lfpChannelBitmap.getHeight())
            {
                if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel
                    bdLfpChannelBitmap.setPixelColour(i,m,Colour(50,50,50));
                }
            }
            
            //draw range markers
            if (isSelected)
            {
                int start = getY()+center -channelHeight/2;
                int jump = channelHeight/4;
                
                for (m = start; m <= start + jump*4; m += jump)
                {
                    if (m > 0 & m < display->lfpChannelBitmap.getHeight())
                    {
                        if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) // make sure we're not drawing over an existing plot from another channel
                            bdLfpChannelBitmap.setPixelColour(i, m, Colour(80,80,80));
                    }
                }
            }
            
            // draw event markers
            int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events)
            
            for (int ev_ch = 0; ev_ch < 8 ; ev_ch++) // for all event channels
            {
                if (display->getEventDisplayState(ev_ch))  // check if plotting for this channel is enabled
                {
                    if (rawEventState & (1 << ev_ch))    // events are  representet by a bit code, so we have to extract the individual bits with a mask
                    {
//                        std::cout << "Drawing event." << std::endl;
                        Colour currentcolor=display->channelColours[ev_ch*2];
                        
                        for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++) // draw line
                            bdLfpChannelBitmap.setPixelColour(i,k,bdLfpChannelBitmap.getPixelColour(i,k).interpolatedWith(currentcolor,0.3f));
                        
                    }
                }
            }
            
            //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl;
            
            
            // set max-min range for plotting, used in all methods
            double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat);
            double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat);
            
            double mean = (canvas->getMean(chan)/range*channelHeightFloat);
            
            if (drawWithOffsetCorrection)
            {
                a -= mean;
                b -= mean;
            }
            
            double a_raw = canvas->getYCoordMax(chan, i);
            double b_raw = canvas->getYCoordMin(chan, i);
            double from_raw=0; double to_raw=0;
            
            //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2;
            if (a<b)
            {
                from = (a); to = (b);
                from_raw = (a_raw); to_raw = (b_raw);
                
            }
            else
            {
                from = (b); to = (a);
                from_raw = (b_raw); to_raw = (a_raw);
            }
            
            // start by clipping so that we're not populating pixels that we dont want to plot
            int lm= channelHeightFloat*canvas->channelOverlapFactor;
            if (lm>0)
                lm=-lm;
            
            if (from > -lm) {from = -lm; clipWarningHi=true;};
            if (to > -lm) {to = -lm; clipWarningHi=true;};
            if (from < lm) {from = lm; clipWarningLo=true;};
            if (to < lm) {to = lm; clipWarningLo=true;};
            
            
            // test if raw data is clipped for displaying saturation warning
            if (from_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;};
            if (to_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;};
            if (from_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;};
            if (to_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;};
            
            bool spikeFlag = display->getSpikeRasterPlotting()
                && !(saturateWarningHi || saturateWarningLo)
                && (from_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold()
                        || to_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold());
            
            from = from + getHeight()/2;       // so the plot is centered in the channeldisplay
            to = to + getHeight()/2;
            
            int samplerange = to - from;
            
            if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing
            { // histogram based supersampling method
                plotterInfo.channelID = chan;
                plotterInfo.samp = i;
                plotterInfo.y = getY();
                plotterInfo.from = from;
                plotterInfo.height = getHeight();
                plotterInfo.lineColourBright = lineColourBright;
                plotterInfo.lineColourDark = lineColourDark;
                plotterInfo.range = range;
                plotterInfo.channelHeightFloat = channelHeightFloat;
                plotterInfo.sampleCountPerPixel = canvas->getSampleCountPerPixel(i);
                plotterInfo.samplesPerPixel = canvas->getSamplesPerPixel(chan, i);
                plotterInfo.histogramParameterA = canvas->histogramParameterA;
                plotterInfo.samplerange = samplerange;
                
                // TODO: (kelly) complete transition toward plotter class encapsulation
//                display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo);
                
            }
            else //drawmethod
            { // simple per-pixel min-max drawing, has no anti-aliasing, but runs faster
                
                plotterInfo.channelID = chan;
                plotterInfo.y = getY();
                plotterInfo.from = from;
                plotterInfo.to = to;
                plotterInfo.samp = i;
                plotterInfo.lineColour = lineColour;
                
                // TODO: (kelly) complete transition toward plotter class encapsulation
//                display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); // plotterInfo is prepared above
                
            }
            
            // Do the actual plotting for the selected plotting method
            if (!display->getSpikeRasterPlotting())
                display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo);
            
                
                
                
            // now draw warnings, if needed
            if (canvas->drawClipWarning) // draw simple warning if display cuts off data
            {
                
                if(clipWarningHi) {
                    for (int j=0; j<=3; j++)
                    {
                        int clipmarker = jto_wholechannel_clip;
                        
                        if(clipmarker>0 & clipmarker<display->lfpChannelBitmap.getHeight()){
                            bdLfpChannelBitmap.setPixelColour(i,clipmarker-j,Colour(255,255,255));
                        }
                    }
                }
                
                if(clipWarningLo) {
                    for (int j=0; j<=3; j++)
                    {
                        int clipmarker = jfrom_wholechannel_clip;
                        
                        if(clipmarker>0 & clipmarker<display->lfpChannelBitmap.getHeight()){
                            bdLfpChannelBitmap.setPixelColour(i,clipmarker+j,Colour(255,255,255));
                        }
                    }
                }
                
                clipWarningHi=false;
                clipWarningLo=false;
            }
            
            if (spikeFlag) // draw spikes
            {
                for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line
                    if(k>0 & k<display->lfpChannelBitmap.getHeight()){
                        bdLfpChannelBitmap.setPixelColour(i,k,lineColour);
                    }
                };
            }
            
            
            if (canvas->drawSaturationWarning) // draw bigger warning if actual data gets cuts off
            {
                
                if(saturateWarningHi || saturateWarningLo) {
                    
                    
                    for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line
                        Colour thiscolour=Colour(255,0,0);
                        if (fmod((i+k),50)>25){
                            thiscolour=Colour(255,255,255);
                        }
                        if(k>0 & k<display->lfpChannelBitmap.getHeight()){
                            bdLfpChannelBitmap.setPixelColour(i,k,thiscolour);
                        }
                    };
                }
                
                saturateWarningHi=false; // we likely just need one of this because for this warning we dont care if its saturating on the positive or negative side
                saturateWarningLo=false;
            }
        } // if i < getWidth()
        
    } // for i (x pixels)

}

void LfpChannelDisplay::paint(Graphics& g) {}



PopupMenu LfpChannelDisplay::getOptions()
{

    PopupMenu menu;
    menu.addItem(1, "Invert signal", true, inputInverted);

    return menu;
}

void LfpChannelDisplay::changeParameter(int id)
{
    switch (id)
    {
        case 1:
            setInputInverted(!inputInverted);
        default:
            break;
    }
}

void LfpChannelDisplay::setRange(float r)
{
    
    range = r;

    //std::cout << "Range: " << r << std::endl;
}

int LfpChannelDisplay::getRange()
{
    return range;
}


void LfpChannelDisplay::select()
{
    isSelected = true;
}

void LfpChannelDisplay::deselect()
{
    isSelected = false;
}

bool LfpChannelDisplay::getSelected()
{
   return isSelected;
}

void LfpChannelDisplay::setColour(Colour c)
{
    lineColour = c;
}


void LfpChannelDisplay::setChannelHeight(int c)
{
    channelHeight = c;

    channelHeightFloat = (float) channelHeight;

    if (!inputInverted)
        channelHeightFloat = -channelHeightFloat;

    channelOverlap = channelHeight*2;
}

int LfpChannelDisplay::getChannelHeight()
{

    return channelHeight;
}

void LfpChannelDisplay::setChannelOverlap(int overlap)
{
    channelOverlap = overlap;
}


int LfpChannelDisplay::getChannelOverlap()
{
    return channelOverlap;
}

int LfpChannelDisplay::getChannelNumber()
{
    return chan;
}

int LfpChannelDisplay::getDrawableChannelNumber()
{
    return drawableChan;
}

void LfpChannelDisplay::setDrawableChannelNumber(int channelId)
{
    drawableChan = channelId;
}

void LfpChannelDisplay::setCanBeInverted(bool _canBeInverted)
{
    canBeInverted = _canBeInverted;
}

void LfpChannelDisplay::setInputInverted(bool isInverted)
{
    if (canBeInverted)
    {
        inputInverted = isInverted;
        setChannelHeight(channelHeight);
    }
}

void LfpChannelDisplay::setDrawMethod(bool isDrawMethod)
{

    drawMethod = isDrawMethod;

}


void LfpChannelDisplay::setName(String name_)
{
    name = name_;
}

DataChannel::DataChannelTypes LfpChannelDisplay::getType()
{
    return type;
}



#pragma mark - LfpChannelDisplayInfo -
// -------------------------------

LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, LfpDisplayOptions* options_, int ch)
    : LfpChannelDisplay(canvas_, display_, options_, ch)
{

    chan = ch;
    x = -1.0f;
    y = -1.0f;

//    enableButton = new UtilityButton(String(ch+1), Font("Small Text", 13, Font::plain));
    enableButton = new UtilityButton("*", Font("Small Text", 13, Font::plain));
    enableButton->setRadius(5.0f);

    enableButton->setEnabledState(true);
    enableButton->setCorners(true, true, true, true);
    enableButton->addListener(this);
    enableButton->setClickingTogglesState(true);
    enableButton->setToggleState(true, dontSendNotification);
    
    isSingleChannel = false;

    addAndMakeVisible(enableButton);

}

void LfpChannelDisplayInfo::updateType()
{
    type = options->getChannelType(chan);
    typeStr = options->getTypeName(type);
    repaint();
}

void LfpChannelDisplayInfo::buttonClicked(Button* button)
{

    bool state = button->getToggleState();

    display->setEnabledState(state, chan);

    //UtilityButton* b = (UtilityButton*) button;

    // if (state)
    // {
    //  b->setLabel("ON");
    // } else {
    //  b->setLabel("OFF");
    // }

    //std::cout << "Turn channel " << chan << " to " << button->getToggleState() << std::endl;

}

void LfpChannelDisplayInfo::setEnabledState(bool state)
{
    enableButton->setToggleState(state, sendNotification);
}

void LfpChannelDisplayInfo::setSingleChannelState(bool state)
{
    isSingleChannel = state;
}

int LfpChannelDisplayInfo::getChannelSampleRate()
{
    return samplerate;
}

void LfpChannelDisplayInfo::setChannelSampleRate(int samplerate_)
{
    samplerate = samplerate_;
}

void LfpChannelDisplayInfo::mouseDrag(const MouseEvent &e)
{
    if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold
    {
        if (e.mods.isCommandDown() && !display->getSingleChannelState())  // CTRL + drag -> change channel spacing
        {
            
            // init state in our track zooming info struct
            if (!display->trackZoomInfo.isScrollingY)
            {
                auto & zoomInfo = display->trackZoomInfo;
                
                zoomInfo.isScrollingY = true;
                zoomInfo.componentStartHeight = getChannelHeight();
                zoomInfo.zoomPivotRatioY = (getY() + e.getMouseDownY())/(float)display->getHeight();
                zoomInfo.zoomPivotRatioX = (getX() + e.getMouseDownX())/(float)display->getWidth();
                zoomInfo.zoomPivotViewportOffset = getPosition() + e.getMouseDownPosition() - canvas->viewport->getViewPosition();
                
                zoomInfo.unpauseOnScrollEnd = !display->isPaused;
                if (!display->isPaused) display->options->togglePauseButton(true);
            }
            
            int h = display->trackZoomInfo.componentStartHeight;
            int hdiff=0;
            int dragDeltaY = -0.1 * (e.getScreenPosition().getY() - e.getMouseDownScreenY()); // invert so drag up -> scale up
            
//             std::cout << dragDeltaY << std::endl;
            if (dragDeltaY > 0)
            {
                hdiff = 2 * dragDeltaY;
            }
            else
            {
                if (h > 5)
                    hdiff = 2 * dragDeltaY;
            }
            
            if (abs(h) > 100) // accelerate scrolling for large ranges
                hdiff *= 3;
            
            int newHeight = h+hdiff;
            
            // constrain the spread resizing to max and min values;
            if (newHeight < display->trackZoomInfo.minZoomHeight)
            {
                newHeight = display->trackZoomInfo.minZoomHeight;
            }
            else if (newHeight > display->trackZoomInfo.maxZoomHeight)
            {
                newHeight = display->trackZoomInfo.maxZoomHeight;
            }
            
            // return early if there is nothing to update
            if (newHeight == getChannelHeight())
            {
                return;
            }
            
            // set channel heights for all channel
//            display->setChannelHeight(newHeight);
            for (int i = 0; i < display->getNumChannels(); ++i)
            {
                display->channels[i]->setChannelHeight(newHeight);
                display->channelInfo[i]->setChannelHeight(newHeight);
            }
            
            options->setSpreadSelection(newHeight, false, true); // update combobox
            
            canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw
            
            display->setBounds(0,0,display->getWidth()-0, display->getChannelHeight()*display->drawableChannels.size()); // update height so that the scrollbar is correct
            
            int newViewportY = display->trackZoomInfo.zoomPivotRatioY * display->getHeight() - display->trackZoomInfo.zoomPivotViewportOffset.getY();
            if (newViewportY < 0) newViewportY = 0; // make sure we don't adjust beyond the edge of the actual view
            
            canvas->viewport->setViewPosition(0, newViewportY);
        }
    }
}

void LfpChannelDisplayInfo::mouseUp(const MouseEvent &e)
{
    if (e.mods.isLeftButtonDown() && display->trackZoomInfo.isScrollingY)
    {
        display->trackZoomInfo.isScrollingY = false;
        if (display->trackZoomInfo.unpauseOnScrollEnd)
        {
            display->isPaused = false;
            display->options->togglePauseButton(false);
        }
    }
}

void LfpChannelDisplayInfo::paint(Graphics& g)
{

    int center = getHeight()/2 - (isSingleChannel?(75):(0));

//    g.setColour(lineColour);
    //if (chan > 98)
    //  g.fillRoundedRectangle(5,center-8,51,22,8.0f);
    //else
    
//    g.fillRoundedRectangle(5,center-8,41,22,8.0f);
    
    // Draw the channel numbers
    g.setColour(Colours::grey);
    const String channelString = (isChannelNumberHidden() ? ("--") : String(getChannelNumber() + 1));
    bool isCentered = !getEnabledButtonVisibility();
    
    g.drawText(channelString,
               2,
               center-4,
               isCentered ? (getWidth()/2-4) : (getWidth()/4),
               10,
               isCentered ? Justification::centred : Justification::centredRight,
               false);
    
    g.setColour(lineColour);
    g.fillRect(0, 0, 2, getHeight());
    
    if (getChannelTypeStringVisibility())
    {
        g.setFont(Font("Small Text", 13, Font::plain));
        g.drawText(typeStr,5,center+10,41,10,Justification::centred,false);
    }
    // g.setFont(channelHeightFloat*0.3);
    g.setFont(Font("Small Text", 11, Font::plain));

    if (isSingleChannel)
    {
        g.setColour(Colours::darkgrey);
        g.drawText("STD:", 5, center+90,41,10,Justification::centred,false);
        g.drawText("MEAN:", 5, center+40,41,10,Justification::centred,false);
        
        if (x > 0)
        {
            g.drawText("uV:", 5, center+140,41,10,Justification::centred,false);
        }
        //g.drawText("Y:", 5, center+200,41,10,Justification::centred,false);

        g.setColour(Colours::grey);
        g.drawText(String(canvas->getStd(chan)), 5, center+110,41,10,Justification::centred,false);
        g.drawText(String(canvas->getMean(chan)), 5, center+60,41,10,Justification::centred,false);
        if (x > 0)
        {
            //g.drawText(String(x), 5, center+150,41,10,Justification::centred,false);
            g.drawText(String(y), 5, center+160,41,10,Justification::centred,false);
        }
        
    }

    //  g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false);

}

void LfpChannelDisplayInfo::updateXY(float x_, float y_)
{
    x = x_;
    y = y_;
}

void LfpChannelDisplayInfo::resized()
{

    int center = getHeight()/2 - (isSingleChannel?(75):(0));

    //if (chan > 98)
    //  enableButton->setBounds(8,center-5,45,16);
    //else
//    enableButton->setBounds(8,center-5,35,16);
    
    setEnabledButtonVisibility(getHeight() >= 16);
    
    if (getEnabledButtonVisibility())
    {
        enableButton->setBounds(getWidth()/4 + 5, (center) - 7, 15, 15);
    }
    
    setChannelNumberIsHidden(getHeight() < 16 && (getDrawableChannelNumber() + 1) % 10 != 0);
    
    setChannelTypeStringVisibility(getHeight() > 34);
}

void LfpChannelDisplayInfo::setEnabledButtonVisibility(bool shouldBeVisible)
{
    if (shouldBeVisible)
    {
        addAndMakeVisible(enableButton);
    }
    else if (enableButton->isVisible())
    {
        removeChildComponent(enableButton);
        enableButton->setVisible(false);
    }
    
}

bool LfpChannelDisplayInfo::getEnabledButtonVisibility()
{
    return enableButton->isVisible();
}

void LfpChannelDisplayInfo::setChannelTypeStringVisibility(bool shouldBeVisible)
{
    channelTypeStringIsVisible = shouldBeVisible;
}

bool LfpChannelDisplayInfo::getChannelTypeStringVisibility()
{
    return channelTypeStringIsVisible || isSingleChannel;
}

void LfpChannelDisplayInfo::setChannelNumberIsHidden(bool shouldBeHidden)
{
    channelNumberHidden = shouldBeHidden;
}

bool LfpChannelDisplayInfo::isChannelNumberHidden()
{
    return channelNumberHidden;
}




#pragma mark - EventDisplayInterface -
// Event display Options --------------------------------------------------------------------

EventDisplayInterface::EventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum):
    isEnabled(true), display(display_), canvas(canvas_)
{

    channelNumber = chNum;

    chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain));
    chButton->setRadius(5.0f);
    chButton->setBounds(4,4,14,14);
    chButton->setEnabledState(true);
    chButton->setCorners(true, false, true, false);
    //chButton.color = display->channelColours[channelNumber*2];
    chButton->addListener(this);
    addAndMakeVisible(chButton);


    checkEnabledState();

}

EventDisplayInterface::~EventDisplayInterface()
{

}

void EventDisplayInterface::checkEnabledState()
{
    isEnabled = display->getEventDisplayState(channelNumber);

    //repaint();
}

void EventDisplayInterface::buttonClicked(Button* button)
{
    checkEnabledState();
    if (isEnabled)
    {
        display->setEventDisplayState(channelNumber, false);
    }
    else
    {
        display->setEventDisplayState(channelNumber, true);
    }

    repaint();

}


void EventDisplayInterface::paint(Graphics& g)
{

    checkEnabledState();

    if (isEnabled)
    {
        g.setColour(display->channelColours[channelNumber*2]);
        g.fillRoundedRectangle(2,2,18,18,6.0f);
    }


    //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false);

}



#pragma mark - LfpViewport -
// Lfp Viewport -------------------------------------------

LfpViewport::LfpViewport(LfpDisplayCanvas *canvas)
    : Viewport()
{
    this->canvas = canvas;
}

void LfpViewport::visibleAreaChanged(const Rectangle<int>& newVisibleArea)
{
    canvas->fullredraw = true;
    canvas->refresh();
}



#pragma mark - PerPixelBitmapPlotter -

PerPixelBitmapPlotter::PerPixelBitmapPlotter(LfpDisplay * lfpDisplay)
    : LfpBitmapPlotter(lfpDisplay)
{ }

void PerPixelBitmapPlotter::plot(Image::BitmapData &bitmapData, LfpBitmapPlotterInfo &pInfo)
{
    int jfrom = pInfo.from + pInfo.y;
    int jto = pInfo.to + pInfo.y;
    
    //if (yofs<0) {yofs=0;};
    
    if (pInfo.samp < 0) {pInfo.samp = 0;};
    if (pInfo.samp >= display->lfpChannelBitmap.getWidth()) {pInfo.samp = display->lfpChannelBitmap.getWidth()-1;}; // this shouldnt happen, there must be some bug above - to replicate, run at max refresh rate where draws overlap the right margin by a lot
    
    if (jfrom<0) {jfrom=0;};
    if (jto >= display->lfpChannelBitmap.getHeight()) {jto=display->lfpChannelBitmap.getHeight()-1;};
    
    
    for (int j = jfrom; j <= jto; j += 1)
    {
        
        //uint8* const pu8Pixel = bdSharedLfpDisplay.getPixelPointer(	(int)(i),(int)(j));
        //*(pu8Pixel)		= 200;
        //*(pu8Pixel+1)	= 200;
        //*(pu8Pixel+2)	= 200;
        
        bitmapData.setPixelColour(pInfo.samp,j,pInfo.lineColour);
        
    }
}



#pragma mark - LfpSupersampledBitmapPlotter -

SupersampledBitmapPlotter::SupersampledBitmapPlotter(LfpDisplay * lfpDisplay)
    : LfpBitmapPlotter(lfpDisplay)
{ }

void SupersampledBitmapPlotter::plot(Image::BitmapData &bdLfpChannelBitmap, LfpBitmapPlotterInfo &pInfo)
{
    std::array<float, MAX_N_SAMP_PER_PIXEL> samplesThisPixel = pInfo.samplesPerPixel;
//    int sampleCountThisPixel = lfpDisplay->canvas->getSampleCountPerPixel(pInfo.samp);
    int sampleCountThisPixel = pInfo.sampleCountPerPixel;
    
    if (pInfo.samplerange>0 & sampleCountThisPixel>0)
    {
        
        //float localHist[samplerange]; // simple histogram
        Array<float> rangeHist; // [samplerange]; // paired range histogram, same as plotting at higher res. and subsampling
        
        for (int k = 0; k <= pInfo.samplerange; k++)
            rangeHist.add(0);
        
        for (int k = 0; k <= sampleCountThisPixel; k++) // add up paired-range histogram per pixel - for each pair fill intermediate with uniform distr.
        {
            int cs_this = (((samplesThisPixel[k]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from); // sample values -> pixel coordinates relative to from
            int cs_next = (((samplesThisPixel[k+1]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from);
            
            
            if (cs_this<0) {cs_this=0;};                        //here we could clip the diaplay to the max/min, or ignore out of bound values, not sure which one is better
            if (cs_this>pInfo.samplerange) {cs_this=pInfo.samplerange;};
            if (cs_next<0) {cs_next=0;};
            if (cs_next>pInfo.samplerange) {cs_next=pInfo.samplerange;};
            
            int hfrom=0;
            int hto=0;
            
            if (cs_this<cs_next)
            {
                hfrom = (cs_this);  hto = (cs_next);
            }
            else
            {
                hfrom = (cs_next);  hto = (cs_this);
            }
            //float hrange=hto-hfrom;
            float ha=1;
            for (int l=hfrom; l<hto; l++)
            {
                rangeHist.set(l, rangeHist[l] + ha); //this emphasizes fast Y components
                
                //rangeHist[l]+=1/hrange; // this is like an oscilloscope, same energy depositetd per dx, not dy
            }
        }
        
        
        for (int s = 0; s <= pInfo.samplerange; s ++)  // plot histogram one pixel per bin
        {
            float a=15*((rangeHist[s])/(sampleCountThisPixel)) * (2*(0.2+pInfo.histogramParameterA));
            if (a>1.0f) {a=1.0f;};
            if (a<0.0f) {a=0.0f;};
            
            
            //Colour gradedColor = lineColour.withMultipliedBrightness(2.0f).interpolatedWith(lineColour.withMultipliedSaturation(0.6f).withMultipliedBrightness(0.3f),1-a) ;
            Colour gradedColor =  pInfo.lineColourBright.interpolatedWith(pInfo.lineColourDark,1-a);
            //Colour gradedColor =  Colour(0,255,0);
            
            int ploty = pInfo.from + s + pInfo.y;
            if(ploty>0 & ploty < display->lfpChannelBitmap.getHeight()) {
                bdLfpChannelBitmap.setPixelColour(pInfo.samp, pInfo.from + s + pInfo.y, gradedColor);
            }
        }
        
    } else {
        
        int ploty = pInfo.from + pInfo.y;
        if(ploty>0 && ploty < display->lfpChannelBitmap.getHeight()) {
            bdLfpChannelBitmap.setPixelColour(pInfo.samp, ploty, pInfo.lineColour);
        }
    }
}



#pragma mark - LfpChannelColourScheme -

int LfpChannelColourScheme::colourGrouping = 1;

void LfpChannelColourScheme::setColourGrouping(int grouping)
{
    colourGrouping = grouping;
}

int LfpChannelColourScheme::getColourGrouping()
{
    return colourGrouping;
}



#pragma mark - LfpDefaultColourScheme -

Array<Colour> LfpDefaultColourScheme::colourList = []() -> Array<Colour> {
    Array<Colour> colours;
    colours.add(Colour(224,185,36));
    colours.add(Colour(214,210,182));
    colours.add(Colour(243,119,33));
    colours.add(Colour(186,157,168));
    colours.add(Colour(237,37,36));
    colours.add(Colour(179,122,79));
    colours.add(Colour(217,46,171));
    colours.add(Colour(217, 139,196));
    colours.add(Colour(101,31,255));
    colours.add(Colour(141,111,181));
    colours.add(Colour(48,117,255));
    colours.add(Colour(184,198,224));
    colours.add(Colour(116,227,156));
    colours.add(Colour(150,158,155));
    colours.add(Colour(82,173,0));
    colours.add(Colour(125,99,32));
    return colours;
}();

LfpDefaultColourScheme::LfpDefaultColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas)
    : LfpDisplayNodeAlpha::LfpChannelColourScheme(LfpDefaultColourScheme::colourList.size(), display, canvas)
{
    setName("Default");
}

void LfpDefaultColourScheme::paint(Graphics &g)
{
    
}

void LfpDefaultColourScheme::resized()
{
    
}

const Colour LfpDefaultColourScheme::getColourForIndex(int index) const
{
//    return colourList[index % colourList.size()];
    return colourList[(int(index/colourGrouping)) % colourList.size()];
}



#pragma mark - LfpMonochromaticColorScheme

LfpMonochromaticColourScheme::LfpMonochromaticColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas)
    : LfpChannelColourScheme (8, display, canvas)
    , isBlackAndWhite (false)
    , colourPattern (DOWN_UP)
{
    setName("Monochromatic");
    
    numChannelsLabel = new Label("numChannelsLabel", "Num Color Steps");
    numChannelsLabel->setFont(Font("Default", 13.0f, Font::plain));
    numChannelsLabel->setColour(Label::textColourId, Colour(100, 100, 100));
    addAndMakeVisible(numChannelsLabel);
    
    StringArray numChannelsSelectionOptions = {"4", "8", "16"};
    numChannelsSelection = new ComboBox("numChannelsSelection");
    numChannelsSelection->addItemList(numChannelsSelectionOptions, 1);
    numChannelsSelection->setEditableText(true);
    numChannelsSelection->addListener(this);
    numChannelsSelection->setSelectedId(2, dontSendNotification);
    addAndMakeVisible(numChannelsSelection);
    
    
    baseHueLabel = new Label("baseHue", "Hue");
    baseHueLabel->setFont(Font("Default", 13.0f, Font::plain));
    baseHueLabel->setColour(Label::textColourId, Colour(100, 100, 100));
    addAndMakeVisible(baseHueLabel);
    
    baseHueSlider = new Slider;
    baseHueSlider->setRange(0, 1);
    baseHueSlider->setValue(0);
    baseHueSlider->setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    baseHueSlider->addListener(this);
    addAndMakeVisible(baseHueSlider);
    
    baseHueSlider->addMouseListener(this, true);
    
    
    colourPatternLabel = new Label("colourPatternLabel", "Pattern");
    colourPatternLabel->setFont(Font("Default", 13.0f, Font::plain));
    colourPatternLabel->setColour(Label::textColourId, Colour(100, 100, 100));
    addAndMakeVisible(colourPatternLabel);
    
    StringArray colourPatternSelectionOptions = {"Down", "Up", "Down-Up", "Up-Down"};
    colourPatternSelection = new ComboBox("colourPatternSelection");
    colourPatternSelection->addItemList(colourPatternSelectionOptions, 1);
    colourPatternSelection->setEditableText(false);
    colourPatternSelection->addListener(this);
    colourPatternSelection->setSelectedId(colourPattern + 1, dontSendNotification);
    addAndMakeVisible(colourPatternSelection);
    
    baseHue = Colour::fromHSV(0, 1, 1, 1);
    swatchHue = baseHue;
    
    calculateColourSeriesFromBaseHue();
}

void LfpMonochromaticColourScheme::paint(Graphics &g)
{
    g.setColour(swatchHue);
    g.fillRect(colourSwatchRect);
}

void LfpMonochromaticColourScheme::resized()
{
    numChannelsLabel->setBounds(0, 5, 120, 25);
    numChannelsSelection->setBounds(numChannelsLabel->getRight(),
                                    numChannelsLabel->getY(),
                                    60,
                                    25);
    
    baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25);
    baseHueSlider->setBounds(baseHueLabel->getRight(),
                             baseHueLabel->getY(),
                             numChannelsSelection->getRight() - baseHueLabel->getRight() - 20,
                             25);
    
    colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10);
    
    colourPatternLabel->setBounds(0, baseHueLabel->getBottom(), 80, 25);
    colourPatternSelection->setBounds(colourPatternLabel->getRight(),
                                      colourPatternLabel->getY(),
                                      numChannelsSelection->getRight() - colourPatternLabel->getRight(),
                                      25);
    
}

void LfpMonochromaticColourScheme::sliderValueChanged(Slider *sl)
{
    swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1);
    repaint(colourSwatchRect);
}

void LfpMonochromaticColourScheme::comboBoxChanged(ComboBox *cb)
{
    if (cb == numChannelsSelection)
    {
        int numChannelsColourSpread = 0;
        if (cb->getSelectedId())
        {
            numChannelsColourSpread = cb->getText().getIntValue();
        }
        else
        {
            numChannelsColourSpread = cb->getText().getIntValue();
            if (numChannelsColourSpread < 1) numChannelsColourSpread = 1;
            else if (numChannelsColourSpread > 16) numChannelsColourSpread = 16;
            
            cb->setText(String(numChannelsColourSpread), dontSendNotification);
        }
        
        setNumColourSeriesSteps(numChannelsColourSpread);
    }
    else if (cb == colourPatternSelection)
    {
        setColourPattern((ColourPattern)(cb->getSelectedId() - 1));
    }
    calculateColourSeriesFromBaseHue();
    lfpDisplay->setColors();
//    canvas->fullredraw = true;
    canvas->redraw();
}

void LfpMonochromaticColourScheme::mouseUp(const MouseEvent &e)
{
    if (swatchHue.getARGB() != baseHue.getARGB())
    {
        baseHue = swatchHue;
        calculateColourSeriesFromBaseHue();
        lfpDisplay->setColors();
        canvas->redraw();
    }
}

void LfpMonochromaticColourScheme::setBaseHue(Colour base)
{
    baseHue = base;
    calculateColourSeriesFromBaseHue();
}

const Colour LfpMonochromaticColourScheme::getBaseHue() const
{
    return baseHue;
}

void LfpMonochromaticColourScheme::setNumColourSeriesSteps(int numSteps)
{
    numColourChannels = numSteps;
}

int LfpMonochromaticColourScheme::getNumColourSeriesSteps()
{
    return numColourChannels;
}

const Colour LfpMonochromaticColourScheme::getColourForIndex(int index) const
{
    int colourIdx = (int(index/colourGrouping) % numColourChannels);
    
    // adjust for oscillating patterns
    if (colourPattern == DOWN_UP || colourPattern == UP_DOWN)
    {
        int mid = numColourChannels / 2;
        if (colourIdx > mid)
        {
            if (numColourChannels % 2 == 0)
                colourIdx = numColourChannels - colourIdx;
            else
                colourIdx = (numColourChannels - colourIdx) * 2 - 1;
        }
        else if (numColourChannels % 2 != 0)
        {
            colourIdx *= 2;
        }
    }
    
    // invert if the pattern is UP or UP_DOWN
    if (colourPattern == UP)
        colourIdx = (numColourChannels - 1) - colourIdx;
    else if (colourPattern == UP_DOWN)
        colourIdx = (colourList.size() - 1) - colourIdx;
    
    return colourList[colourIdx];
}

void LfpMonochromaticColourScheme::calculateColourSeriesFromBaseHue()
{
    colourList.clear();
    
    int coloursToCalculate = numColourChannels;
    
    if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN))
    {
        coloursToCalculate = coloursToCalculate / 2 + 1;
    }
    
    for (int i = 0; i < coloursToCalculate; ++i)
    {
        float saturation = 1 - (i / float(coloursToCalculate + 1));
        colourList.add(baseHue.withMultipliedSaturation(saturation));
    }
}



#pragma mark - LfpGradientColourScheme

LfpGradientColourScheme::LfpGradientColourScheme(LfpDisplay * display, LfpDisplayCanvas * canvas)
    : LfpMonochromaticColourScheme(display, canvas)
{
    setName("Gradient");
    
    baseHueLabel->setName("baseHueA");
    baseHueLabel->setText("Hue A", dontSendNotification);
    
    baseHueLabelB = new Label("baseHueB", "Hue B");
    baseHueLabelB->setFont(Font("Default", 13.0f, Font::plain));
    baseHueLabelB->setColour(Label::textColourId, Colour(100, 100, 100));
    addAndMakeVisible(baseHueLabelB);
    
    baseHueSliderB = new Slider;
    baseHueSliderB->setRange(0, 1);
    baseHueSliderB->setValue(0.5);
    baseHueSliderB->setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    baseHueSliderB->addListener(this);
    addAndMakeVisible(baseHueSliderB);
    
    baseHueSliderB->addMouseListener(this, true);
    
    baseHueB = Colour::fromHSV(0.5, 1.0, 1.0, 1.0);
    swatchHueB = baseHueB;
    
    calculateColourSeriesFromBaseHue();
}

void LfpGradientColourScheme::paint(Graphics &g)
{
    g.setColour(swatchHue);
    g.fillRect(colourSwatchRect);
    
    g.setColour(swatchHueB);
    g.fillRect(colourSwatchRectB);
}

void LfpGradientColourScheme::resized()
{
    numChannelsLabel->setBounds(0, 5, 120, 25);
    numChannelsSelection->setBounds(numChannelsLabel->getRight(),
                                    numChannelsLabel->getY(),
                                    60,
                                    25);
    
    baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25);
    baseHueSlider->setBounds(baseHueLabel->getRight(),
                             baseHueLabel->getY(),
                             numChannelsSelection->getRight() - baseHueLabel->getRight() - 20,
                             25);
    
    colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10);
    
    baseHueLabelB->setBounds(0, baseHueLabel->getBottom(), 35, 25);
    baseHueSliderB->setBounds(baseHueLabelB->getRight(),
                             baseHueLabelB->getY(),
                             numChannelsSelection->getRight() - baseHueLabelB->getRight() - 20,
                             25);
    
    colourSwatchRectB.setBounds(baseHueSliderB->getRight() + 5, baseHueSliderB->getY() + 5, 15, baseHueSliderB->getHeight() - 10);
    
    colourPatternLabel->setBounds(0, baseHueLabelB->getBottom(), 80, 25);
    colourPatternSelection->setBounds(colourPatternLabel->getRight(),
                                      colourPatternLabel->getY(),
                                      numChannelsSelection->getRight() - colourPatternLabel->getRight(),
                                      25);
}

void LfpGradientColourScheme::sliderValueChanged(Slider *sl)
{
    if (sl == baseHueSlider)
    {
        swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1);
        repaint(colourSwatchRect);
    }
    else
    {
        swatchHueB = Colour::fromHSV(sl->getValue(), 1, 1, 1);
        repaint(colourSwatchRectB);
    }
}

void LfpGradientColourScheme::mouseUp(const MouseEvent &e)
{
    if (e.originalComponent == baseHueSlider)
    {
        if (swatchHue.getARGB() != baseHue.getARGB())
        {
            baseHue = swatchHue;
            calculateColourSeriesFromBaseHue();
            lfpDisplay->setColors();
            canvas->redraw();
        }
    }
    else
    {
        if (swatchHueB.getARGB() != baseHueB.getARGB())
        {
            baseHueB = swatchHueB;
            calculateColourSeriesFromBaseHue();
            lfpDisplay->setColors();
            canvas->redraw();
        }
    }
}

void LfpGradientColourScheme::calculateColourSeriesFromBaseHue()
{
    colourList.clear();
    
    int coloursToCalculate = numColourChannels;
    
    if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN))
    {
        coloursToCalculate = coloursToCalculate / 2 + 1;
    }
    
    for (int i = 0; i < coloursToCalculate; ++i)
    {
        float hue = (baseHueB.getHue() - baseHue.getHue()) * i / float(coloursToCalculate - 1);
        colourList.add(baseHue.withRotatedHue(hue));
    }
}