/*! \file src/booleng.cpp
    \author Klaas Holwerda
 
    Copyright: 2001-2004 (C) Klaas Holwerda
 
    Licence: see kboollicense.txt 
 
    RCS-ID: $Id: booleng.cpp,v 1.7 2009/09/14 16:50:12 titato Exp $
*/

#include "kbool/booleng.h"

#include <time.h>

#include "kbool/link.h"
#include "kbool/line.h"
#include "kbool/node.h"
#include "kbool/graph.h"
#include "kbool/graphlst.h"

B_INT bmin( B_INT const value1, B_INT const value2 )
{
    return( ( value1 < value2 ) ? value1 : value2 );
}

B_INT bmax( B_INT const value1, B_INT const value2 )
{
    return( ( value1 > value2 ) ? value1 : value2 );
}

B_INT babs( B_INT a )
{
    if ( a < 0 ) a = -a;
    return a;
}

//-------------------------------------------------------------------/
//----------------- Bool_Engine_Error -------------------------------/
//-------------------------------------------------------------------/

Bool_Engine_Error::Bool_Engine_Error( string message, string header, int degree, int fatal )
{
    _message = message;
    _header = header;

    _degree = degree;
    _fatal = fatal;

}

Bool_Engine_Error::Bool_Engine_Error( const Bool_Engine_Error& a )
{
    _message = a._message;
    _header = a._header;

    _degree = a._degree;
    _fatal = a._fatal;

}

Bool_Engine_Error::~Bool_Engine_Error()
{
    _message = "";
    _header = "";
}

string Bool_Engine_Error::GetErrorMessage()
{
    return _message;
}

string Bool_Engine_Error::GetHeaderMessage()
{
    return _header;
}

int Bool_Engine_Error::GetErrorDegree()
{
    return _degree;
}

int Bool_Engine_Error::GetFatal()
{
    return _fatal;
}

//-------------------------------------------------------------------/
//----------------- Bool_Engine -------------------------------------/
//-------------------------------------------------------------------/

Bool_Engine::Bool_Engine()
{
    _linkiter = new TDLI<kbLink>();
    m_intersectionruns = 1;

    m_orientationEntryMode = false;
    m_doLinkHoles = true;
    m_allowNonTopHoleLinking = true;

    m_graphlist = new kbGraphList( this );
    m_ACCUR = 1e-4;
    m_WINDINGRULE = true;
    m_GraphToAdd = NULL;
    m_firstNodeToAdd = NULL;
    m_lastNodeToAdd = NULL;

    m_logfile = NULL;

#if KBOOL_LOG == 1
    SetLog( true );
#else
    SetLog( false );
#endif
}

Bool_Engine::~Bool_Engine()
{
    if ( m_logfile != NULL )
        fclose ( m_logfile );

    delete _linkiter;
    delete m_graphlist;
}

void Bool_Engine::SetLog( bool OnOff )
{
    m_doLog = OnOff;
    if ( m_doLog )
    {
        if ( m_logfile == NULL )
        {
            // create a new logfile
            m_logfile = fopen( KBOOL_LOGFILE, "w" );
            if ( m_logfile == NULL )
                fprintf( stderr, "Bool_Engine: Unable to write to Boolean Engine logfile\n" );
            else
            {
                time_t timer;
                struct tm * today;
                timer = time( NULL );
                today = localtime( &timer );

                fprintf( m_logfile, "Logfile created on:\t\t\t%s", ctime( &timer ) );
            }
        }
    }
    else
    {
        if ( m_logfile != NULL )
        {
            fclose ( m_logfile );
            m_logfile = NULL;
        }
    }
}

void Bool_Engine::SetState( string process )
{
    Write_Log( process );
}

void Bool_Engine::error( string text, string title )
{
    Write_Log( "FATAL ERROR: ", title );
    Write_Log( "FATAL ERROR: ", text );
    throw Bool_Engine_Error( " Fatal Error", "Fatal Error", 9, 1 );
}

void Bool_Engine::info( string text, string title )
{
    Write_Log( "FATAL ERROR: ", title );
    Write_Log( "FATAL ERROR: ", text );
}

void Bool_Engine::SetMarge( double marge )
{
    m_MARGE = marge;
    Write_Log( "Bool_Engine::m_MARGE = %f\n", m_MARGE );
}

double Bool_Engine::GetAccur()
{
    return m_ACCUR;
}

void Bool_Engine::SetRoundfactor( double roundfac )
{
    m_ROUNDFACTOR = roundfac;
    Write_Log( "Bool_Engine::m_ROUNDFACTOR = %f\n", m_ROUNDFACTOR );
}

double Bool_Engine::GetRoundfactor()
{
    return m_ROUNDFACTOR;
}

double Bool_Engine::GetMarge()
{
    return m_MARGE;
}

void Bool_Engine::SetDGrid( double dgrid )
{
    m_DGRID = dgrid;
    Write_Log( "Bool_Engine::m_DGRID = %f\n", m_DGRID );
}

double Bool_Engine::GetDGrid()
{
    return m_DGRID;
}

void Bool_Engine::SetGrid( B_INT grid )
{
    m_GRID = grid;
    Write_Log( "Bool_Engine::m_GRID = %lld\n", m_GRID );
}

B_INT Bool_Engine::GetGrid()
{
    return m_GRID;
}

void Bool_Engine::SetCorrectionAber( double aber )
{
    m_CORRECTIONABER = aber;
    Write_Log( "Bool_Engine::m_CORRECTIONABER = %f\n", m_CORRECTIONABER );
}

double Bool_Engine::GetCorrectionAber()
{
    return m_CORRECTIONABER;
}

void Bool_Engine::SetCorrectionFactor( double aber )
{
    m_CORRECTIONFACTOR = aber;
    Write_Log( "Bool_Engine::m_CORRECTIONFACTOR = %f\n", m_CORRECTIONFACTOR );
}

double Bool_Engine::GetCorrectionFactor()
{
    return m_CORRECTIONFACTOR;
}

void Bool_Engine::SetSmoothAber( double aber )
{
    m_SMOOTHABER = aber;
    Write_Log( "Bool_Engine::m_SMOOTHABER = %f\n", m_SMOOTHABER );
}

double Bool_Engine::GetSmoothAber()
{
    return m_SMOOTHABER;
}

void Bool_Engine::SetMaxlinemerge( double maxline )
{
    m_MAXLINEMERGE = maxline;
    Write_Log( "Bool_Engine::m_MAXLINEMERGE = %f\n", m_MAXLINEMERGE );
}

double Bool_Engine::GetMaxlinemerge()
{
    return m_MAXLINEMERGE;
}

void Bool_Engine::SetWindingRule( bool rule )
{
    m_WINDINGRULE = rule;
}

bool Bool_Engine::GetWindingRule()
{
    return m_WINDINGRULE;
}


void Bool_Engine::SetInternalMarge( B_INT marge )
{
    m_MARGE = ( double )marge / m_GRID / m_DGRID;
}

B_INT Bool_Engine::GetInternalMarge()
{
    return ( B_INT ) ( m_MARGE * m_GRID * m_DGRID );
}

double Bool_Engine::GetInternalCorrectionAber()
{
    return  m_CORRECTIONABER * m_GRID * m_DGRID;
}

double Bool_Engine::GetInternalCorrectionFactor()
{
    return m_CORRECTIONFACTOR * m_GRID * m_DGRID;
}

double Bool_Engine::GetInternalSmoothAber()
{
    return m_SMOOTHABER * m_GRID * m_DGRID;
}

B_INT Bool_Engine::GetInternalMaxlinemerge()
{
    return ( B_INT ) ( m_MAXLINEMERGE * m_GRID * m_DGRID );
}

#define TRIALS 30

bool Bool_Engine::Do_Operation( BOOL_OP operation )
{

#if KBOOL_DEBUG
    kbGraphList * saveme = new kbGraphList( m_graphlist );
#endif

    try
    {
        switch ( operation )
        {
            case ( BOOL_OR ):
                        case ( BOOL_AND ):
                            case ( BOOL_EXOR ):
                                case ( BOOL_A_SUB_B ):
                                    case ( BOOL_B_SUB_A ):
                                            m_graphlist->Boolean( operation, m_intersectionruns );
                break;
            case ( BOOL_CORRECTION ):
                            m_graphlist->Correction();
                break;
            case ( BOOL_MAKERING ):
                            m_graphlist->MakeRings();
                break;
            case ( BOOL_SMOOTHEN ):
                            m_graphlist->Smoothen( GetInternalSmoothAber() );
                break;
            default:
    {
                error( "Wrong operation", "Command Error" );
                return false;
            }
        }
    }
    catch ( Bool_Engine_Error & error )
    {

#if KBOOL_DEBUG
        delete m_graphlist;
        m_graphlist = new kbGraphList( saveme );
        m_graphlist->WriteGraphsKEY( this );
#endif

        if ( m_logfile != NULL )
        {
            fclose ( m_logfile );
            m_logfile = NULL;
        }

        info( error.GetErrorMessage(), "error" );
        throw error;
    }
    catch( ... )
    {

#if KBOOL_DEBUG
        delete m_graphlist;
        m_graphlist = new kbGraphList( saveme );
        m_graphlist->WriteGraphsKEY( this );
#endif

        if ( m_logfile != NULL )
        {
            fclose ( m_logfile );
            m_logfile = NULL;
        }

        info( "Unknown exception", "error" );
        throw ;
    }

#if KBOOL_DEBUG
    delete saveme;
#endif

    return true;
}

bool Bool_Engine::StartPolygonAdd( GroupType A_or_B )
{
#if KBOOL_DEBUG
    if ( m_logfile != NULL )
        fprintf( m_logfile, "-> StartPolygonAdd(%d)\n", A_or_B );
#endif
    if ( m_GraphToAdd != NULL )
        return false;

    kbGraph *myGraph = new kbGraph( this );
    m_graphlist->insbegin( myGraph );
    m_GraphToAdd = myGraph;
    m_groupType = A_or_B;

    return true;
}

bool Bool_Engine::AddPoint( double x, double y )
{
    if ( m_GraphToAdd == NULL ){return false;}

    double scaledx = x * m_DGRID * m_GRID;
    double scaledy = y * m_DGRID * m_GRID;

    if ( scaledx > MAXB_INT  || scaledx < MINB_INT )
        error( "X coordinate of vertex to big", "" );
    if ( scaledy > MAXB_INT || scaledy < MINB_INT )
        error( "Y coordinate of vertex to big", "" );


    B_INT rintx = ( ( B_INT ) ( x * m_DGRID ) ) * m_GRID;
    B_INT rinty = ( ( B_INT ) ( y * m_DGRID ) ) * m_GRID;
    kbNode *myNode = new kbNode( rintx, rinty, this );

    // adding first point to graph
    if ( m_firstNodeToAdd == NULL )
    {
#if KBOOL_DEBUG
        if ( m_logfile != NULL )
        {
            fprintf( m_logfile, "-> AddPt() *FIRST* :" );
            fprintf( m_logfile, " input: x = %f, y = %f\n", x, y );
            fprintf( m_logfile, " input: x = %I64d, y = %I64d\n", rintx, rinty ) ;
        }
#endif

        m_firstNodeToAdd = ( kbNode * ) myNode;
        m_lastNodeToAdd  = ( kbNode * ) myNode;
    }
    else
    {
#if KBOOL_DEBUG
        if ( m_logfile != NULL )
        {
            fprintf( m_logfile, "-> AddPt():" );
            fprintf( m_logfile, " input: x = %f, y = %f\n", x, y );
            fprintf( m_logfile, " input: x = %I64d, y = %I64d\n", rintx, rinty ) ;
        }
#endif

        m_GraphToAdd->AddLink( m_lastNodeToAdd, myNode );
        m_lastNodeToAdd = ( kbNode * ) myNode;
    }

    return true;
}

bool Bool_Engine::EndPolygonAdd()
{
    if ( m_GraphToAdd == NULL ) {return false;}

    m_GraphToAdd->AddLink( m_lastNodeToAdd, m_firstNodeToAdd );
    m_GraphToAdd->SetGroup( m_groupType );
    m_GraphToAdd = NULL;
    m_lastNodeToAdd  = NULL;
    m_firstNodeToAdd = NULL;

    return true;
}

bool Bool_Engine::StartPolygonGet()
{
    if ( !m_graphlist->empty() )
    {
        m_getGraph = ( kbGraph* ) m_graphlist->headitem();
        m_getLink  = m_getGraph->GetFirstLink();
        m_getNode  = m_getLink->GetBeginNode();
        m_numPtsInPolygon = m_getGraph->GetNumberOfLinks();
        m_numNodesVisited = 0;
        return true;
    }
    else
    {
        return false;
    }
}

bool Bool_Engine::PolygonHasMorePoints()
{
    // see if first point
    if ( m_numNodesVisited == 0 )
    {
        // don't need to touch the m_getNode
        m_numNodesVisited++;
        return true;
    }

    if ( m_numNodesVisited < m_numPtsInPolygon )
    {
        // traverse to the next node
        m_getNode = m_getLink->GetOther( m_getNode );
        m_getLink = m_getLink->Forth( m_getNode );

        m_numNodesVisited++;
        return true;
    }
    else
    {
        return false;
    }
}

void Bool_Engine::EndPolygonGet()
{
    m_graphlist->removehead();
    delete m_getGraph;
}

double Bool_Engine::GetPolygonXPoint()
{
    return m_getNode->GetX() / m_GRID / m_DGRID;
}

double Bool_Engine::GetPolygonYPoint()
{
    return m_getNode->GetY() / m_GRID / m_DGRID;
}

bool Bool_Engine::GetHoleSegment()
{
    if ( m_getLink->GetHole() )
        return true;
    return false;
}

bool Bool_Engine::GetHoleConnectionSegment()
{
    if ( m_getLink->GetHoleLink() )
        return true;
    return false;
}

kbEdgeType Bool_Engine::GetPolygonPointEdgeType()
{
    // see if the point is the beginning of a false edge
    if ( m_getLink->GetHoleLink() )
        return KB_FALSE_EDGE;
    else
        // it's either an outside or inside edge
        if ( m_getLink->GetHole() )
            return KB_INSIDE_EDGE;
        else
            return KB_OUTSIDE_EDGE;
}


void Bool_Engine::Write_Log( string msg1 )
{
    if ( m_logfile == NULL )
        return;

    fprintf( m_logfile, "%s \n", msg1.c_str() );
}

void Bool_Engine::Write_Log( string msg1, string msg2 )
{
    if ( m_logfile == NULL )
        return;

    fprintf( m_logfile, "%s %s\n", msg1.c_str(), msg2.c_str() );
}

void Bool_Engine::Write_Log( string fmt, double dval )
{
    if ( m_logfile == NULL )
        return;

    fprintf( m_logfile, fmt.c_str(), dval );
}

void Bool_Engine::Write_Log( string fmt, B_INT bval )
{
    if ( m_logfile == NULL )
        return;

    fprintf( m_logfile, fmt.c_str(), bval );
}