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

    Patcher.cpp
    Created: 10 Aug 2013 9:33:32am
    Author:  Niall

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

#include "../JuceLibraryCode/JuceHeader.h"
#include "ListenPortComponent.h"
#include "SendPortComponent.h"
#include "MainComponent.h"
#include "Patcher.h"

//------------------------------------------------------------------------------
Patcher::Patcher(MainContentComponent& mainComp):
nextListenPort(10001),
nextSendPort(5678),
tempStart(-1),
mainComponent(mainComp)
{
    setSize(574, 339);
}

//------------------------------------------------------------------------------
Patcher::~Patcher()
{
    
}

//------------------------------------------------------------------------------
void Patcher::paint(Graphics& g)
{
    int i, j;
    PathStrokeType strokeType(8.0f);

    g.fillAll(Colour(0xFFEEECE1).brighter(0.5f));

    g.setColour(Colour(0xFFB0B0FF));
    for(i=0;i<connectionPaths.size();++i)
    {
        for(j=0;j<connectionPaths.getReference(i).size();++j)
            g.strokePath(connectionPaths.getReference(i)[j], strokeType);
    }

    if(tempStart > -1)
        g.strokePath(tempPath, strokeType);
}

//------------------------------------------------------------------------------
void Patcher::resized()
{
    int i;

	for(i=0;i<sendPorts.size();++i)
	{
		sendPorts[i]->setTopLeftPosition(getWidth()-140-16,
										 sendPorts[i]->getPosition().y);
	}

	rebuildPaths();
}

//------------------------------------------------------------------------------
void Patcher::changeListenerCallback(ChangeBroadcaster *source)
{
	int i, j, k;

	for(i=0;i<listenPorts.size();++i)
	{
		if(source == listenPorts[i])
		{
			mainComponent.setListenPort(i, listenPorts[i]->getPort().getIntValue());

			return;
		}
	}

	//Find the sendPort that sent the message.
	for(i=0;i<sendPorts.size();++i)
	{
		if(source == sendPorts[i])
		{
			//Go through every connection to find all the listenPorts that
			//port is connected to.
			for(j=0;j<connections.size();++j)
			{
				for(k=0;k<connections.getReference(j).size();++k)
				{
					if(connections.getReference(j)[k] == i)
					{
						mainComponent.setSendAddress(j,
													 i,
													 sendPorts[i]->getAddress());
						mainComponent.setSendPort(j,
												  i,
												  sendPorts[i]->getPort().getIntValue());
					}
				}
			}
		}
	}
}

//------------------------------------------------------------------------------
void Patcher::addListenPort(const String& game, const String& port)
{
	int y = 8 + (listenPorts.size() * 64);
	String actualGame((game != "") ? game : "Game Name");
	String actualPort((port != "") ? port : String(nextListenPort++));
	ListenPortComponent *newPort = new ListenPortComponent(actualGame, actualPort);

	newPort->setTopLeftPosition(8, y);
	newPort->addChangeListener(this);
	addAndMakeVisible(newPort);
	listenPorts.add(newPort);

	if((y + 72) > getHeight())
		setSize(getWidth(), y+72);

    connections.add(Array<int>());
    connectionPaths.add(Array<Path>());

	//...and add the actual OSCThread for this port.
	mainComponent.addListenPort(actualPort.getIntValue());
}

//------------------------------------------------------------------------------
void Patcher::addSendPort(const String& game,
						  const String& address,
						  const String& port)
{
	int y = 8 + (sendPorts.size() * 80);
	String actualGame((game != "") ? game : "Game Name");
	String actualAddress((address != "") ? address : "127.0.0.1");
	String actualPort((port != "") ? port : String(nextSendPort++));
	SendPortComponent *newPort = new SendPortComponent(actualGame,
													   actualAddress,
													   actualPort);

	newPort->setTopLeftPosition(getWidth()-140-32, y);
	newPort->addChangeListener(this);
	addAndMakeVisible(newPort);
	sendPorts.add(newPort);

	if((y + 72) > getHeight())
		setSize(getWidth(), y+88);
}

//------------------------------------------------------------------------------
void Patcher::startNewConnection(ListenPortComponent *comp)
{
    int i;

    for(i=0;i<listenPorts.size();++i)
    {
        if(listenPorts[i] == comp)
        {
            tempStart = i;
            tempMousePos = listenPorts[tempStart]->getPosition();
            tempMousePos.x += 128;
            tempMousePos.y += 24;

            buildPath(tempPath, tempMousePos, tempMousePos);

			draggingFromSend = -1;

            break;
        }
    }
}

//------------------------------------------------------------------------------
void Patcher::dragNewConnection(const Point<int>& pos)
{
	if(tempStart > -1)
    {
        Point<int> listenPos(listenPorts[tempStart]->getPosition());

        listenPos.x += 128;
        listenPos.y += 24;

        tempMousePos = pos;

        buildPath(tempPath, listenPos, tempMousePos);

		repaint();
    }
}

//------------------------------------------------------------------------------
void Patcher::endNewConnection(const Point<int>& pos)
{
    int i;

	if(draggingFromSend > -1)
	{
		//Remove the send destination from the correct OSCThread.
		mainComponent.removeSendDestination(tempStart,
											sendPorts[draggingFromSend]->getAddress(),
											sendPorts[draggingFromSend]->getPort().getIntValue());
	}

    for(i=0;i<sendPorts.size();++i)
    {
		Point<int> nodePos(sendPorts[i]->getPosition());

		nodePos.x += 12;
		nodePos.y += 32;
		if(pos.getDistanceFrom(nodePos) < 14)
        {
            connections.getReference(tempStart).add(i);
            connectionPaths.getReference(tempStart).add(Path());

			//Add the send port to the appropriate OSCThread.
			mainComponent.addSendDestination(tempStart,
											 sendPorts[i]->getAddress(),
											 sendPorts[i]->getPort().getIntValue());

            break;
        }
    }

    tempStart = -1;

    rebuildPaths();

	repaint();
}

//------------------------------------------------------------------------------
void Patcher::removeConnection(SendPortComponent *comp)
{
	int i;

    for(i=0;i<sendPorts.size();++i)
    {
        if(sendPorts[i] == comp)
        {
			tempStart = removeFirstConnection(comp);
			if(tempStart > -1)
			{
				Point<int> startPos(listenPorts[tempStart]->getPosition());

				startPos.x += 128;
				startPos.y += 24;
				tempMousePos = sendPorts[i]->getPosition();
				tempMousePos.x += 128;
				tempMousePos.y += 24;

				buildPath(tempPath, startPos, tempMousePos);

				draggingFromSend = i;
			}
            break;
        }
    }
}

//------------------------------------------------------------------------------
void Patcher::rebuildPaths()
{
    int i, j;

    jassert(connections.size() == connectionPaths.size());

    for(i=0;i<connections.size();++i)
    {
        jassert(connections.getReference(i).size() == connectionPaths.getReference(i).size())

        for(j=0;j<connections[i].size();++j)
        {
            Point<int> listenPos(listenPorts[i]->getPosition());
            Point<int> sendPos(sendPorts[connections.getReference(i)[j]]->getPosition());

            listenPos.x += 128;
            listenPos.y += 24;

            sendPos.x += 12;
            sendPos.y += 32;

            buildPath(connectionPaths.getReference(i).getReference(j), listenPos, sendPos);
        }
    }
}

//------------------------------------------------------------------------------
void Patcher::buildPath(Path& path,
                        const Point<int>& start,
                        const Point<int>& end)
{
    path.clear();

    path.startNewSubPath((float)start.x, (float)start.y);
	path.cubicTo((float)start.x + (((float)end.x-start.x)*0.5f),
				 (float)start.y,
				 (float)start.x + (((float)end.x-start.x)*0.5f),
				 (float)end.y,
				 (float)end.x,
				 (float)end.y);
}

//------------------------------------------------------------------------------
int Patcher::removeFirstConnection(SendPortComponent *port)
{
	int i, j;
	int retval = -1;
	int portIndex = -1;

	for(i=0;i<sendPorts.size();++i)
	{
		if(sendPorts[i] == port)
		{
			portIndex = i;

			break;
		}
	}

	for(i=0;i<connections.size();++i)
	{
		for(j=0;j<connections.getReference(i).size();++j)
		{
			if(connections.getReference(i)[j] == portIndex)
			{
				retval = i;

				connections.getReference(i).remove(j);
				connectionPaths.getReference(i).remove(j);

				break;
			}
		}

		if(retval > -1)
			break;
	}

	return retval;
}
