#include "CabbageTable.h"
#include "CabbageUtils.h"



/*
  ====================================================================================
	Cabbage Envelope Handle
  ====================================================================================
*/
CabbageEnvelopeHandleComponent::CabbageEnvelopeHandleComponent(int _index, Colour colour, bool fixed)
    :index(_index), x(0), y(0), colour(colour), fixed(fixed)
{
    getProperties().set(String("curveType"), LINEAR);
    this->setInterceptsMouseClicks(true, false);
}

CabbageEnvelopeHandleComponent::~CabbageEnvelopeHandleComponent()
{
}

void CabbageEnvelopeHandleComponent::paint (Graphics& g)
{
    g.setColour (colour);
    if(!fixed)
        g.fillEllipse (1, 1, getWidth()-2, getHeight()-2);
    else
        g.fillRect(1, 1, getWidth()-2, getHeight()-2);

}

Table* CabbageEnvelopeHandleComponent::getParentComponent()
{
    return (Table*)Component::getParentComponent();
}

void CabbageEnvelopeHandleComponent::removeThisHandle()
{
    getParentComponent()->removeHandle (this);
}

void CabbageEnvelopeHandleComponent::mouseEnter (const MouseEvent& e)
{
    setMouseCursor (MouseCursor::DraggingHandCursor);
}

void CabbageEnvelopeHandleComponent::mouseUp (const MouseEvent& e)
{
    changeMessage = "updateOnMouseUp";
    sendChangeMessage();
}

void CabbageEnvelopeHandleComponent::mouseDown (const MouseEvent& e)
{

    x = getX();
    y = getY();

    setMouseCursor (MouseCursor::DraggingHandCursor);

    dragger.startDraggingComponent (this, e);

    if ((e.mods.isShiftDown() == true) && (e.mods.isRightButtonDown() == true))
        removeThisHandle();

    PopupMenu pop, subm;
    pop.setLookAndFeel(&this->getTopLevelComponent()->getLookAndFeel());
    subm.setLookAndFeel(&this->getTopLevelComponent()->getLookAndFeel());
    if(e.mods.isRightButtonDown() == true)
    {
        //pop.addItem(1, "Linear");
        //subm.addItem(2, "Convex");
        //subm.addItem(3, "Concave");
        //pop.addSubMenu("Expon", subm);
        pop.addItem(4, "Delete");

        int result;
#if !defined(AndroidBuild)		
		result = pop.show();
#endif
        if(result==1)
            getProperties().set(String("curveType"), LINEAR);
        else if(result==2)
            getProperties().set(String("curveType"), CONVEX);
        else if(result==3)
            getProperties().set(String("curveType"), CONCAVE);
        else if(result==4)
        {
            changeMessage = "removeHandle";
            sendChangeMessage();
        }
    }
}

CabbageEnvelopeHandleComponent* CabbageEnvelopeHandleComponent::getPreviousHandle()
{
    return getParentComponent()->getPreviousHandle(this);
}

CabbageEnvelopeHandleComponent* CabbageEnvelopeHandleComponent::getNextHandle()
{
    return getParentComponent()->getNextHandle(this);
}


void CabbageEnvelopeHandleComponent::mouseDrag (const MouseEvent& e)
{
    CabbageEnvelopeHandleComponent* previousHandle = getPreviousHandle();
    CabbageEnvelopeHandleComponent* nextHandle = getNextHandle();
    bool fixed = this->getProperties().getWithDefault("fixedPos", false);


    int leftLimit = previousHandle == 0 ? 0 : previousHandle->getX()+1;
    int rightLimit = nextHandle == 0 ? getParentWidth()-previousHandle->getHeight() : nextHandle->getX()-1;
    int topLimit = previousHandle == 0 ? 0 : previousHandle->getX()+1;
    int bottomLimit = nextHandle == 0 ? getParentWidth()-previousHandle->getHeight() : nextHandle->getX()-1;
    int dragX = x+e.getDistanceFromDragStartX();
    int dragY = y+e.getDistanceFromDragStartY();

    //dragger.dragComponent(this, e, &resizeLimits);
    if(dragX < leftLimit)
        dragX = leftLimit;
    if(dragX > rightLimit)
        dragX = rightLimit;
    if(dragY< 0)
        dragY = 0;
    if(dragY > height-5)
        dragY = height-5;
    if(fixed)
        dragX = x;

    this->setTopLeftPosition(dragX, dragY);

    getParentComponent()->repaint();
}


/*
  ====================================================================================
	Table class
  ====================================================================================
*/
Table::Table (String chan, int tblNumber, int tableSize, bool fixedEnv, bool drawHorizontal, bool drawOriginal, Colour colour) :
    tableSize(tableSize),
    zoom(1.0),
    tableNumber(tblNumber),
    channel(chan),
    activeColour(colour),
    draggingHandle (0),
    scrubberPosition(0),
    isCurrentlyOnTop(false),
    drawOriginalTableData(drawOriginal),
    drawHorizontalSegments(drawHorizontal),
    fixedEnvelope(fixedEnv),
    handleWidth(8),
    drawFill(false),
    isResizing(true),
    useAmpRanges(false),
    editMode(false)
{

}

Table::~Table()
{
    handles.clear();
}
//====================================================
void Table::resized()
{
    //We need to make room for the h scrollbar, therefore table data
    //can't use the full height of the canvas
    tableTop = getHeight()*0.01;
    tableBottom = getHeight()*0.99;
    tableHeight = tableBottom - tableTop;
}

//====================================================
void Table::setOriginalWidth(int w)
{
    //Sets the original width from when the table was initialised
    origWidth = w;
    maxZoomForOverview = tableSize / origWidth;
}

//====================================================
void Table::setGlobalAmpRange(float globalMaxAmp, float globalMinAmp, float globalAmpRange)
{
    minAmp = globalMinAmp;
    maxAmp = globalMaxAmp;
    ampRange = globalAmpRange;
}

//====================================================
void Table::createAmpOverviews (Array<float, CriticalSection> csndInputData)
{
    //This method creates smaller amp overview arrays using the
    //original table data...
    tableData.amps.clear();
    tableData.y.clear();
    overview.maxAmps.clear();
    overview.minAmps.clear();

    tableData.amps = csndInputData;

    //Logger::writeToLog(String(tableData.amps.size()));
    zeroAmpPosition = convertAmpToPixel(0);

    // Filling overview arrays. The original table data is broken into
    //separate blocks. The max and min values are then stored from each block.
    //Block size is dependent on the max zoom level we use for overviews.
    float incr = (float)tableSize / (getWidth()*maxZoomForOverview);
    float maxBlockValue, minBlockValue;
    for (float i=0; i<tableSize; i+=incr)
    {
        maxBlockValue = minBlockValue = tableData.amps[(int)i+0.5];
        for (int j=0; j<(int)incr+0.5; j++)   //loop for current block
        {
            if (tableData.amps[(int)i+j+0.5] > maxBlockValue)
                maxBlockValue = tableData.amps[(int)i+j+0.5];
            if (tableData.amps[(int)i+j+0.5] < minBlockValue)
                minBlockValue = tableData.amps[(int)i+j+0.5];
        }
        overview.maxAmps.add (maxBlockValue);
        overview.minAmps.add (minBlockValue);
    }
    setDataSource (zoom);
    //if(drawOriginalTableData==false)
    //createEnvPath();
    //makeTableEditable();
}

//====================================================
void Table::setDataSource (int zoomValue)
{
    //If current zoom <= max zoom for overview, then the
    //overview arrays are used and converted to y coordinates.
    //Otherwise the initial table data is used.
    if (zoomValue <= maxZoomForOverview)
    {
        int incrSize = maxZoomForOverview/zoomValue;
        float maxBlockValue, minBlockValue;

        overview.maxY.clear();
        overview.minY.clear();
        for (int i=0; i<overview.maxAmps.size(); i+=incrSize)
        {
            maxBlockValue = overview.maxAmps[i];
            minBlockValue = overview.minAmps[i];
            for (int j=0; j<incrSize; j++)   //loop for current block
            {
                if (overview.maxAmps[i+j] > maxBlockValue)
                    maxBlockValue = overview.maxAmps[i+j];
                if (overview.minAmps[i+j]  < minBlockValue)
                    minBlockValue = overview.minAmps[i+j];
            }

            overview.maxY.add (convertAmpToPixel(maxBlockValue));
            overview.minY.add (convertAmpToPixel(minBlockValue));
            useOverview = true;
        }
    }
    // Else we use original table data for painting
    else
    {
        //numPixelsPerIndex = (zoomValue*origWidth) / (float)tableSize;
        numPixelsPerIndex = (float)getWidth() / (float)tableSize;
        useOverview = false;
    }

    repaint();
}

//====================================================
float Table::convertAmpToPixel (float ampValue)
{
    float normalisedAmp = (ampValue-minAmp) / ampRange; //first change to normalised value

    if(this->toggleMaxMin)
    {
        if(ampValue == maxAmp)
            return 0;
        else if(ampValue==minAmp)
            return getHeight();
        else
        {
            return jmax(0.f, ((1-normalisedAmp) * tableHeight) + tableTop);
        }
    }
    else
        return jmax(0.f, ((1-normalisedAmp) * tableHeight) + tableTop);
}

//====================================================
float Table::convertPixelToAmp(float pixelYValue)
{
//scale values between 0 and 1
    double yAmp = (float)(this->getHeight()-(pixelYValue))/(float)this->getHeight();
//now bring them in range
    yAmp = (yAmp*ampRange)+minAmp;
    yAmp = (yAmp < minAmp ? minAmp : yAmp);
    yAmp = (yAmp > maxAmp ? maxAmp : yAmp);
    return yAmp;
}

//====================================================
void Table::cacheBackgroundImage()
{
    //This method creates the background image that the table data will be
    //painted on to.  It is then stored in cache.
    img = Image(Image::ARGB, origWidth, getHeight(), true);
    Graphics g (img);

    ImageCache::addImageToCache (img, 15);
}

//====================================================
void Table::paint (Graphics& g)
{

    //Image bg = ImageCache::getFromHashCode(15);
    //g.drawImage (bg, viewX, 0, origWidth, getHeight(),
    //	0, 0, bg.getWidth(), bg.getHeight(), false);
    if (isCurrentlyOnTop)
        currColour = activeColour.withMultipliedSaturation(3);
    else
        currColour = activeColour;
    int startIndx, endIndx;

    // If using overview...
    if (useOverview && drawOriginalTableData)
    {
        startIndx = (viewX/getWidth()) * overview.maxY.size();
        endIndx = startIndx + viewWidth;
        float bottomYPixelValue, topYPixelValue;
        bottomYPixelValue = topYPixelValue = 0;

        // For loop which will draw a vertical line between the min and max value
        //for each x pixel
        int xPixelValue = 0;
        for (int i=startIndx; i<endIndx; i++)
        {
            //We need to make sure that the current min value is not greater than the
            //next max value. Otherwise there would be a gap in the wave form...
            if (overview.minY[i] < overview.maxY[i+1])
                bottomYPixelValue = overview.maxY[i+1];
            else
                bottomYPixelValue = overview.minY[i];
            if (overview.maxY[i] > overview.minY[i+1])
                topYPixelValue = overview.minY[i+1];
            else
                topYPixelValue = overview.maxY[i];

            float minGap = 0.4;
            float diff = bottomYPixelValue - topYPixelValue;
            if (diff < minGap)
            {
                bottomYPixelValue += (minGap-diff)/2;
                topYPixelValue -= (minGap-diff)/2;
            }

            if (CabbageUtils::isNumber(topYPixelValue) && CabbageUtils::isNumber(bottomYPixelValue))
            {
                g.setColour(currColour);
                topYPixelValue -= minWaveHeight/2;
                bottomYPixelValue += minWaveHeight/2;
                g.drawVerticalLine (xPixelValue+viewX, topYPixelValue, bottomYPixelValue);
                xPixelValue += 1;

                // Fill
                if (tableSize <= 4096)
                {
                    g.setColour(currColour.withAlpha(0.1f));
                    if (bottomYPixelValue < zeroAmpPosition)
                        g.drawVerticalLine (xPixelValue+viewX, bottomYPixelValue, zeroAmpPosition);
                    else if (bottomYPixelValue > zeroAmpPosition)
                        g.drawVerticalLine (xPixelValue+viewX, zeroAmpPosition, bottomYPixelValue);

                    if (isCurrentlyOnTop)
                        currColour = activeColour.withMultipliedSaturation(3);
                    else
                        currColour = activeColour;
                }
            }
        }
    }

    //Else if using original array values for painting...
    else if (!useOverview && drawOriginalTableData)
    {
        g.setColour(currColour);
        startIndx = ((viewX/getWidth()) * tableData.amps.size()) + 0.5; //0.5 for rounding
        endIndx = (startIndx + (viewWidth/numPixelsPerIndex))+0.5;
        float prevX = viewX;
        float prevY = convertAmpToPixel (tableData.amps[startIndx]);
        float currY;
        for (int i=startIndx+1; i<=endIndx; i++)
        {
            currY = convertAmpToPixel (tableData.amps[i]);
            if(drawHorizontalSegments==true)
            {
                g.setColour(currColour);
                g.drawLine(prevX, prevY, prevX+numPixelsPerIndex, prevY, minWaveHeight);
                g.setColour(currColour.darker(.7));
                if(drawFill==true)
                    g.fillRect(prevX, (prevY>convertAmpToPixel(0) ? prevY-minWaveHeight : prevY+minWaveHeight), numPixelsPerIndex, convertAmpToPixel(0)-prevY);
            }
            else
            {
                g.drawLine(prevX, prevY, prevX+numPixelsPerIndex, currY, minWaveHeight);
                // For drawing index markers
                if (numPixelsPerIndex > 4)
                    g.drawVerticalLine (prevX+numPixelsPerIndex, currY-3, currY+3);

            }
            prevX = prevX + numPixelsPerIndex;
            prevY = currY;
        }
    }

//	g.setColour(Colours::lightblue);
//	envPath.letoleToFit (0, tableTop, getWidth(), tableHeight, false);
//	g.strokePath (envPath, PathStrokeType(2.0f));
    //----- For handles....

    else if(!drawOriginalTableData)
    {
        envPath.clear();

        for(int i = 0; i < handles.size(); i++)
        {
            CabbageEnvelopeHandleComponent* handle = handles.getUnchecked(i);
            int prevX, prevY;
            if((int)handle->getProperties().getWithDefault("curveType", 1)==0)
            {
                //for linear envelopes
                if(i==0)
                {
                    envPath.startNewSubPath((handle->getX() + handle->getRight()) / 2,
                                            (handle->getY() + handle->getBottom()) / 2);
                    if(toggleMaxMin)
                        g.setColour(currColour);
                    else
                        g.setColour(currColour.darker(.9f));

                    if(drawHorizontalSegments==true && fixedEnvelope == true)
                        if(drawFill==true && !toggleMaxMin)
                            g.fillRect(0, jmax(handle->getY(), 0),  jmax(handle->getWidth(), 0), jmax(0, getHeight()-1));
                        else if(drawFill==true && toggleMaxMin)
                        {
                            //g.setColour(Colours::red);
                            g.fillRect(0, jmax(handle->getY(), 0),  jmax(handle->getWidth(), 0), jmax(0, getHeight()-1));
                            //g.drawRect(0, jmax(handle->getY(), 0),  jmax(handle->getWidth(), 0), jmax(0, getHeight()-1), 2);
                            g.setColour(currColour);
                        }

                    prevX = handle->getX();
                    prevY = handle->getY();
                }
                else
                {
                    if(drawFill==true)
                        g.fillRect(jmax(handle->getX(), 0), jmax(handle->getY(), 0), jmax(handle->getWidth()-1, 0), jmax(0, getHeight()-1));

                    if(toggleMaxMin)
                        g.setColour(currColour);
                    else
                        g.setColour(currColour.darker(.9f));

                    if(toggleMaxMin)
                    {
                        g.drawRect(jmax(handle->getX(), 0), jmax(handle->getY(), 0), jmax(handle->getWidth()-1, 0), jmax(0, getHeight()-1), 2);
                        g.setColour(currColour);
                    }

                    if(drawHorizontalSegments==true && fixedEnvelope == false)
                    {
                        envPath.lineTo((handle->getX() + handle->getRight()) / 2, (prevY+(handle->getHeight()/2)));
                        envPath.lineTo((handle->getX() + handle->getRight()) / 2, (handle->getY() + handle->getBottom()) / 2);
                    }
                    else if(drawHorizontalSegments==true && fixedEnvelope==true)
                    {
                        envPath.lineTo((handle->getX()+1), (prevY+(handle->getHeight()/2)));
                        envPath.lineTo((handle->getX()+1),  (handle->getY() + handle->getBottom()) / 2);
                    }
                    else
                    {
                        envPath.lineTo((handle->getX() + handle->getRight()) / 2,
                                       (handle->getY() + handle->getBottom()) / 2);
                    }
                    prevY = handle->getY();
                    prevX = handle->getX();
                }
            }
            else
            {
                //for bezier envelopes
                if(i==0)
                {
                    envPath.startNewSubPath((handle->getX() + handle->getRight()) / 2,
                                            (handle->getY() + handle->getBottom()) / 2);
                }
                else
                {
                    int curX = handle->getX();
                    int curY = handle->getY();
                    int prevX = envPath.getCurrentPosition().getX();
                    int prevY = envPath.getCurrentPosition().getY();
                    //int controlPoint1
                    if(curY>prevY)
                    {
                        if((int)handle->getProperties().getWithDefault("curveType", 0)==CONVEX)
                            envPath.quadraticTo(prevX+(curX/3), curY,
                                                (curX + handle->getRight()) / 2,
                                                (curY + handle->getBottom()) / 2);
                        else if((int)handle->getProperties().getWithDefault("curveType", 0)==CONCAVE)
                            envPath.quadraticTo(curX-(curX/3), prevY,
                                                (curX + handle->getRight()) / 2,
                                                (curY + handle->getBottom()) / 2);
                    }
                    else if(curY<prevY)
                        if((int)handle->getProperties().getWithDefault("curveType", 0)==CONVEX)
                            envPath.quadraticTo(curX-(curX/3.f), prevY,
                                                (curX + handle->getRight()) / 2,
                                                (curY + handle->getBottom()) / 2);
                        else if((int)handle->getProperties().getWithDefault("curveType", 0)==CONCAVE)
                            envPath.quadraticTo(prevX+(prevX/3.f), curY,
                                                (curX + handle->getRight()) / 2,
                                                (curY + handle->getBottom()) / 2);
                }
            }

            //String coordinate;
            //coordinate = String(handle->getX()) + String(", ") + String(handle->getY());
            //g.setColour(Colours::lime);
            //g.drawFittedText(coordinate, handle->getX(), handle->getY(), 50, 20, Justification::centred, 1);

            //envPath.lineTo(getWidth(), getHeight());
            //envPath.lineTo(0, getHeight());
            //envPath.closeSubPath();
            //g.setColour(currColour.darker(.7f));
            //if(toggleMaxMin)
            //	int test;
            //else if((drawHorizontalSegments==true && fixedEnvelope == false))
            //g.fillPath(envPath);
            //else
            if(!drawFill)
                g.strokePath (envPath, PathStrokeType(2.0f));
        }
    }

    //if current table is on top, place text on widget to indicate so..
    /*
    if(onTop){
    String text = "ftable:"+String(tableNumber);
    g.setColour(currColour);
    g.setFont(CabbageUtils::getComponentFont());
    int length = CabbageUtils::getComponentFont().getStringWidth(text)+10;
    g.drawText(text, getWidth()-length,
    					   2,
    					   length, 12,
    					   Justification::centred, 1);
    }*/

    //draw the crubber if needed
    if(scrubberPosition>0)
    {
        g.setColour(currColour);
        scrubberPosition = scrubberPosition*getWidth();
        g.drawLine(scrubberPosition, 0, scrubberPosition, getHeight(), 2);
    }
}

//====================================================
void Table::mouseDown (const MouseEvent& e)
{

    if(editMode==true)
        if(fixedEnvelope==false)
            if(e.mods.isShiftDown() == true)
                draggingHandle = addHandle (e.x, e.y, false, handleWidth, activeColour.contrasting(.5));

}

//====================================================
void Table::applyZoom (int zoomInput)
{
    zoom = zoomInput;
    setDataSource(zoom);
    this->repaint();
}

//====================================================
void Table::setToEnabled(bool isEnabled)
{
    isCurrentlyOnTop = isEnabled;

    if (isCurrentlyOnTop) //if on top
        minWaveHeight = 3;
    else
        minWaveHeight = 1.5;
}

//====================================================
void Table::setViewStart(float x)
{
    viewX = x;
}

//====================================================
void Table::setViewWidth(float width)
{
    viewWidth = width;
}

//====================================================
void Table::modifyHandlePos (float j)
{
    //This function changes the x position of each envelope handle
    //after a zoom in or out. j will either be 2 or 0.5.
    for (int i=0; i<handles.size(); i++)
    {
        CabbageEnvelopeHandleComponent* handle = handles.getUnchecked (i);
        handle->setBounds (handle->getX() * j, handle->getY(), HANDLESIZE, HANDLESIZE);
    }
}

//====================================================
void Table::createHandlesFromTable(int points)
{
    handles.clear();
    Colour col;
    editMode=true;
    int x;
    float end = getWidth();
    float scaleX = .99;

    if(points>tableSize)
    {
        points=tableSize;
        fixedEnvelope = true;
    }

    float segmentIncr = (float)tableSize/(float)points;
    if(fixedEnvelope && drawHorizontalSegments)
    {
        handleWidth = getWidth()/tableSize;
        col = activeColour;
        scaleX = 1;
    }
    else
        col = activeColour.contrasting(.5);

    addHandle(0, convertAmpToPixel(tableData.amps[0]), true, handleWidth-2, col);

    for(int i=segmentIncr; i<tableSize; i+=segmentIncr)
    {
        int x = int((float(i)/(float)tableSize)*getWidth()*scaleX);
        addHandle(x, convertAmpToPixel(tableData.amps[i]), (i==0 || i==tableSize, fixedEnvelope), handleWidth, col);
    }
    addHandle(end, convertAmpToPixel(tableData.amps[0]), true, handleWidth, col);
}
//====================================================
void Table::toggleMinMaxAmp(int x)
{
    for(int i=0; i<handles.size(); i++)
        if((x> handles[i]->getX()) && (x<handles[i]->getRight()))
        {
            if(handles[i]->getPosition().getY()>1)
                handles[i]->setTopLeftPosition((i==0 ? handles[i]->getX() : handles[i]->getX()), 0);
            else
                handles[i]->setTopLeftPosition((i==0 ? handles[i]->getX() : handles[i]->getX()), getHeight());

            repaint();
        }
}
//====================================================
void Table::setYValueOfHandle(int x, int y)
{
    for(int i=0; i<handles.size(); i++)
        if((x>= handles[i]->getX()) && (x<=handles[i]->getRight()))
        {
            handles[i]->setTopLeftPosition(handles[i]->getX(), y);
            repaint();
        }
}

//====================================================
// delete handles if user selects delete from handle popup menu
//====================================================
void Table::changeListenerCallback(juce::ChangeBroadcaster *source)
{
    CabbageEnvelopeHandleComponent* hand = dynamic_cast <CabbageEnvelopeHandleComponent*> (source);
    if(hand)
    {
        //PathFlatteningIterator flatpath(envPath);
        Logger::writeToLog("Table Length in Pixels:"+String(getWidth()));
        Logger::writeToLog("Table Length in samples:"+String(tableSize));


        if(hand->changeMessage=="removeHandle")
        {
            removeHandle(hand);
            sendChangeMessage();
        }

        else if(hand->changeMessage=="updateOnMouseUp")
        {
            sendChangeMessage();
        }
    }
    //change messages being sent from this component
    else
        sendChangeMessage();
}

CabbageEnvelopeHandleComponent* Table::addHandle(int x, int y, bool fixedPos, int width, Colour col)
{
    int i;
    for (i=0; i<handles.size(); i++)
    {
        CabbageEnvelopeHandleComponent* handle = handles.getUnchecked (i);
        if (handle->getX() > x)
            break;
    }

    CabbageEnvelopeHandleComponent* handle = new CabbageEnvelopeHandleComponent(handles.size(), col, fixedEnvelope);
    handle->addChangeListener(this);
    handle->getProperties().set("fixedPos", fixedPos);
    handle->height = getHeight();
    handle->width = getWidth();
    //if(this->toggleMaxMin)
    //handle->setBounds (x, y, width, 3);
    //else
    handle->setBounds (x, y, width, HANDLESIZE);
    addAndMakeVisible (handle);
    handles.insert (i, handle);
    repaint();
    return handle;
}

int Table::getHandleIndex(CabbageEnvelopeHandleComponent* thisHandle)
{
    return handles.indexOf(thisHandle);
}

CabbageEnvelopeHandleComponent* Table::getPreviousHandle(CabbageEnvelopeHandleComponent* thisHandle)
{
    int thisHandleIndex = handles.indexOf(thisHandle);

    if(thisHandleIndex <= 0)
        return 0;
    else
        return handles.getUnchecked(thisHandleIndex-1);
}

CabbageEnvelopeHandleComponent* Table::getNextHandle(CabbageEnvelopeHandleComponent* thisHandle)
{
    int thisHandleIndex = handles.indexOf(thisHandle);

    if(thisHandleIndex == -1 || thisHandleIndex >= handles.size()-1)
        return 0;
    else
        return handles.getUnchecked(thisHandleIndex+1);
}

void Table::removeHandle (CabbageEnvelopeHandleComponent* thisHandle)
{
    if (handles.size() > 0)
    {
        handles.removeObject(thisHandle, true);
        repaint();
    }
}


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

	Table Manager class

  ====================================================================================
*/
CabbageTableManager::CabbageTableManager(int tableSize)
    : alpha(1.0f), zoom(1), maxZoom(1), maxNumPixelsPerIndex(1),
      globalMaxAmp(0.f), globalMinAmp(0.f), tableSize(tableSize), readOnly(false)
{
}

CabbageTableManager::~CabbageTableManager()
{
    tables.clear();
}

void CabbageTableManager::resized()
{
    //We need to make room for the h scrollbar, therefore table data
    //can't use the full height of the canvas
    tableTop = getHeight()*0.15;
    tableBottom = getHeight()*0.85;
    tableHeight = tableBottom - tableTop;
}

void CabbageTableManager::setOriginalWidth(float width)
{
    originalWidth = width;
}

Table* CabbageTableManager::getTable(int index)
{
    if(index > 0 && index < tables.size());
    return tables[index];
}


void CabbageTableManager::paint(Graphics& g)
{
    g.setColour(CabbageUtils::getDarkerBackgroundSkin());
    g.fillAll();
    //this needs to be updated so that we can stables on top of each other
    if(toggleMode)
    {
        float incr = (float)getWidth()/(float)tableSize;
        g.setColour (Colours::whitesmoke.withAlpha(.5f));
        for(float i=0; i<getWidth()+1; i+=(incr))
            g.drawLine((i==0 ? 0 : i-1), 0, (i==0 ? 0 : i-1), getHeight()-.1, .5);

        int rowHeight = getHeight()/(getNumberOfTables());

        for(int i=0; i<getNumberOfTables()+1; i++)
            g.drawLine(0, i*rowHeight, getWidth(), i*rowHeight, .5);
    }
    // Amp horizontal markers...
    else
    {
        if(getHeight()>40)
        {
            g.setColour (Colour::fromRGBA (220, 220, 240, 255));
            g.drawLine (0, convertAmpToPixel(globalMaxAmp), getWidth(), convertAmpToPixel(globalMaxAmp), 0.1);
            g.drawLine (0, convertAmpToPixel(globalMinAmp), getWidth(), convertAmpToPixel(globalMinAmp), 0.1);
            g.drawLine (0, convertAmpToPixel(globalMaxAmp*0.5), getWidth(), convertAmpToPixel(globalMaxAmp*0.5), 0.1);
            g.drawLine (0, convertAmpToPixel(globalMinAmp*0.5), getWidth(), convertAmpToPixel(globalMinAmp*0.5), 0.1);
            if (globalMinAmp < 0)
                g.drawLine (0, convertAmpToPixel(0), getWidth(), convertAmpToPixel(0), 0.3);
        }
        else
        {
            //draw vertical markers
            /*
            float incr = (float)getWidth()/(float)tableSize;
            g.setColour (Colour::fromRGBA (220, 220, 240, 255));
            for(int i=0;i<getWidth();i+=(incr))
            	g.drawLine(i, 0, i, getHeight()-.1, 0.1);
            g.drawLine (0, getHeight()-.1, getWidth(), getHeight()-.1, 0.1);
            g.drawLine (0, 0, getWidth(), 0, 0.1);
            */
        }

    }
    // update tables
    for (int i=0; i<tables.size(); ++i)
        tables[i]->setViewStart(0);


}

float CabbageTableManager::convertAmpToPixel (float ampValue)
{
    // This method converts amps to y pixel values
    float normalisedAmp = (ampValue-globalMinAmp) / globalAmpRange; //first change to normalised value
    return jmax(0.f, ((1-normalisedAmp) * tableHeight) + tableTop);
}

void CabbageTableManager::addTable (String name,
                                    String channel,
                                    int tableNumber,
                                    int tableSize,
                                    bool fixedEnv,
                                    bool drawHoriz,
                                    bool drawOrig,
                                    bool toggleMaxMin,
                                    bool drawFill,
                                    int resizeMode,
                                    Point<float> minMax,
                                    Colour colour,
                                    bool readonly,
                                    bool stackMode,
                                    int numberOfTables,
                                    ChangeListener* listener)
{
    int i = tables.size();
    tables.add (new Table(channel, tableNumber, tableSize, fixedEnv, drawHoriz, drawOrig, colour));
    if(stackMode==true)
        tables[i]->setBounds (0, i*(getHeight()/(numberOfTables)), getWidth(), (getHeight()/(numberOfTables)));
    else
        tables[i]->setBounds (0, 0, getWidth(), getHeight());

    tables[i]->addChangeListener(listener);
    tables[i]->setOriginalWidth (getWidth());
    tables[i]->setViewWidth(getWidth());
    tables[i]->toggleMaxMin = toggleMaxMin;
    toggleMode = toggleMaxMin;
    tables[i]->drawFill = drawFill;
    readOnly = readonly;
    addAndMakeVisible (tables[i]);
    tables[i]->addMouseListener(this, false); //"this" will now also get mouse events for tables[i]
    tableToTop(i); //setting this table to the top


    Logger::writeToLog("MinRange"+String(minMax.getX()));
    Logger::writeToLog("MaxRange"+String(minMax.getY()));

    if(resizeMode==1)
        tables[i]->isResizing = true;

    if(minMax.getX()!=minMax.getY())
        tables[i]->useAmpRanges=true;

    tables[i]->minMaxAmps = minMax;

    //the max possible zoom is based on the table with the most indices. It will have a max zoom
    //of 50 pixels for every index.
    if ((tableSize*50) > maxNumPixelsPerIndex)
    {
        maxNumPixelsPerIndex = (tableSize*50);
        maxZoom = sqrt(maxNumPixelsPerIndex);
    }
}

void CabbageTableManager::tableToTop (int tableIndex)
{
    jassert(tables[tableIndex]);
    tables[tableIndex]->toFront(true);

    Logger::writeToLog("Table on top is"+String(tables[tableIndex]->tableNumber));

    for(int i=0; i<tables.size(); i++)
    {
        tables[i]->setToEnabled(false); //disabling the previously active table
        tables[i]->setAlpha(0.7);
    }

    if (tables.size() > 1)
    {
        tables.swap(tableIndex, tables.size()-1); //swapping currently active table to end of array
    }

    tables.getLast()->setToEnabled(true);
    tables.getLast()->setAlpha(1.0);

}

void CabbageTableManager::fillTable (int tableIndex, Array<float, CriticalSection> csndInputData)
{
    if(isPositiveAndBelow(tableIndex, tables.size()))
    {
        float currTableMaxAmp, currTableMinAmp;
        if(tables[tableIndex]->isResizing==0)
            globalMaxAmp = globalMinAmp = 0;

        // Getting the min and max amplitude values....
        currTableMinAmp = currTableMaxAmp = csndInputData.getFirst();
        for (int i=0; i<csndInputData.size(); ++i)
        {
            //Logger::writeToLog(String(csndInputData[i]));
            if (csndInputData[i] > currTableMaxAmp)
                currTableMaxAmp = csndInputData[i];
            if (csndInputData[i] < currTableMinAmp)
                currTableMinAmp = csndInputData[i];
        }


        //if min and max amps are the same value....
        if (currTableMinAmp == currTableMaxAmp)
        {
            if (currTableMinAmp > 0)
                currTableMinAmp = 0;
            else if (currTableMinAmp < 0)
            {
                currTableMaxAmp = 0;
            }
            else   //else if both are 0
            {
                currTableMinAmp = -1;
                currTableMaxAmp = 1;
            }
        }

        if (currTableMaxAmp > globalMaxAmp)
            globalMaxAmp = currTableMaxAmp;
        if (currTableMinAmp < globalMinAmp)
            globalMinAmp = currTableMinAmp;

        if(tables[tableIndex]->useAmpRanges)
        {
            globalMinAmp = tables[tableIndex]->minMaxAmps.getX();
            globalMaxAmp = tables[tableIndex]->minMaxAmps.getY();
        }

        globalAmpRange = globalMaxAmp - globalMinAmp;

        for (int i=0; i<tables.size(); ++i)
            tables[i]->setGlobalAmpRange(globalMaxAmp, globalMinAmp, globalAmpRange);

        tables[tableIndex]->createAmpOverviews((csndInputData));

        if(tables[tableIndex]->drawHorizontalSegments &&
                tables[tableIndex]->fixedEnvelope &&
                tables[tableIndex]->toggleMaxMin)
        {
            tables[tableIndex]->createHandlesFromTable(16);
            tables[tableIndex]->setInterceptsMouseClicks(false,false);
        }

    }
    //Logger::writeToLog("MaxAmp="+String(globalMaxAmp));
    //Logger::writeToLog("MinAmp="+String(globalMinAmp));

}

void CabbageTableManager::mouseDrag(const MouseEvent& e)
{
//if in toggle mode, draw on mouse move
    if(e.mods.isLeftButtonDown())
        for(int i=0; i<tables.size(); i++)
        {
            if(tables[i]->toggleMaxMin)
            {
                if(tables[i]->getBounds().contains(Point<int>(e.getPosition().getX(), e.getPosition().getY())))
                {
                    tables[i]->toggleMinMaxAmp(e.getPosition().getX());
                    tables[i]->changeMessage = "updateFunctionTable";
                    tables[i]->sendChangeMessage();
                }
            }
        }
}

void CabbageTableManager::mouseMove(const MouseEvent& e)
{

}

void CabbageTableManager::mouseDown (const MouseEvent& e)
{
    if(e.mods.isPopupMenu())
    {
        PopupMenu pop, subMenu1, subMenu2;
        pop.setLookAndFeel(&getTopLevelComponent()->getLookAndFeel());
        subMenu1.setLookAndFeel(&getTopLevelComponent()->getLookAndFeel());
        subMenu2.setLookAndFeel(&getTopLevelComponent()->getLookAndFeel());

        subMenu1.addItem(101, "1 segment");
        subMenu1.addItem(102, "2 segments");
        subMenu1.addItem(104, "4 segments");
        subMenu1.addItem(108, "8 segments");
        subMenu1.addItem(112, "12 segments");
        subMenu1.addItem(116, "16 segments");
        subMenu1.addItem(120, "20 segments");
        subMenu1.addItem(124, "24 segments");
        subMenu1.addItem(128, "28 segments");
        subMenu1.addItem(132, "32 segments");



        for(int i=0; i<tables.size(); i++)
            subMenu2.addColouredItem(200+i, "fTable:"+String(tables[i]->tableNumber), tables[i]->currColour);

        if(!readOnly)
        {
            pop.addSubMenu(TRANS("Edit..."), subMenu1);
            pop.addSubMenu(TRANS("Table to front..."), subMenu2);
            pop.addItem(300, "Replace existing table");
            pop.addItem(301, "Add table to score");
        }


        int choice;
#if !defined(AndroidBuild)
		choice = pop.show();
#endif
        if((choice>=100) && (choice<200))
        {
            for(int i=0; i<tables.size(); i++)
            {
                if(tables[i]->isCurrentlyOnTop)
                {
                    //Logger::writeToLog("currentOnTop:"+String(tables[i]->tableNumber));
                    tables[i]->createHandlesFromTable(choice-100);
                    tables[i]->drawOriginalTableData = false;
                }
            }
        }
        else if(choice>=200 && choice<300)
        {
            this->tableToTop(choice-200);
        }
        else if(choice==300)
        {
            for(int i=0; i<tables.size(); i++)
            {
                if(tables[i]->isCurrentlyOnTop)
                {
                    tables[i]->changeMessage = "overwriteFunctionTable";
                    tables[i]->sendChangeMessage();
                }
            }
        }
        else if(choice==301)
        {
            for(int i=0; i<tables.size(); i++)
            {
                if(tables[i]->isCurrentlyOnTop)
                {
                    tables[i]->changeMessage = "writeNewFunctionTable";
                    tables[i]->sendChangeMessage();
                }
            }
        }
    }
    //if left button is pressed..
    else
    {
        for(int i=0; i<tables.size(); i++)
        {
            if(tables[i]->editMode)
                if(tables[i]->toggleMaxMin)
                {
                    if(tables[i]->getBounds().contains(Point<int>(e.getMouseDownX(), e.getMouseDownY())))
                    {
                        tables[i]->toggleMinMaxAmp(e.getMouseDownX());
                        tables[i]->changeMessage = "updateFunctionTable";
                        tables[i]->sendChangeMessage();
                    }
                }

                else if(tables[i]->isCurrentlyOnTop)
                {
                    tables[i]->toggleMinMaxAmp(e.getMouseDownX());
                    tables[i]->changeMessage = "updateFunctionTable";
                    tables[i]->sendChangeMessage();
                }
        }

    }

}