/*
 * KiRouter - a push-and-(sometimes-)shove PCB router
 *
 * Copyright (C) 2013  CERN
 * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * 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 3 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, see <http://www.gnu.or/licenses/>.
 */

#include <deque>
#include <cassert>

#include <wx/timer.h>

#include "trace.h"

#include "pns_line.h"
#include "pns_node.h"
#include "pns_walkaround.h"
#include "pns_shove.h"
#include "pns_optimizer.h"
#include "pns_via.h"
#include "pns_utils.h"

#include <profile.h>

using namespace std;

PNS_SHOVE::PNS_SHOVE( PNS_NODE* aWorld )
{
    m_root = aWorld;
    m_iterLimit = 100;
};


PNS_SHOVE::~PNS_SHOVE()
{
}


struct range
{
    range()
    {
        min_v = max_v = -1;
    }

    void add( int x )
    {
        if( min_v < 0 ) min_v = x;

        if( max_v < 0 ) max_v = x;

        if( x < min_v )
            min_v = x;
        else if( x > max_v )
            max_v = x;
    }

    int start()
    {
        return min_v;
    }

    int end()
    {
        return max_v;
    }

    int min_v, max_v;
};

// fixme: this is damn f***ing inefficient. And fails much too often due to broken direction finding algorithm.
bool PNS_SHOVE::tryShove( PNS_NODE* aNode, PNS_LINE* aHead, PNS_LINE* aObstacle,
        PNS_SEGMENT& aObstacleSeg, PNS_LINE* aResult, bool aInvertWinding )
{
    const SHAPE_LINE_CHAIN& head = aHead->GetCLine();
    bool cw = false;
    int i;

    if( aHead->EndsWithVia() && !aHead->GetLayers().Overlaps( aObstacle->GetLayers() ) )
    {
        int clearance = aNode->GetClearance( aHead, aObstacle );
        SHAPE_LINE_CHAIN hull = aHead->GetVia().Hull( clearance - aObstacle->GetWidth() / 2 );

        // SHAPE_LINE_CHAIN path_pre, path_walk_cw, path_walk_ccw, path_post;

        SHAPE_LINE_CHAIN path_cw, path_ccw, * path;

        aObstacle->NewWalkaround( hull, path_cw, true );
        aObstacle->NewWalkaround( hull, path_ccw, false );

        path = path_ccw.Length() < path_cw.Length() ? &path_ccw : &path_cw;
        aResult->SetShape( *path );

        // PNSDisplayDebugLine (*path, 5);

        if( !aResult->Is45Degree() )
        {
            // printf("polyset non-45\npoly %s\nendpolyset\n", aResult->GetCLine().Format().c_str());
        }

        /*... special case for vias? */

        return !aNode->CheckColliding( aResult, aHead );
    }

    int ns = head.SegmentCount();

    if( aHead->EndsWithVia() )
        ns++;

    for( i = 0; i < head.SegmentCount(); i++ )
    {
        const PNS_SEGMENT hs( *aHead, head.CSegment( i ) );


        if( aNode->CheckColliding( &hs, aObstacle ) )
        {
            VECTOR2I v1 = hs.GetSeg().b - hs.GetSeg().a;
            VECTOR2I v2 = aObstacleSeg.GetSeg().b - aObstacleSeg.GetSeg().a;

            VECTOR2I::extended_type det = v1.Cross( v2 );

            if( det > 0 )
                cw = true;
            else
                cw = false;

            break;
        }
    }

    if( aInvertWinding )
    {
        if( cw )
            cw = false;
        else
            cw = true;
    }

    PNS_LINE shoved( *aObstacle );

    int clearance = aNode->GetClearance( aHead, aObstacle );

    range r;

    for( i = 0; i < ns; i++ )
    {
        SHAPE_LINE_CHAIN hull;

        if( i < head.SegmentCount() )
        {
            const PNS_SEGMENT hs( *aHead, head.CSegment( i ) );
            hull = hs.Hull( clearance, 0 );
        }
        else
            hull = aHead->GetVia().Hull( clearance - aObstacle->GetWidth() / 2 );

        SHAPE_LINE_CHAIN path_pre, path_walk, path_post, tmp;
        SHAPE_LINE_CHAIN path_pre2, path_walk2, path_post2;

        // shoved.NewWalkaround(hull, path_pre, path_walk, path_post, cw);
        shoved.NewWalkaround( hull, path_pre, path_walk, path_post, cw );

        /*if(path_pre != path_pre2 || path_post != path_post2 || path_walk != path_walk2 )
         *  {
         *   TRACE(5, "polyset orig\npoly %s\npoly %s\npoly %s\nendpolyset\n", path_pre.Format().c_str() % path_walk.Format().c_str() % path_post.Format().c_str());
         *   TRACE(5, "polyset err\npoly %s\npoly %s\npoly %s\nendpolyset\n", path_pre2.Format().c_str() % path_walk2.Format().c_str() % path_post2.Format().c_str());
         *  }*/

        tmp = shoved.GetCLine();

        if( path_walk.SegmentCount() )
            r.add( i );

        path_pre.Append( path_walk );
        path_pre.Append( path_post );
        path_pre.Simplify();
        shoved.SetShape( path_pre );
// shoved.SetAffectedRange ( start, end );
        *aResult = shoved;

        if( !aResult->Is45Degree() )
        {
            // TRACE(5, "polyset non-45\npoly %s\npoly %s\npoly %s\nendpolyset\n", tmp.Format().c_str() % hull.Format().c_str() % aResult->GetCLine().Format().c_str());
        }
    }

    TRACE( 2, "CW %d affectedRange %d-%d [total %d]", (cw ? 1 : 0) % r.start() % r.end() % ns );

    return !aNode->CheckColliding( aResult, aHead );
}


PNS_SHOVE::ShoveStatus PNS_SHOVE::shoveSingleLine( PNS_NODE* aNode, PNS_LINE* aCurrent,
        PNS_LINE* aObstacle, PNS_SEGMENT& aObstacleSeg, PNS_LINE* aResult )
{
    bool rv = tryShove( aNode, aCurrent, aObstacle, aObstacleSeg, aResult, false );

    if( !rv )
        rv = tryShove( aNode, aCurrent, aObstacle, aObstacleSeg, aResult, true );

    if( !rv )
    {
        TRACEn( 2, "Shove failed" );
        return SH_INCOMPLETE;
    }

    aResult->GetLine().Simplify();

    const SHAPE_LINE_CHAIN& sh_shoved = aResult->GetCLine();
    const SHAPE_LINE_CHAIN& sh_orig = aObstacle->GetCLine();

    if( sh_shoved.SegmentCount() > 1 && sh_shoved.CPoint( 0 ) == sh_orig.CPoint( 0 )
        && sh_shoved.CPoint( -1 ) == sh_orig.CPoint( -1 ) )
        return SH_OK;
    else if( !sh_shoved.SegmentCount() )
        return SH_NULL;
    else
        return SH_INCOMPLETE;
}


bool PNS_SHOVE::reduceSpringback( PNS_LINE* aHead )
{
    bool rv = false;

    while( !m_nodeStack.empty() )
    {
        SpringbackTag st_stack = m_nodeStack.back();
        bool tail_ok = true;

        if( !st_stack.node->CheckColliding( aHead ) && tail_ok )
        {
            rv = true;
            delete st_stack.node;
            m_nodeStack.pop_back();
        }
        else
            break;
    }

    return rv;
}


bool PNS_SHOVE::pushSpringback( PNS_NODE* aNode, PNS_LINE* aHead, const PNS_COST_ESTIMATOR& aCost )
{
    BOX2I headBB = aHead->GetCLine().BBox();
    SpringbackTag st;

    st.node = aNode;
    st.cost = aCost;
    st.length = std::max( headBB.GetWidth(), headBB.GetHeight() );;
    m_nodeStack.push_back( st );
    return true;
}


const PNS_COST_ESTIMATOR PNS_SHOVE::TotalCost() const
{
    if( m_nodeStack.empty() )
        return PNS_COST_ESTIMATOR();
    else
        return m_nodeStack.back().cost;
}


PNS_SHOVE::ShoveStatus PNS_SHOVE::ShoveLines( PNS_LINE* aCurrentHead )
{
    stack <PNS_LINE*> lineStack;
    PNS_NODE* node, * parent;
    PNS_VIA*    headVia = NULL;
    bool    fail    = false;
    int     iter    = 0;

    PNS_LINE* head = aCurrentHead->Clone();

    reduceSpringback( aCurrentHead );

    parent = m_nodeStack.empty() ? m_root : m_nodeStack.back().node;
    node = parent->Branch();

    lineStack.push( head );

    // node->Add(tail);
    node->Add( head );

    if( head->EndsWithVia() )
    {
        headVia = head->GetVia().Clone();
        node->Add( headVia );
    }

    PNS_OPTIMIZER optimizer( node );

    optimizer.SetEffortLevel( PNS_OPTIMIZER::MERGE_SEGMENTS | PNS_OPTIMIZER::SMART_PADS );
    optimizer.SetCollisionMask( -1 );
    PNS_NODE::OptObstacle nearest;

    optimizer.CacheStaticItem( head );

    if( headVia )
        optimizer.CacheStaticItem( headVia );

    TRACE( 1, "ShoveStart [root: %d jts, node: %d jts]", m_root->JointCount() %
            node->JointCount() );

    // PNS_ITEM *lastWalkSolid = NULL;
    prof_counter totalRealTime;

    wxLongLong t_start = wxGetLocalTimeMillis();

    while( !lineStack.empty() )
    {
        wxLongLong t_cur = wxGetLocalTimeMillis();

        if( (t_cur - t_start).ToLong() > ShoveTimeLimit )
        {
            fail = true;
            break;
        }

        iter++;

        if( iter > m_iterLimit )
        {
            fail = true;
            break;
        }

        PNS_LINE* currentLine = lineStack.top();

        prof_start( &totalRealTime, false );
        nearest = node->NearestObstacle( currentLine, PNS_ITEM::ANY );
        prof_end( &totalRealTime );

        TRACE( 2, "t-nearestObstacle %lld us", (totalRealTime.value ) );

        if( !nearest )
        {
            if( lineStack.size() > 1 )
            {
                PNS_LINE* original = lineStack.top();
                PNS_LINE optimized;
                int r_start, r_end;

                original->GetAffectedRange( r_start, r_end );

                TRACE( 1, "Iter %d optimize-line [range %d-%d, total %d]",
                        iter % r_start % r_end % original->GetCLine().PointCount() );
                // lastWalkSolid = NULL;
                prof_start( &totalRealTime, false );

                if( optimizer.Optimize( original, &optimized ) )
                {
                    node->Remove( original );
                    optimizer.CacheRemove( original );
                    node->Add( &optimized );

                    if( original->BelongsTo( node ) )
                        delete original;
                }

                prof_end( &totalRealTime );

                TRACE( 2, "t-optimizeObstacle %lld us", (totalRealTime.value ) );
            }

            lineStack.pop();
        }
        else
        {
            switch( nearest->item->GetKind() )
            {
            case PNS_ITEM::SEGMENT:
                {
                    TRACE( 1, "Iter %d shove-line", iter );

                    PNS_SEGMENT* pseg = static_cast<PNS_SEGMENT*>(nearest->item);
                    PNS_LINE* collidingLine = node->AssembleLine( pseg );
                    PNS_LINE* shovedLine = collidingLine->CloneProperties();

                    prof_start( &totalRealTime, false );
                    ShoveStatus st = shoveSingleLine( node, currentLine, collidingLine,
                            *pseg, shovedLine );
                    prof_end( &totalRealTime );

                    TRACE( 2, "t-shoveSingle %lld us", (totalRealTime.value ) );

                    if( st == SH_OK )
                    {
                        node->Replace( collidingLine, shovedLine );

                        if( collidingLine->BelongsTo( node ) )
                            delete collidingLine;

                        optimizer.CacheRemove( collidingLine );
                        lineStack.push( shovedLine );
                    }
                    else
                        fail = true;

                    // lastWalkSolid = NULL;

                    break;
                }    // case SEGMENT

            case PNS_ITEM::SOLID:
            case PNS_ITEM::VIA:
                {
                    TRACE( 1, "Iter %d walkaround-solid [%p]", iter % nearest->item );

                    if( lineStack.size() == 1 )
                    {
                        fail = true;
                        break;
                    }

/*					if(lastWalkSolid == nearest->item)
 *                   {
 *                       fail = true;
 *                       break;
 *                   }*/

                    PNS_WALKAROUND walkaround( node );
                    PNS_LINE* walkaroundLine = currentLine->CloneProperties();

                    walkaround.SetSolidsOnly( true );
                    walkaround.SetSingleDirection( true );

                    prof_start( &totalRealTime, false );
                    walkaround.Route( *currentLine, *walkaroundLine, false );
                    prof_end( &totalRealTime );

                    TRACE( 2, "t-walkSolid %lld us", (totalRealTime.value ) );


                    node->Replace( currentLine, walkaroundLine );

                    if( currentLine->BelongsTo( node ) )
                        delete currentLine;

                    optimizer.CacheRemove( currentLine );
                    lineStack.top() = walkaroundLine;

                    // lastWalkSolid = nearest->item;
                    break;
                }

            default:
                break;
            }    // switch

            if( fail )
                break;
        }
    }

    node->Remove( head );
    delete head;

    if( headVia )
    {
        node->Remove( headVia );
        delete headVia;
    }

    TRACE( 1, "Shove status : %s after %d iterations", (fail ? "FAILED" : "OK") % iter );

    if( !fail )
    {
        pushSpringback( node, aCurrentHead, PNS_COST_ESTIMATOR() );
        return SH_OK;
    }
    else
    {
        delete node;
        return SH_INCOMPLETE;
    }
}

