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

 This file is part of the JUCETICE project - Copyright 2009 by Lucio Asnaghi.

 JUCETICE is based around the JUCE library - "Jules' Utility Class Extensions"
 Copyright 2007 by Julian Storer.

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

 JUCE and JUCETICE can be redistributed and/or modified under the terms of
 the GNU General Public License, as published by the Free Software Foundation;
 either version 2 of the License, or (at your option) any later version.

 JUCE and JUCETICE are distributed in the hope that they 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 JUCE and JUCETICE; if not, visit www.gnu.org/licenses or write to
 Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 Boston, MA 02111-1307 USA

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

BEGIN_JUCE_NAMESPACE

#include "jucetice_GraphLinkComponent.h"


//==============================================================================
GraphNodeComponent::GraphNodeComponent ()
  : listener (0),
    userData (0),
    uniqueID (0),
    nodeColour (Colours::grey),
    leftToRight (true),
    hasBeenLocked (false),
    originalX (0),
    originalY (0),
    lastX (0),
    lastY (0)/*,
    dropShadower (0.5f, 0, 1, 2.0f)*/
{
    setBufferedToImage (false);
    setOpaque (true);
    toFront (true);

    constrainer.setMinimumOnscreenAmounts (9999,9999,9999,9999);

    //dropShadower.setOwner (this);
}

GraphNodeComponent::~GraphNodeComponent()
{
}

//==============================================================================
void GraphNodeComponent::moved()
{
    updateConnectors();

    // update last position            
    lastX = getX ();
    lastY = getY ();
}

void GraphNodeComponent::resized()
{
    updateConnectors();
}

void GraphNodeComponent::mouseDoubleClick (const MouseEvent& e)
{
    // notify listener
    if (listener)
        listener->nodeDoubleClicked (this, e);
}

void GraphNodeComponent::mouseDown (const MouseEvent& e)
{
    if (listener)
        listener->nodeSelected (this);

    if (e.mods.isRightButtonDown())
    {
        if (listener)
            listener->nodePopupMenuSelected (this);
    }
    else if (e.mods.isLeftButtonDown())
    {    
        if (! hasBeenLocked)
        {
            originalX = getX();
			originalY = getY();
        }
    }
}

void GraphNodeComponent::mouseDrag (const MouseEvent& e)
{
    if (! hasBeenLocked && e.mods.isLeftButtonDown())
    {
        int x = originalX + e.getDistanceFromDragStartX();
        int y = originalY + e.getDistanceFromDragStartY();
        int w = getWidth();
        int h = getHeight();
                
        const Component* const parentComp = getParentComponent();
        Rectangle<int> limits;
        limits.setSize (parentComp->getWidth(), parentComp->getHeight());

		Rectangle<int> bounds (x, y, w, h);
        constrainer.checkBounds (bounds,
                                 getBounds(), limits,
                                 false, false, false, false);

        if (listener)
            listener->nodeMoved (this, x - lastX, y - lastY);

        constrainer.applyBoundsToComponent (*this, bounds);
    }
}

//==============================================================================
void GraphNodeComponent::paint (Graphics& g)
{
    if (listener)
        listener->nodePaint (this, g);
}

//==============================================================================
void GraphNodeComponent::addInputConnector (const int connectorType)
{
    GraphConnectorComponentInput* connector =
        new GraphConnectorComponentInput (this, connectorType);

    connector->setConnectorID (graphInputs.size());

    getParentComponent()->addAndMakeVisible (connector);

    graphInputs.add (connector);

    updateConnectors();
}

void GraphNodeComponent::addOutputConnector (const int connectorType)
{
    GraphConnectorComponentOutput* connector =
        new GraphConnectorComponentOutput (this, connectorType);

    connector->setConnectorID (graphOutputs.size());

    getParentComponent()->addAndMakeVisible (connector);

    graphOutputs.add (connector);

    updateConnectors();
}

//==============================================================================
GraphConnectorComponentInput* GraphNodeComponent::getInputConnector (const int connectorID)
{
    for (int i = 0; i < graphInputs.size(); i++)
    {
        GraphConnectorComponentInput* input = graphInputs.getUnchecked (i);

        if (input && (input->getConnectorID() == connectorID))
        {
            return input;
        }
    }

    return 0;
}

GraphConnectorComponentOutput* GraphNodeComponent::getOutputConnector (const int connectorID)
{
    for (int i = 0; i < graphOutputs.size(); i++)
    {
        GraphConnectorComponentOutput* output = graphOutputs.getUnchecked (i);

        if (output && (output->getConnectorID() == connectorID))
        {
            return output;
        }
    }

    return 0;
}

//==============================================================================
int GraphNodeComponent::getFirstInputOfType (const int connectorType)
{
    for (int i = 0; i < graphInputs.size(); i++)
    {
        GraphConnectorComponentInput* input = graphInputs.getUnchecked (i);

        if (input && (input->getType() == connectorType))
        {
            return i;
        }
    }

    return -1;
}

int GraphNodeComponent::getFirstOutputOfType (const int connectorType)
{
    for (int i = 0; i < graphOutputs.size(); i++)
    {
        GraphConnectorComponentOutput* output = graphOutputs.getUnchecked (i);

        if (output && (output->getType() == connectorType))
        {
            return i;
        }
    }

    return -1;
}

//==============================================================================
int GraphNodeComponent::getInputLinksCount () const
{
    int linksCount = 0;

    Array<GraphConnectorComponent*> links;
    for (int i = 0; i < graphInputs.size(); i++)
    {
        graphInputs.getUnchecked (i)->getLinkedConnectors (links);
        linksCount += links.size ();
    }

    return linksCount;
}

int GraphNodeComponent::getOutputLinksCount () const
{
    int linksCount = 0;

    Array<GraphConnectorComponent*> links;
    for (int i = 0; i < graphOutputs.size(); i++)
    {
        graphOutputs.getUnchecked (i)->getLinkedConnectors (links);
        linksCount += links.size ();
    }

    return linksCount;
}

//==============================================================================
void GraphNodeComponent::deleteConnectors (const bool freeConnectors)
{
    breakAllLinks (false);

    if (freeConnectors) {
        for (int i = 0; i < graphInputs.size(); i++)
            delete graphInputs.getUnchecked (i);
    }

    if (freeConnectors) {
        for (int i = 0; i < graphOutputs.size(); i++)
            delete graphOutputs.getUnchecked (i);
    }

    graphInputs.clear ();
    graphOutputs.clear ();
}

//==============================================================================
void GraphNodeComponent::breakAllLinks (const bool notifyListeners)
{
    for (int i = 0; i < graphInputs.size(); i++)
        graphInputs.getUnchecked (i)->destroyAllLinks (notifyListeners);

    for (int i = 0; i < graphOutputs.size(); i++)
        graphOutputs.getUnchecked (i)->destroyAllLinks (notifyListeners);

    if (notifyListeners)
        notifyGraphChanged ();
}

void GraphNodeComponent::breakInputLinks (const bool notifyListeners)
{
    for (int i = 0; i < graphInputs.size(); i++)
        graphInputs.getUnchecked (i)->destroyAllLinks (notifyListeners);

    if (notifyListeners)
        notifyGraphChanged ();
}

void GraphNodeComponent::breakOutputLinks (const bool notifyListeners)
{
    for (int i = 0; i < graphOutputs.size(); i++)
        graphOutputs.getUnchecked (i)->destroyAllLinks (notifyListeners);

    if (notifyListeners)
        notifyGraphChanged ();
}

//==============================================================================
void GraphNodeComponent::setNodeListener (GraphNodeListener* const listener_)
{
    listener = listener_;
}

void GraphNodeComponent::notifyConnectorPopupMenuSelected (GraphConnectorComponent* changedConnector)
{
    if (listener)
        listener->connectorPopupMenuSelected (changedConnector);
}

void GraphNodeComponent::notifyLinkPopupMenuSelected (GraphLinkComponent* changedLink)
{
    if (listener)
        listener->linkPopupMenuSelected (changedLink);
}

void GraphNodeComponent::notifyLinkConnected (GraphLinkComponent* changedLink)
{
    if (listener)
        listener->linkConnected (changedLink);
}

void GraphNodeComponent::notifyLinkDisconnected (GraphLinkComponent* changedLink)
{
    if (listener)
        listener->linkDisconnected (changedLink);
}

void GraphNodeComponent::notifyGraphChanged ()
{
    if (listener)
        listener->graphChanged ();
}

Colour GraphNodeComponent::getConnectorColour (GraphConnectorComponent* connector,
                                               const bool isSelected)
{
    if (listener)
        return listener->getConnectorColour (connector, isSelected);

    return Colours::black;
}

//==============================================================================
bool GraphNodeComponent::connectToNode (const int sourcePort,
                                        GraphNodeComponent* destinationNode,
                                        const int destinationPort,
                                        const bool notifyListeners)
{
    GraphConnectorComponentOutput* out = getOutputConnector (sourcePort);
    GraphConnectorComponentInput* in = destinationNode->getInputConnector (destinationPort);

    if (out != 0 && in != 0)
    {
        GraphLinkComponent* newLink = new GraphLinkComponent (leftToRight);
        newLink->from = out;

        if (in->canConnectFrom (newLink))
        {
            int startX = out->getBounds ().getCentreX ();
            int startY = out->getBounds ().getCentreY ();
            int endX = in->getBounds ().getCentreX ();
            int endY = in->getBounds ().getCentreY ();

            newLink->setStartPoint (startX, startY);
            newLink->setEndPoint (endX, endY);

            if (in->connectFrom (newLink))
            {
                out->addLink (newLink);

                if (notifyListeners)
                    notifyGraphChanged ();

                getParentComponent()->addAndMakeVisible (newLink);
                newLink->toBack ();
                newLink->repaint();
                return true;
            }
        }

        deleteAndZero (newLink);
    }

    return false;
}

//==============================================================================
bool GraphNodeComponent::isAfterNode (GraphNodeComponent* nodeBefore)
{
    bool findConnector = false;

    for (int j = getInputConnectorCount (); --j >= 0;)
    {
        GraphConnectorComponent* connector = getInputConnector (j);
        if (connector)
        {
            Array <GraphConnectorComponent*> linked;
            connector->getLinkedConnectors (linked);

            for (int c = linked.size (); --c >= 0;)
            {
                GraphConnectorComponent* other = linked.getUnchecked (c);
                if (other)
                {
                    if (other->getParentGraphComponent() == nodeBefore)
                    {
                        return true;
                    }
                    else
                    {
                        findConnector = other->getParentGraphComponent()->isAfterNode (nodeBefore);
                    }
                }

                if (findConnector)
                    return true;
            }
        }
    }

    return findConnector;
}

bool GraphNodeComponent::isBeforeNode (GraphNodeComponent* nodeAfter)
{
    bool findConnector = false;

    for (int j = getOutputConnectorCount (); --j >= 0;)
    {
        GraphConnectorComponent* connector = getOutputConnector (j);
        if (connector)
        {
            Array <GraphConnectorComponent*> linked;
            connector->getLinkedConnectors (linked);

            for (int c = linked.size (); --c >= 0;)
            {
                GraphConnectorComponent* other = linked.getUnchecked (c);
                if (other)
                {
                    if (other->getParentGraphComponent() == nodeAfter)
                    {
                        return true;
                    }
                    else
                    {
                        findConnector = other->getParentGraphComponent()->isBeforeNode (nodeAfter);
                    }
                }

                if (findConnector)
                    return true;
            }
        }
    }

    return findConnector;
}

//==============================================================================
void GraphNodeComponent::updateConnectors()
{
    float x_pos, y_pos, y_pos_step;
    float height, width;

    if (leftToRight)
    {
        height = (float) getHeight();
        width = (float) getWidth();
    }
    else
    {
        width = (float) getHeight();
        height = (float) getWidth();
    }

    if (graphInputs.size() > 0)
    {
        y_pos_step = height / (float) graphInputs.size();

        x_pos = 0.0;
        y_pos = (height / 2) - (((float)y_pos_step * ((float) graphInputs.size() - 1.0f)) / 2.0f);
        for (int i = 0; i < graphInputs.size(); i++)
        {
            if (leftToRight)
                graphInputs[i]->setRelativePosition ((int) (x_pos - graphInputs[i]->getWidth()),
                                                     (int) (y_pos - graphInputs[i]->getWidth() / 2));
            else
                graphInputs[i]->setRelativePosition ((int) (y_pos - graphInputs[i]->getWidth() / 2),
                                                     (int) (x_pos - graphInputs[i]->getWidth()));
            y_pos += y_pos_step;
        }
    }

    if (graphOutputs.size() > 0)
    {
        y_pos_step = height / (float) graphOutputs.size();

        x_pos = width;
        y_pos = (height / 2) - (((float)y_pos_step * ((float) graphOutputs.size() - 1.0f)) / 2.0f);
        for(int i = 0; i < graphOutputs.size(); i++)
        {
            if (leftToRight)
                graphOutputs[i]->setRelativePosition ((int) (x_pos),
                                                      (int) (y_pos - graphOutputs[i]->getWidth() / 2));
            else
                graphOutputs[i]->setRelativePosition ((int) (y_pos - graphOutputs[i]->getWidth() / 2),
                                                      (int) (x_pos));
            y_pos += y_pos_step;
        }
    }
}

END_JUCE_NAMESPACE
