/***************************************************************************
 *   Copyright (C) 2003-2005 by David Saxton                               *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "cnitem.h"
#include "icndocument.h"
#include "connector.h"
#include "itemdocumentdata.h"
#include "node.h"

#include <kdebug.h>

#include <tqpainter.h>

Node::Node( ICNDocument *icnDocument, Node::node_type type, node_dir dir, const TQPoint &pos, TQString *id )
	: TQObject(), TQCanvasPolygon( icnDocument->canvas() )
{
	p_nodeGroup = 0l;
	p_parentItem = 0L;
	b_deleted = false;
	m_dir = dir;
	m_type = type;
	p_icnDocument = icnDocument;
	m_level = 0;
	m_selectedColor = TQColor( 101, 134, 192 );
	
	if (id)
	{
		m_id = *id;
		if ( !p_icnDocument->registerUID(*id) )
			kdError() << k_funcinfo << "Could not register id " << *id << endl;
	}
	else m_id = p_icnDocument->generateUID("node"+TQString::number(type));
	
	initPoints();
	move( pos.x(), pos.y() );
	setBrush( TQt::black );
	setPen( TQt::black );
	show();
	
	emit (moved(this));
}


Node::~Node()
{
	p_icnDocument->unregisterUID( id() );
}


int Node::rtti() const
{
	return ICNDocument::RTTI::Node;
}


void Node::setLevel( const int level )
{
	m_level = level;
}


bool Node::acceptInput() const
{
	return type() != fp_out;
}


bool Node::acceptOutput() const
{
	return type() != fp_in;
}


void Node::setVisible( bool yes )
{
	if ( isVisible() == yes )
		return;
	
	TQCanvasPolygon::setVisible(yes);
	
	const ConnectorList::iterator inputEnd = m_inputConnectorList.end();
	for ( ConnectorList::iterator it = m_inputConnectorList.begin(); it != inputEnd; ++it )
	{
		Connector *connector = *it;
		if (connector)
		{
			if ( isVisible() )
				connector->setVisible(true);
			
			else
			{
				Node *node = connector->startNode();
				connector->setVisible( node && node->isVisible() );
			}
		}
	}
	
	const ConnectorList::iterator outputEnd = m_outputConnectorList.end();
	for ( ConnectorList::iterator it = m_outputConnectorList.begin(); it != outputEnd; ++it )
	{
		Connector *connector = *it;
		if (connector)
		{
			if ( isVisible() )
				connector->setVisible(true);
			
			else
			{
				Node *node = connector->endNode();
				connector->setVisible( node && node->isVisible() );
			}
		}
	}
}


bool Node::isConnected( Node *node, NodeList *checkedNodes )
{
	if ( this == node )
		return true;
		
	bool firstNode = !checkedNodes;
	if (firstNode)
		checkedNodes = new NodeList();
		
	else if ( checkedNodes->contains(this) )
		return false;
	
	
	checkedNodes->append(this);
	
	const ConnectorList::const_iterator inputEnd = m_inputConnectorList.end();
	for ( ConnectorList::const_iterator it = m_inputConnectorList.begin(); it != inputEnd; ++it )
	{
		Connector *connector = *it;
		if (connector)
		{
			Node *startNode = connector->startNode();
			if ( startNode && startNode->isConnected( node, checkedNodes ) ) {
				if (firstNode) {
					delete checkedNodes;
				}
				return true;
			}
		}
	}
	
	const ConnectorList::const_iterator outputEnd = m_outputConnectorList.end();
	for ( ConnectorList::const_iterator it = m_outputConnectorList.begin(); it != outputEnd; ++it )
	{
		Connector *connector = *it;
		if (connector)
		{
			Node *endNode = connector->endNode();
			if ( endNode && endNode->isConnected( node, checkedNodes ) ) {
				if (firstNode) {
					delete checkedNodes;
				}
				return true;
			}
		}
	}
	
	if (firstNode) {
		delete checkedNodes;
	}
	
	return false;
}


void Node::setOrientation( node_dir dir )
{
	if ( m_dir == dir )
		return;
	
	if ( dir != Node::dir_up &&
		 dir != Node::dir_right &&
		 dir != Node::dir_down &&
		 dir != Node::dir_left )
	{
		kdDebug() << "Node::setOrientation: Unknown node direction "<<dir<<endl;
		return;
	}
	else
		m_dir = dir;
	initPoints();
}


void Node::initPoints()
{
	if ( type() == ec_junction )
	{
		setPoints( TQPointArray( TQRect( -4, -4, 8, 8 ) ) );
		return;
	}
	
	if ( type() == fp_junction )
	{
		setPoints( TQPointArray( TQRect( -4, -4, 9, 9 ) ) );
		return;
	}
	
	const int length = ( type() == ec_pin ) ? 8 : -8;
	
	// Bounding rectangle, facing right
	TQPointArray pa( TQRect( 0, -8, length, 16 ) );
	
	double angle;
	if		( m_dir == Node::dir_up ) angle = -90.;
	else if ( m_dir == Node::dir_right ) angle = 0.;
	else if ( m_dir == Node::dir_down ) angle = 90.;
	else if ( m_dir == Node::dir_left ) angle = 180.;
	else
	{
		kdError() << "Node::initPoints: unknown m_dir = "<<m_dir<<endl;
		return;
	}
	
	TQWMatrix m;
	m.rotate(angle);
	pa = m.map(pa);
	setPoints(pa);
}


TQPoint Node::findConnectorDivergePoint( bool * found )
{
	bool temp;
	if (!found)
		found = &temp;
	*found = false;
	
	if ( numCon( false, false ) != 2 )
		return TQPoint(0,0);
	
	TQPointList p1;
	TQPointList p2;
	
	int inSize = m_inputConnectorList.count();
	
	const ConnectorList connectors = m_inputConnectorList + m_outputConnectorList;
	const ConnectorList::const_iterator end = connectors.end();
	bool gotP1 = false;
	bool gotP2 = false;
	int at = -1;
	for ( ConnectorList::const_iterator it = connectors.begin(); it != end && !gotP2; ++it )
	{
		at++;
		if ( !(*it) || !(*it)->canvas() )
			continue;
		
		if (gotP1)
		{
			p2 = (*it)->connectorPoints( at < inSize );
			gotP2 = true;
		}
		else
		{
			p1 = (*it)->connectorPoints( at < inSize );
			gotP1 = true;
		}
	}
	if ( !gotP1 || !gotP2 )
		return TQPoint(0,0);
	
	unsigned maxLength = p1.size() > p2.size() ? p1.size() : p2.size();
	
	for ( unsigned i = 1; i < maxLength; ++i )
	{
		if ( p1[i] != p2[i] )
		{
			*found = true;
			return p1[i-1];
		}
	}
	return TQPoint(0, 0);
}


void Node::setParentItem( CNItem *parentItem )
{
	if (!parentItem)
	{
		kdError() << k_funcinfo << "no parent item" << endl;
		return;
	}
	
	p_parentItem = parentItem;
	
	setLevel(p_parentItem->level());
	
	connect( p_parentItem, TQ_SIGNAL(movedBy(double, double )), this, TQ_SLOT(moveBy(double, double)) );
	connect( p_parentItem, TQ_SIGNAL(removed(Item*)), this, TQ_SLOT(removeNode(Item*)) );
}


void Node::removeNode()
{
	if (b_deleted)
		return;
	b_deleted = true;
	
	emit removed(this);
	p_icnDocument->appendDeleteList(this);
}


void Node::moveBy( double dx, double dy )
{
	if ( dx == 0 && dy == 0 ) return;
	TQCanvasPolygon::moveBy( dx, dy );
	emit moved(this);
}


int Node::numCon( bool includeParentItem, bool includeHiddenConnectors ) const
{
	unsigned count = 0;
	
	const ConnectorList connectors[2] = { m_inputConnectorList, m_outputConnectorList };
	
	for ( unsigned i = 0; i < 2; i++ )
	{
		ConnectorList::const_iterator end = connectors[i].end();
		for ( ConnectorList::const_iterator it = connectors[i].begin(); it != end; ++it )
		{
			if ( *it && (includeHiddenConnectors || (*it)->canvas()) )
				count++;
		}
	}
	
	if ( isChildNode() && includeParentItem )
		count++;
	
	return count;
}


void Node::addOutputConnector( Connector * const connector )
{
	if ( type() == fp_in || !handleNewConnector(connector) )
		return;
	
	m_outputConnectorList.append(connector);
	
	if ( type() == fp_out || type() == fp_junction )
	{
		// We can only have one output connector, so remove the others. Note
		// that this code has to come *after* adding the new output connector,
		// as this node will delete itself if it's an fp_junction and there are
		// no output connectors.
		
		const ConnectorList connectors = m_outputConnectorList;
		const ConnectorList::const_iterator end = connectors.end();
		for ( ConnectorList::const_iterator it = connectors.begin(); it != end; ++it )
		{
			Connector * con = *it;
			if ( con && con != connector )
				con->removeConnector();
		}
	}
	
	m_outputConnectorList.remove((Connector*)0l);
}


void Node::addInputConnector( Connector * const connector )
{
	if ( type() == fp_out || !handleNewConnector(connector) )
		return;
	
	m_inputConnectorList.append(connector);
}


bool Node::handleNewConnector( Connector * connector )
{
	if (!connector)
		return false;
	
	if ( m_inputConnectorList.contains(connector) || m_outputConnectorList.contains(connector) )
	{
		kdWarning() << k_funcinfo << " Already have connector = " << connector << endl;
		return false;
	}
	
	connect( this, TQ_SIGNAL(removed(Node*)), connector, TQ_SLOT(removeConnector(Node*)) );
	connect( connector, TQ_SIGNAL(removed(Connector*)), this, TQ_SLOT(checkForRemoval(Connector*)) );
	connect( connector, TQ_SIGNAL(selected(bool)), this, TQ_SLOT(setNodeSelected(bool)) );
	
	if ( !isChildNode() )
		p_icnDocument->slotRequestAssignNG();
	
	return true;
}


Connector* Node::createInputConnector( Node * startNode )
{
	if ( type() == fp_out || !startNode )
		return 0l;
	
	Connector *connector = new Connector( startNode, this, p_icnDocument );
	addInputConnector(connector);
	
	return connector;
}


void Node::removeConnector( Connector *connector )
{
	if (!connector)
		return;
	
	ConnectorList::iterator it;
	
	it = m_inputConnectorList.find(connector);
	if ( it != m_inputConnectorList.end() )
	{
		(*it)->removeConnector();
		(*it) = 0L;
	}
	
	it = m_outputConnectorList.find(connector);
	if ( it != m_outputConnectorList.end() )
	{
		(*it)->removeConnector();
		(*it) = 0L;
	}
}

void Node::checkForRemoval( Connector *connector )
{
	removeConnector(connector);
	setNodeSelected(false);
	
	removeNullConnectors();
	
	if (!p_parentItem)
	{
		int conCount = m_inputConnectorList.count() + m_outputConnectorList.count();
		if ( conCount < 2 )
			removeNode();
	}
	
	if ( type() == Node::fp_junction && m_outputConnectorList.isEmpty() )
		removeNode();
}

void Node::removeNullConnectors()
{
	m_inputConnectorList.remove((Connector*)0L);
	m_outputConnectorList.remove((Connector*)0L);
}


NodeData Node::nodeData() const
{
	NodeData data;
	data.x = x();
	data.y = y();
	return data;
}


void Node::setNodeSelected( bool yes )
{
	if ( isSelected() == yes )
		return;
	
	TQCanvasItem::setSelected(yes);
	
	setPen(   yes ? m_selectedColor : TQt::black );
	setBrush( yes ? m_selectedColor : TQt::black );
}

#include "node.moc"
