/*
 * Kivio - Visual Modelling and Flowcharting
 * Copyright (C) 2000-2001 theKompany.com & Dave Marotti
 *
 * 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.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/*
 * Implementing a connector involves deriving from this class.
 *
 * You need to implement the following routines:
 *   setStartPoint
 *   setEndPoint
 *   checkForCollision
 *   duplicate
 *   paint
 *   paintOutline
 *   saveXML
 *   loadXML
 *
 * Save and Load should call saveConnectors, loadConnectors, saveProperties, and
 * loadProperties.  These are helper functions which take care of the common
 * acts of saving/loading connectors and colors/line-styles, etc...
 *
 */

#include "kivio_arrowhead.h"
#include "kivio_base_connector_stencil.h"
#include "kivio_common.h"
#include "kivio_connector_point.h"
#include "kivio_custom_drag_data.h"
#include "kivio_fill_style.h"
#include "kivio_intra_stencil_data.h"
#include "kivio_layer.h"
#include "kivio_line_style.h"
#include "kivio_page.h"
#include "kivio_painter.h"
#include "kivio_point.h"
#include "kivio_stencil.h"

#include <kdebug.h>
#include <KoZoomHandler.h>

/**
 * Default constructor.
 *
 * This will allocate a new fill style object, a list for
 * connection points, and set some default values.
 */
KivioBaseConnectorStencil::KivioBaseConnectorStencil()
    : KivioStencil()
{
    m_pFillStyle = new KivioFillStyle();
    m_pLineStyle = new KivioLineStyle();

    m_pConnectorPoints = new TQPtrList<KivioConnectorPoint>;
    m_pConnectorPoints->setAutoDelete(true);

}


/**
 * Destructor
 */
KivioBaseConnectorStencil::~KivioBaseConnectorStencil()
{
    delete m_pFillStyle;
    delete m_pLineStyle;
    delete m_pConnectorPoints;
}

void KivioBaseConnectorStencil::setFGColor( TQColor c )
{
    m_pLineStyle->setColor(c);
}

TQColor KivioBaseConnectorStencil::fgColor()
{
    return m_pLineStyle->color();
}

void KivioBaseConnectorStencil::setLineWidth( double f )
{
    m_pLineStyle->setWidth(f);
}

double KivioBaseConnectorStencil::lineWidth()
{
    return m_pLineStyle->width();
}

void KivioBaseConnectorStencil::setBGColor( TQColor c )
{
    m_pFillStyle->setColor(c);
}

TQColor KivioBaseConnectorStencil::bgColor()
{
    return m_pFillStyle->color();
}


/////////////////////////////////
// Position functions
/////////////////////////////////
void KivioBaseConnectorStencil::setX( double x )
{
    double dx = x - m_x;

    m_x += dx;

    KivioConnectorPoint *p = m_pConnectorPoints->first();
    while( p )
    {
        p->setX( p->x() + dx, false );
        p->disconnect();

        p = m_pConnectorPoints->next();
    }


    m_x = x;
}

void KivioBaseConnectorStencil::setY( double y )
{
    double dy = y - m_y;

    m_y += dy;

    KivioConnectorPoint *p = m_pConnectorPoints->first();
    while( p )
    {
        p->setY( p->y() + dy, false );
        p->disconnect();

        p = m_pConnectorPoints->next();
    }

    m_y = y;
}

void KivioBaseConnectorStencil::setPosition( double x, double y )
{
    double dx = x - m_x;
    double dy = y - m_y;

    m_x += dx;
    m_y += dy;

    KivioConnectorPoint *p = m_pConnectorPoints->first();
    while( p )
    {
        p->setPosition( p->x()+dx, p->y()+dy, false );
        p->disconnect();

        p = m_pConnectorPoints->next();
    }


    m_x = x;
    m_y = y;
}



/////////////////////////////
// Connection tool functions
/////////////////////////////
void KivioBaseConnectorStencil::setStartPoint( double /*x*/, double /*y*/ )
{
    /* Derived class must implement this function */
    // m_start.setPosition( x, y, false );
    // m_start.disconnect();
}


void KivioBaseConnectorStencil::setEndPoint( double /*x*/, double /*y*/ )
{
    /* Derived class must implement this function */
    // m_end.setPosition( x, y, false );
    // m_end.disconnect();
}


void KivioBaseConnectorStencil::paint( KivioIntraStencilData * )
{
    /* Derived class must implement this */
}

void KivioBaseConnectorStencil::paintOutline( KivioIntraStencilData *pData )
{
    /* Derived class should implement this */
    paint( pData );
}

void KivioBaseConnectorStencil::paintConnectorTargets( KivioIntraStencilData * )
{
}

void KivioBaseConnectorStencil::paintSelectionHandles( KivioIntraStencilData *pData )
{
  // Handle Width
  const double HW = 6.0f;
  const double HWP1 = HW+1.0f;

  // Handle Width Over 2
  const double HWo2 = HW/2.0f;

  // Stencil data
  KoZoomHandler* zoomHandler = pData->zoomHandler;
  KivioPainter *painter = pData->painter;
  double x1, y1;

  painter->setLineWidth(1.0f);
  painter->setFGColor(TQColor(0,0,0));

  KivioConnectorPoint *p = m_pConnectorPoints->first();
  while( p )
  {
    x1 = zoomHandler->zoomItX(p->x()) - HWo2;
    y1 = zoomHandler->zoomItY(p->y()) - HWo2;

    if( p->target() )
        painter->setBGColor(TQColor(200,0,0));
    else
        painter->setBGColor(TQColor(0,200,0));

    painter->fillRect( x1, y1, HWP1, HWP1 );

    p = m_pConnectorPoints->next();
  }
}



///////////////////////////////
// Collision detection
///////////////////////////////
KivioCollisionType KivioBaseConnectorStencil::checkForCollision( KoPoint *, double )
{
    /* Derived class must implement this */
    return kctNone;
}




/////////////////////////////////
// Custom dragging
/////////////////////////////////
/**
 * Custom drag the connector points.
 *
 * The default action of this function is to locate the point
 * in the connector list by the id and then drag it around.
 * Then attempt to snap it to another stencil.  Otherwise
 * disconnect it.
 */
void KivioBaseConnectorStencil::customDrag( KivioCustomDragData *pData )
{
    double _x = pData->x;
    double _y = pData->y;
    int id = pData->id;

    KivioConnectorPoint *p;

    // Locate the point specified by id
    p = m_pConnectorPoints->at( id - (kctCustom+1));

    if( !p )
    {
       kdDebug(43000) << "KivioBaseConnectorStencil::customDrag() - KivioConnectorPoint id: " << (id - (kctCustom+1)) << "  not found\n" << endl;
       return;
    }

    p->setPosition( _x, _y, true );


    KivioLayer *pCurLayer = pData->page->curLayer();
    KivioLayer *pLayer = pData->page->firstLayer(); //pData->page->curLayer();

    while( pLayer )
    {
        // To be connected to, a layer must be visible and connectable
        if( pLayer!=pCurLayer )
        {
            if( pLayer->connectable()==false || pLayer->visible()==false )
            {
                pLayer = pData->page->nextLayer();
                continue;
            }
        }

        // Tell the layer to search for a target
        if( pLayer->connectPointToTarget( p, 8.0f ) )
        {
            return;
        }

        pLayer = pData->page->nextLayer();
    }


    // Nope, disconnect
    p->disconnect();
}


/**
 * Sets the position and dimensions of this stencil based on its connection points.
 */
void KivioBaseConnectorStencil::updateGeometry()
{
    double minX, minY, maxX, maxY;

    minX = 1000000000000.0f;
    minY = minX;
    maxX = -100000000000.0f;
    maxY = maxX;

    KivioConnectorPoint *p;
    p = m_pConnectorPoints->first();
    while( p )
    {
        if( p->x() < minX )
            minX = p->x();
        if( p->x() > maxX )
            maxX = p->x();
        if( p->y() < minY )
            minY = p->y();
        if( p->y() > maxY )
            maxY = p->y();

        p = m_pConnectorPoints->next();
    }

    m_x = minX;
    m_y = minY;
    m_w = maxX - minX + 1.0f;
    m_h = maxY - minY + 1.0f;
}



// file i/o routines
bool KivioBaseConnectorStencil::loadXML( const TQDomElement & )
{
    return false;
}

TQDomElement KivioBaseConnectorStencil::saveXML( TQDomDocument & )
{
    return TQDomElement();
}



KivioStencil *KivioBaseConnectorStencil::duplicate()
{
    /* Derived class must implement this function */
    return NULL;
}


bool KivioBaseConnectorStencil::boolAllTrue( bool *boolArray, int count )
{
    int i;

    for( i=0; i<count; i++ )
    {
        if( boolArray[i]==false )
            return false;
    }

    return true;
}

bool KivioBaseConnectorStencil::boolContainsFalse( bool *boolArray, int count )
{
    int i;

    for( i=0; i<count; i++ )
    {
        if( boolArray[i]==false )
            return true;
    }

    return false;
}


void KivioBaseConnectorStencil::searchForConnections( KivioPage *pPage )
{
    bool *done = new bool[ m_pConnectorPoints->count() ];

    int i;

    for( i=0; i<(int)m_pConnectorPoints->count(); i++ )
        done[i] = false;

    KivioConnectorPoint *p;

    i=0;
    p = m_pConnectorPoints->first();
    while( p )
    {
        if( p->targetId() == -1 )
            done[i] = true;

        i++;
        p = m_pConnectorPoints->next();
    }


    // No connections? BaiL!
    if( boolAllTrue( done, m_pConnectorPoints->count() ) )
    {
       delete [] done;
       return;
    }



    KivioLayer *pLayer = pPage->firstLayer();
    while( pLayer && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) )
    {
        KivioStencil *pStencil = pLayer->firstStencil();

        while( pStencil && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) )
        {
            // No connecting to ourself!
            if( pStencil != this )
            {

                // Iterate through all connectors attempting to connect it to the stencil.
                // If it connects, mark it as done
                i=0;
                p = m_pConnectorPoints->first();
                while( p )
                {
                    if( !done[i] &&
                         p->targetId() != -1 )
                    {
                        if( pStencil->connectToTarget( p, p->targetId() ) )
                        {
                            done[i] = true;
                        }
                    }

                    i++;
                    p = m_pConnectorPoints->next();
                }

            }

            pStencil = pLayer->nextStencil();
        }

        pLayer = pPage->nextLayer();
    }

    delete [] done;
}


//////////////////////
// resize handles
//////////////////////
int KivioBaseConnectorStencil::resizeHandlePositions()
{
    return (int)krhpNone;
}

TQDomElement KivioBaseConnectorStencil::saveConnectors( TQDomDocument &doc )
{
    TQDomElement eConns = doc.createElement("KivioConnectors");

    KivioConnectorPoint *p;
    p = m_pConnectorPoints->first();
    while( p )
    {
        eConns.appendChild( p->saveXML(doc) );

        p = m_pConnectorPoints->next();
    }

    return eConns;
}

bool KivioBaseConnectorStencil::loadConnectors( const TQDomElement &e )
{
    m_pConnectorPoints->clear();

    KivioConnectorPoint *p;

    TQDomNode node = e.firstChild();
    TQDomElement e2;
    TQString name;

    while( !node.isNull() )
    {
        e2 = node.toElement();
        name = e2.nodeName();

        if( name == "KivioConnectorPoint" )
        {
            p = new KivioConnectorPoint();
            p->setStencil(this);
            p->loadXML( e2 );

            m_pConnectorPoints->append( p );
            p = NULL;
        }

        node = node.nextSibling();
    }

    return true;
}

TQDomElement KivioBaseConnectorStencil::saveProperties( TQDomDocument &doc )
{
    TQDomElement propE = doc.createElement("KivioConnectorProperties");

    propE.appendChild( m_pLineStyle->saveXML( doc ) );

    propE.appendChild( m_pFillStyle->saveXML( doc ) );

    return propE;
}

bool KivioBaseConnectorStencil::loadProperties( const TQDomElement &e )
{
    TQDomNode node;
    TQString nodeName;

    node = e.firstChild();
    while( !node.isNull() )
    {
        nodeName = node.nodeName();
        if( nodeName == "KivioFillStyle" )
        {
            m_pFillStyle->loadXML( node.toElement() );
        }
        else if( nodeName == "KivioLineStyle" )
        {
            m_pLineStyle->loadXML( node.toElement() );
        }

        node = node.nextSibling();
    }

    return true;
}
