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

 This file is part of the Open Ephys GUI
 Copyright (C) 2014 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 "GraphViewer.h"

#define PI 3.14159265359

GraphViewer::GraphViewer()
{

    labelFont = Font("Paragraph", 50, Font::plain);
    rootNum = 0;
}

GraphViewer::~GraphViewer()
{

}

void GraphViewer::addNode(GenericEditor* editor)
{

    GraphNode* gn = new GraphNode(editor, this);
    addAndMakeVisible(gn);
    availableNodes.add(gn);
    gn->setBounds(20, 20, 150, 50);
    updateNodeLocations();

}


void GraphViewer::removeNode(GenericEditor* editor)
{

    availableNodes.remove(indexOfEditor(editor));

    updateNodeLocations();

}

void GraphViewer::removeAllNodes()
{
    availableNodes.clear();

    updateNodeLocations();

}

// void GenericEditor::updateNodeName(GenericEditor* editor)
// {
//     GraphNode* n = getNodeForEditor(editor);
//     n->repaint();
// }

void GraphViewer::updateNodeLocations()
{
    // set the initial locations
    for (int i = 0; i < availableNodes.size(); i++)
    {

    }

    rootNum = 0;

    // do initial layout
    for (int i = 0; i < availableNodes.size(); i++)
    {
        checkLayout(availableNodes[i]);
    }

    // check for overlap
    for (int i = 0; i < availableNodes.size(); i++)
    {
        for (int j = 0; j < availableNodes.size(); j++)
        {
            if (j != i)
            {
                if (availableNodes[j]->getLevel() == availableNodes[i]->getLevel() &&
                    availableNodes[j]->getHorzShift() == availableNodes[i]->getHorzShift())
                {
                    availableNodes[j]->setHorzShift(availableNodes[j]->getHorzShift()+1);
                }
            }
        }
    }

    repaint();
}

void GraphViewer::checkLayout(GraphNode* gn)
{

    if (gn != nullptr)
    {

        GraphNode* sourceNode;

        if (gn->isMerger())
        {
            Array<GenericEditor*> editors = gn->getConnectedEditors();

            int level1 = 0;
            int level2 = 0;

            if (editors[0] != nullptr)
            {
                level1 = getNodeForEditor(editors[0])->getLevel();
            }

            if (editors[1] != nullptr)
            {
                level2 = getNodeForEditor(editors[1])->getLevel();
            }

            // std::cout << "LEVEL1 = " << level1 << " LEVEL2 = " << level2 << std::endl;

            sourceNode = level1 > level2 ? getNodeForEditor(editors[0]) :
                         getNodeForEditor(editors[1]); // choose the higher source

        }
        else
        {
            sourceNode = getNodeForEditor(gn->getSource());
        }

        if (sourceNode == nullptr)
        {
            gn->setLevel(0);
            gn->setHorzShift(rootNum);
            rootNum++;
        }
        else if (sourceNode->isSplitter())
        {
            Array<GenericEditor*> editors = sourceNode->getConnectedEditors();

            if (gn->hasEditor(editors[1]))
            {
                gn->setLevel(sourceNode->getLevel()+1); // increase level
                gn->setHorzShift(sourceNode->getHorzShift()+1); // increase horz shift
            }
            else
            {
                gn->setLevel(sourceNode->getLevel()+1); // increase level
                gn->setHorzShift(sourceNode->getHorzShift()); // same horz shift
            }

        }
        else
        {

            gn->setLevel(sourceNode->getLevel()+1); // increase level
            gn->setHorzShift(sourceNode->getHorzShift()); // same horz shift
        }

        checkLayout(getNodeForEditor(gn->getDest()));

    }

}

int GraphViewer::indexOfEditor(GenericEditor* editor)
{
    int index = -1;

    for (int i = 0; i < availableNodes.size(); i++)
    {
        if (availableNodes[i]->hasEditor(editor))
        {
            return i;
        }
    }

    return index;
}

GraphNode* GraphViewer::getNodeForEditor(GenericEditor* editor)
{
    int index = indexOfEditor(editor);

    if (index > -1)
        return availableNodes[index];
    else
        return nullptr;
}

int GraphViewer::nodesAtLevel(int level)
{

    int numNodes;

    for (int i = 0; i < availableNodes.size(); i++)
    {
        if (availableNodes[i]->getLevel() == numNodes)
        {
            numNodes++;
        }
    }

    return numNodes;

}

int GraphViewer::getHorizontalShift(GraphNode* gn)
{
    int shift = 0;

    for (int i = 0; i < availableNodes.size(); i++)
    {
        if (availableNodes[i] == gn)
        {
            break;
        }
        else
        {
            if (availableNodes[i]->getLevel() == gn->getLevel())
            {
                shift++;
            }
        }
    }

    return shift;

}


void GraphViewer::paint(Graphics& g)
{
    g.fillAll(Colours::darkgrey);

    g.setFont(labelFont);
    g.setFont(50);

    g.setColour(Colours::grey);

    JUCEApplication* app = JUCEApplication::getInstance();
    String text = "GUI version ";
    text += app->getApplicationVersion();

    g.drawFittedText("open ephys", 40, 40, getWidth()-50, getHeight()-60, Justification::bottomRight, 100);

    g.setFont(Font("Small Text", 14, Font::plain));
    g.drawFittedText(text, 40, 40, getWidth()-50, getHeight()-45, Justification::bottomRight, 100);

    // draw connections

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

        if (!availableNodes[i]->isSplitter())
        {
            if (availableNodes[i]->getDest() != nullptr)
            {
                int indexOfDest = indexOfEditor(availableNodes[i]->getDest());

                if (indexOfDest > -1)
                    connectNodes(i, indexOfDest, g);
            }
        }
        else
        {

            Array<GenericEditor*> editors = availableNodes[i]->getConnectedEditors();

            for (int path = 0; path < 2; path++)
            {
                int indexOfDest = indexOfEditor(editors[path]);

                if (indexOfDest > -1)
                    connectNodes(i, indexOfDest, g);
            }


        }

    }
}

void GraphViewer::connectNodes(int node1, int node2, Graphics& g)
{

    Point<float> start = availableNodes[node1]->getCenterPoint();
    Point<float> end = availableNodes[node2]->getCenterPoint();

    Path linePath;
    float x1 = start.getX();
    float y1 = start.getY();
    float x2 = end.getX();
    float y2 = end.getY();
    linePath.startNewSubPath(x1, y1);
    linePath.cubicTo(x1, y1 + (y2 - y1) * 0.9f,
                     x2, y1 + (y2 - y1) * 0.1f,
                     x2, y2);

    PathStrokeType stroke(2.0f);
    g.strokePath(linePath, stroke);
}

/// ------------------------------------------------------

GraphNode::GraphNode(GenericEditor* ed, GraphViewer* g)
{
    editor = ed;
    mouseOver = false;
    labelFont = Font("Paragraph", 14, Font::plain);

    gv = g;
}

GraphNode::~GraphNode()
{

}

int GraphNode::getLevel()
{
    // int level = -1;

    // GenericEditor* ed = editor;

    // while (ed != nullptr)
    // {
    //     level += 1;
    //     ed = ed->getSourceEditor();
    // }

    return vertShift;
}

void GraphNode::setLevel(int level)
{
    setBounds(getX(), 20+level*40, getWidth(), getHeight());

    vertShift = level;
}

int GraphNode::getHorzShift()
{
    return horzShift; //gv->getHorizontalShift(this);
}

void GraphNode::setHorzShift(int shift)
{
    setBounds(20+shift*140, getY(), getWidth(), getHeight());
    horzShift = shift;
}

void GraphNode::mouseEnter(const MouseEvent& m)
{
    mouseOver = true;
    repaint();
}
void GraphNode::mouseExit(const MouseEvent& m)
{
    mouseOver = false;
    repaint();
}
void GraphNode::mouseDown(const MouseEvent& m)
{
    editor->makeVisible();
}

bool GraphNode::hasEditor(GenericEditor* ed)
{
    if (ed == editor)
        return true;
    else
        return false;
}

bool GraphNode::isSplitter()
{
    return editor->isSplitter();
}

bool GraphNode::isMerger()
{
    return editor->isMerger();
}


GenericEditor* GraphNode::getDest()
{
    return editor->getDestEditor();
}

GenericEditor* GraphNode::getSource()
{
    return editor->getSourceEditor();
}

Array<GenericEditor*> GraphNode::getConnectedEditors()
{
    return editor->getConnectedEditors();
}

const String GraphNode::getName()
{
    return editor->getDisplayName();
}

Point<float> GraphNode::getCenterPoint()
{
    Point<float> center = Point<float>(getX()+10, getY()+10);

    return center;
}

void GraphNode::switchIO(int path)
{

}

void GraphNode::updateBoundaries()
{

    int level = getLevel();

    int horzShift = gv->getHorizontalShift(this);

    setBounds(20+horzShift*140, 20+getLevel()*40, 150, 50);

}

void GraphNode::paint(Graphics& g)
{

    Array<bool> recordStatuses = editor->getRecordStatusArray();

    Path recordPath;

    for (int i = 0; i < recordStatuses.size(); i++)
    {
        float stepSize = 2*PI/recordStatuses.size();
        float startRadians = stepSize*i;
        float endRadians = startRadians + stepSize;
        if (recordStatuses[i])
            recordPath.addPieSegment(0,0,20,20,startRadians,endRadians,0.5);
    }

    g.setColour(Colours::red);
    g.fillPath(recordPath);

    if (mouseOver)
    {
        g.setColour(Colours::yellow);
        g.fillEllipse(2,2,16,16);
    }
    else
    {
        g.setColour(Colours::lightgrey);
        g.fillEllipse(2,2,16,16);

    }

    g.drawText(getName(), 25, 0, getWidth()-25, 20, Justification::left, true);

}