/*
 * vim: ts=4 sw=4 et tw=0 wm=0
 *
 * libavoid - Fast, Incremental, Object-avoiding Line Router
 *
 * Copyright (C) 2004-2014  Monash University
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * See the file LICENSE.LGPL distributed with the library.
 *
 * Licensees holding a valid commercial license may use this file in
 * accordance with the commercial license agreement provided with the 
 * library.
 *
 * This library 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.  
 *
 * Author(s):  Michael Wybrow
*/

// For M_PI.
// This should be first include for MSVC.
#define _USE_MATH_DEFINES
#include <cmath>

#include <algorithm>
#include <vector>
#include <climits>
#include <cfloat>

#include "libavoid/makepath.h"
#include "libavoid/vertices.h"
#include "libavoid/geometry.h"
#include "libavoid/connector.h"
#include "libavoid/viscluster.h"
#include "libavoid/graph.h"
#include "libavoid/router.h"
#include "libavoid/debug.h"
#include "libavoid/assertions.h"
#include "libavoid/debughandler.h"

//#define ESTIMATED_COST_DEBUG

namespace Avoid {

class ANode
{
    public:
        VertInf* inf;
        double g;        // Gone
        double h;        // Heuristic
        double f;        // Formula f = g + h
        
        ANode *prevNode; // VertInf for the previous ANode.
        int timeStamp;   // Time-stamp used to determine exploration order of
                         // seemingly equal paths during orthogonal routing.

        ANode(VertInf *vinf, int time)
            : inf(vinf),
              g(0),
              h(0),
              f(0),
              prevNode(nullptr),
              timeStamp(time)
        {
        }
        ANode()
            : inf(nullptr),
              g(0),
              h(0),
              f(0),
              prevNode(nullptr),
              timeStamp(-1)
        {
        }
};

class AStarPathPrivate
{
    public:
        AStarPathPrivate()
            : m_available_nodes(),
              m_available_array_size(0),
              m_available_array_index(0),
              m_available_node_index(0)
        {
        }
        ~AStarPathPrivate()
        {
            // Free memory
            for (size_t i = 0; i < m_available_nodes.size(); ++i)
            {
                delete[] m_available_nodes[i];
            }
        }
        // Returns a pointer to an ANode for aStar search, but allocates
        // these in blocks 
        ANode *newANode(const ANode& node, const bool addToPending = true)
        {
            const size_t blockSize = 5000;
            if ((m_available_array_index + 1 > m_available_array_size) ||
                    (m_available_node_index >= blockSize))
            {
                m_available_nodes.push_back(new ANode[blockSize]);
                ++m_available_array_size;
                m_available_node_index = 0;
                m_available_array_index = m_available_array_size - 1;
            }
            
            ANode *nodes = m_available_nodes[m_available_array_index];
            ANode *newNode = &(nodes[m_available_node_index++]);
            *newNode = node;
            if (addToPending)
            {
                node.inf->aStarPendingNodes.push_back(newNode);
            }
            return newNode;
        }
        void search(ConnRef *lineRef, VertInf *src, VertInf *tar, 
                VertInf *start);

    private:
        void determineEndPointLocation(double dist, VertInf *start,
                VertInf *target, VertInf *other, int level);
        double estimatedCost(ConnRef *lineRef, const Point *last,
                const Point& curr) const;

        std::vector<ANode *> m_available_nodes;
        size_t m_available_array_size;
        size_t m_available_array_index;
        size_t m_available_node_index;
 
        // For determining estimated cost target.
        std::vector<VertInf *> m_cost_targets;
        std::vector<unsigned int> m_cost_targets_directions;
        std::vector<double> m_cost_targets_displacements;
};



// This returns the opposite result (>) so that when used with stl::make_heap, 
// the head node of the heap will be the smallest value, rather than the 
// largest.  This saves us from having to sort the heap (and then reorder
// it back into a heap) when getting the next node to examine.  This way we
// get better complexity -- logarithmic pushes and pops to the heap.
//
class ANodeCmp
{
    public:
    ANodeCmp()
    {
    }
bool operator()(const ANode *a, const ANode *b)
{
    // We need to use an epsilon here since otherwise the multiple addition
    // of floating point numbers that makes up the 'f' values cause a problem
    // with routings occasionally being non-deterministic.
    if (fabs(a->f - b->f) > 0.0000001)
    {
        return a->f > b->f;
    }
    if (a->timeStamp != b->timeStamp)
    {
        // Tiebreaker, if two paths have equal cost, then choose the one with
        // the highest timeStamp.  This corresponds to the furthest point
        // explored along the straight-line path.  When exploring we give the
        // directions the following timeStamps; left:1, right:2 and forward:3,
        // then we always try to explore forward first.
        return a->timeStamp < b->timeStamp;
    }
    return false;
}
};


static double Dot(const Point& l, const Point& r)
{
    return (l.x * r.x) + (l.y * r.y);
}

static double CrossLength(const Point& l, const Point& r)
{
    return (l.x * r.y) - (l.y * r.x);
}


// Return the angle between the two line segments made by the
// points p1--p2 and p2--p3.  Return value is in radians.
//
static double angleBetween(const Point& p1, const Point& p2, const Point& p3)
{
    if ((p1.x == p2.x && p1.y == p2.y) || (p2.x == p3.x && p2.y == p3.y))
    {
        // If two of the points are the same, then we can't say anything
        // about the angle between.  Treat them as being collinear.
        return M_PI;
    }

    Point v1(p1.x - p2.x, p1.y - p2.y);
    Point v2(p3.x - p2.x, p3.y - p2.y);

    return fabs(atan2(CrossLength(v1, v2), Dot(v1, v2)));
}


// Construct a temporary Polygon path given several VertInf's for a connector.
//
static void constructPolygonPath(Polygon& connRoute, VertInf *inf2, 
        VertInf *inf3, ANode *inf1Node)
{
    // Don't include colinear points.
    bool simplified = true;

    int routeSize = 2;
    for (ANode *curr = inf1Node; curr != nullptr; curr = curr->prevNode)
    {
        routeSize += 1;
    }
    connRoute.ps.resize(routeSize);
    int arraySize = routeSize;
    connRoute.ps[routeSize - 1] = inf3->point;
    connRoute.ps[routeSize - 2] = inf2->point;
    routeSize -= 3;
    for (ANode *curr = inf1Node; curr != nullptr; curr = curr->prevNode)
    {
        // For connection pins, we stop and don't include the fake shape 
        // center as part of this path.
        bool isConnectionPin = curr->inf->id.isConnectionPin();

        if (!simplified)
        {
            // If this is non-simplified, we don't need to do anything 
            // clever and can simply add the new point.
            connRoute.ps[routeSize] = curr->inf->point;
            routeSize -= 1;
            
            if (isConnectionPin)
            {
                // Stop at the connection pin.
                break;
            }
            continue;
        }
            
        if ((curr == inf1Node) || 
                vecDir(curr->inf->point, connRoute.ps[routeSize + 1], 
                    connRoute.ps[routeSize + 2]) != 0)
        {
            // Add new point if this is the earlier than the last segment
            // and it is not colinear with the other points.  
            // Note, you can't collapse the 'last' segment with previous 
            // segments, or if this just intersects another line you risk 
            // penalising it once for each collapsed line segment.
            connRoute.ps[routeSize] = curr->inf->point;
            routeSize -= 1;
        }
        else
        {
            // The last point is inline with this one, so update it.
            connRoute.ps[routeSize + 1] = curr->inf->point;
        }
            
        if (isConnectionPin)
        {
            // Stop at the connection pin.
            break;
        }
    }

    // If the vector is not filled, move entries to the beginning and 
    // remove the unused end of the vector.
    int diff = routeSize + 1;
    COLA_ASSERT(simplified || (diff == 0));
    if (diff > 0)
    {
        for (int i = diff; i < arraySize; ++i)
        {
            connRoute.ps[i - diff] = connRoute.ps[i];
        }
        connRoute.ps.resize(connRoute.size() - diff);
    }
}

// Used to get an indication of if a diffence is positive (1),
// negative (-1) or no different (0).
static inline int dimDirection(double difference)
{
    if (difference > 0)
    {
        return 1;
    }
    else if (difference < 0)
    {
        return -1;
    }
    return 0;
}

// Given the two points for a new segment of a path (inf2 & inf3)
// as well as the distance between these points (dist), as well as
// possibly the previous point (inf1) [from inf1--inf2], return a
// cost associated with this route.
//
static double cost(ConnRef *lineRef, const double dist, VertInf *inf2, 
        VertInf *inf3, ANode *inf1Node)
{
    bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal);
    VertInf *inf1 = (inf1Node) ? inf1Node->inf : nullptr;
    double result = dist;
    Polygon connRoute;

    Router *router = inf2->_router;
    if (inf1 != nullptr)
    {
        const double angle_penalty = router->routingParameter(anglePenalty);
        const double segmt_penalty = router->routingParameter(segmentPenalty);

        // This is not the first segment, so there is a bend
        // between it and the last one in the existing path.
        if ((angle_penalty > 0) || (segmt_penalty > 0))
        {
            Point p1 = inf1->point;
            Point p2 = inf2->point;
            Point p3 = inf3->point;

            double rad = M_PI - angleBetween(p1, p2, p3);

            if ((rad > 0) && !isOrthogonal)
            {
                // Make `xval' between 0--10 then take its log so small
                // angles are not penalised as much as large ones.
                //
                double xval = rad * 10 / M_PI;
                double yval = xval * log10(xval + 1) / 10.5;
                result += (angle_penalty * yval);
                //db_printf("deg from straight: %g\tpenalty: %g\n",
                //        rad * 180 / M_PI, (angle_penalty * yval));
            }

            if (rad == M_PI)
            {
                // Needs to double back
                result += (2 * segmt_penalty);
            }
            else if (rad > 0)
            {
                // Only penalise as an extra segment if the two 
                // segments are not collinear.
                result += segmt_penalty;
            }
        }
    }

    const double cluster_crossing_penalty = 
            router->routingParameter(clusterCrossingPenalty);
    // XXX: Clustered routing doesn't yet work with orthogonal connectors.
    if (router->ClusteredRouting && !router->clusterRefs.empty() &&
            (cluster_crossing_penalty > 0))
    {
        if (connRoute.empty())
        {
            constructPolygonPath(connRoute, inf2, inf3, inf1Node);
        }
        // There are clusters so do cluster routing.
        for (ClusterRefList::const_iterator cl = router->clusterRefs.begin(); 
                cl != router->clusterRefs.end(); ++cl)
        {
            Polygon cBoundary = (isOrthogonal) ?
                    (*cl)->rectangularPolygon() : (*cl)->polygon();
            if (cBoundary.size() <= 2)
            {
                continue;
            }
            COLA_ASSERT(cBoundary.ps[0] != cBoundary.ps[cBoundary.size() - 1]);
            for (size_t j = 0; j < cBoundary.size(); ++j)
            {
                // Non-orthogonal cluster boundary points should correspond to 
                // shape vertices and hence already be in the list of vertices.
                COLA_ASSERT(isOrthogonal || 
                        router->vertices.getVertexByPos(cBoundary.at(j)));
            }
            
            bool isConn = false;
            Polygon dynamic_conn_route(connRoute);
            const bool finalSegment = (inf3 == lineRef->dst());
            ConnectorCrossings cross(cBoundary, isConn, dynamic_conn_route);
            cross.checkForBranchingSegments = true;
            cross.countForSegment(connRoute.size() - 1, finalSegment);
            
            result += (cross.crossingCount * cluster_crossing_penalty);
        }
    }
    
    // This penalty penalises route segments that head in a direction opposite
    // of the direction(s) toward the target point.  
    const double reversePenalty = router->routingParameter(
            reverseDirectionPenalty);
    if (reversePenalty)
    {
        // X and Y direction of destination from source point.
        const Point& srcPoint = lineRef->src()->point;
        const Point& dstPoint = lineRef->dst()->point;
        int xDir = dimDirection(dstPoint.x - srcPoint.x);
        int yDir = dimDirection(dstPoint.y - srcPoint.y);
        
        bool doesReverse = false;

        if ((xDir != 0) && 
                (-xDir == dimDirection(inf3->point.x - inf2->point.x)))
        {
            // Connector has an X component and the segment heads in the 
            // opposite direction.
            doesReverse |= true;
        }

        if ((yDir != 0) && 
                (-yDir == dimDirection(inf3->point.y - inf2->point.y)))
        {
            // Connector has an Y component and the segment heads in the 
            // opposite direction.
            doesReverse |= true;
        }

        if (doesReverse)
        {
            result += reversePenalty;
        }
    }

    if (!router->isInCrossingPenaltyReroutingStage())
    {
        // Return here if we are not in the post-processing stage 
        return result;
    }

    const double crossing_penalty = router->routingParameter(crossingPenalty);
    const double shared_path_penalty = 
            router->routingParameter(fixedSharedPathPenalty);
    if ((shared_path_penalty > 0) || (crossing_penalty > 0))
    {
        if (connRoute.empty())
        {
            constructPolygonPath(connRoute, inf2, inf3, inf1Node);
        }
        ConnRefList::const_iterator curr, finish = router->connRefs.end();
        for (curr = router->connRefs.begin(); curr != finish; ++curr)
        {
            ConnRef *connRef = *curr;

            if (connRef->id() == lineRef->id())
            {
                continue;
            }
            const Avoid::PolyLine& route2 = connRef->displayRoute();
            
            bool isConn = true;
            Polygon dynamic_route2(route2);
            Polygon dynamic_conn_route(connRoute);
            const bool finalSegment = (inf3->point == lineRef->dst()->point);
            ConnectorCrossings cross(dynamic_route2, isConn, 
                    dynamic_conn_route, connRef, lineRef);
            cross.checkForBranchingSegments = true;
            cross.countForSegment(connRoute.size() - 1, finalSegment);

            if ((cross.crossingFlags & CROSSING_SHARES_PATH) &&
                    (cross.crossingFlags & CROSSING_SHARES_FIXED_SEGMENT) &&
                    (router->routingOption(
                            penaliseOrthogonalSharedPathsAtConnEnds) || 
                     !(cross.crossingFlags & CROSSING_SHARES_PATH_AT_END))) 
            {
                // Penalise unnecessary shared paths in the middle of
                // connectors.
                result += shared_path_penalty;
            }
            result += (cross.crossingCount * crossing_penalty);
        }
    }

    return result;
}

// Directions for estimated orthgonal cost, as bitflags.
static const unsigned int CostDirectionN = 1;
static const unsigned int CostDirectionE = 2;
static const unsigned int CostDirectionS = 4;
static const unsigned int CostDirectionW = 8;

#ifdef ESTIMATED_COST_DEBUG
static void printDirections(FILE *fp, unsigned int directions)
{
    if (directions & CostDirectionN)
    {
        fprintf(fp, "N ");
    }
    if (directions & CostDirectionE)
    {
        fprintf(fp, "E ");
    }
    if (directions & CostDirectionS)
    {
        fprintf(fp, "S ");
    }
    if (directions & CostDirectionW)
    {
        fprintf(fp, "W ");
    }
}
#endif

// Returns the number of directions for the argument.
static unsigned int orthogonalDirectionsCount(const unsigned int directions)
{
    unsigned int count = 0;
    if (directions & CostDirectionN)
    {
        ++count;
    }
    if (directions & CostDirectionE)
    {
        ++count;
    }
    if (directions & CostDirectionS)
    {
        ++count;
    }
    if (directions & CostDirectionW)
    {
        ++count;
    }
    return count;
}

// Returns the directions of point b from point a.
static unsigned int orthogonalDirection(const Point &a, const Point &b)
{
    unsigned int result = 0;

    if (b.y > a.y)
    {
        result |= CostDirectionS;
    }
    else if (b.y < a.y)
    {
        result |= CostDirectionN;
    }

    if (b.x > a.x)
    {
        result |= CostDirectionE;
    }
    else if (b.x < a.x)
    {
        result |= CostDirectionW;
    }

    return result;
}

// Returns the direction to the right of the given direction.
static unsigned int dirRight(unsigned int direction)
{
    if (direction == CostDirectionN)
    {
        return CostDirectionE;
    }
    else if (direction == CostDirectionE)
    {
        return CostDirectionS;
    }
    else if (direction == CostDirectionS)
    {
        return CostDirectionW;
    }
    else if (direction == CostDirectionW)
    {
        return CostDirectionN;
    }
    
    // Should not be possible to reach here.
    COLA_ASSERT(false);
    return direction;
}

// Returns the direction to the left of the given direction.
static unsigned int dirLeft(unsigned int direction)
{
    if (direction == CostDirectionN)
    {
        return CostDirectionW;
    }
    else if (direction == CostDirectionE)
    {
        return CostDirectionN;
    }
    else if (direction == CostDirectionS)
    {
        return CostDirectionE;
    }
    else if (direction == CostDirectionW)
    {
        return CostDirectionS;
    }
    
    // Should not be possible to reach here.
    COLA_ASSERT(false);
    return direction;
}

// Returns the reverse direction to the given direction.
static unsigned int dirReverse(unsigned int direction)
{
    if (direction == CostDirectionN)
    {
        return CostDirectionS;
    }
    else if (direction == CostDirectionE)
    {
        return CostDirectionW;
    }
    else if (direction == CostDirectionS)
    {
        return CostDirectionN;
    }
    else if (direction == CostDirectionW)
    {
        return CostDirectionE;
    }
    
    // Should not be possible to reach here.
    COLA_ASSERT(false);
    return direction;
}

// Given Point curr with a direction of currDir, returns the nimimum number 
// of bends to reach Point dest with the entry direction of destDir
// 
// This is used for estimating the bend penalty cost to the target point
// from the current point of the search. The geometry was described in the 
// "Orthogonal Connector Routing" paper, although the version described 
// there is incorrect.
//
int bends(const Point& curr, unsigned int currDir, const Point& dest,
        unsigned int destDir)
{
        // Bend counts from 'o' to 'D' should be:
        //
        //                1            1            3
        //                v            v            v
        //            2 > o < 2    2 > o < 2    4 > o < 2
        //                ^            ^            ^
        //                3            3            3
        //                                      
        //   0 > o < 4                 D-->             4 > o < 4
        //       ^                                          ^
        //       1                                          3
        //
    COLA_ASSERT(currDir != 0);
    unsigned int currToDestDir = orthogonalDirection(curr, dest);
    unsigned int reverseDestDir = dirReverse(destDir);
    bool currDirPerpendicularToDestDir =
            (currDir == dirLeft(destDir)) || (currDir == dirRight(destDir));

    if ((currDir == destDir) &&
             (currToDestDir == currDir))
    {
        //                                      
        //   0 > o                     D--> 
        //
        return 0;
    }
    else if (currDirPerpendicularToDestDir &&
             (currToDestDir == (destDir | currDir)))
    {
        //
        //                1
        //                v
        //                o
        //                 
        //                                      
        //                             D-->
        //
        return 1;
    }
    else if (currDirPerpendicularToDestDir &&
             (currToDestDir == currDir))
    {
        //
        //                             1
        //                             v
        //                             o
        //                 
        //                                      
        //                             D-->
        //
        return 1;
    }
    else if (currDirPerpendicularToDestDir &&
             (currToDestDir == destDir))
    {
        //
        //       o                     D-->
        //       ^
        //       1
        //
        return 1;
    }
    else if ((currDir == destDir) && 
             (currToDestDir != currDir) &&
             !(currToDestDir & reverseDestDir))
    {
        //
        //            2 > o        2 > o
        //
        //                                      
        //                             D-->
        //
        return 2;
    }
    else if (currDir == reverseDestDir && 
             (currToDestDir != destDir) &&
             (currToDestDir != currDir))
    {
        //
        //                o < 2        o < 2       o < 2
        //
        //                                      
        //                             D-->
        //
        return 2;
    }
    else if (currDirPerpendicularToDestDir &&
             (currToDestDir != (destDir | currDir)) && 
             (currToDestDir != currDir))
    {
        //
        //                                          3
        //                                          v
        //                o            o            o
        //                ^            ^            ^
        //                3            3            3
        //                                      
        //                             D-->                 o
        //                                                  ^
        //                                                  3
        //
        return 3;
    }
    else if ((currDir == reverseDestDir) && 
             ((currToDestDir == destDir) || (currToDestDir == currDir)))
    {
        //
        //                                      
        //                                      
        //       o < 4                 D-->                 o < 4
        //
        return 4;
    }
    else if ((currDir == destDir) && 
             (currToDestDir & reverseDestDir))
    {
        //
        //                                      4 > o
        //                                      
        //                                      
        //                             D-->             4 > o
        //
        return 4;
    }

    // Should not be possible to reach here.
    COLA_ASSERT(false);
    return 0;
}


static double estimatedCostSpecific(ConnRef *lineRef, const Point *last,
        const Point& curr, const VertInf *costTar,
        const unsigned int costTarDirs)
{
    Point costTarPoint = costTar->point;

    if (lineRef->routingType() == ConnType_PolyLine)
    {
        return euclideanDist(curr, costTarPoint);
    }
    else // Orthogonal
    {
        // Really doesn't make sense to route orthogonal paths without 
        // a segment penalty.
        COLA_ASSERT(lineRef->router()->routingParameter(segmentPenalty) > 0);

        double dist = manhattanDist(curr, costTarPoint);

        int bendCount = 0;
        double xmove = costTarPoint.x - curr.x;
        double ymove = costTarPoint.y - curr.y;
        if (last == nullptr)
        {
            // This is just the initial point.  Penalise it simply if it is 
            // not inline with the target in either the x- or y-dimension.
            if ((xmove != 0) && (ymove != 0))
            {
                bendCount += 1;
            }
        }
        else if (dist > 0)
        {
            // We have two points and a non-zero distance, so we know 
            // the segment direction.

            unsigned int currDir = orthogonalDirection(*last, curr);
            if ((currDir > 0) && (orthogonalDirectionsCount(currDir) == 1))
            {
                // Suitably high value, then we find the minimum.
                bendCount = 10;

                // Find the minimum bent penalty given all the possible 
                // directions at the target point.
                if (costTarDirs & CostDirectionN)
                {
                    bendCount = std::min(bendCount,
                            bends(curr, currDir, costTarPoint, CostDirectionN));
                }
                if (costTarDirs & CostDirectionE)
                {
                    bendCount = std::min(bendCount,
                            bends(curr, currDir, costTarPoint, CostDirectionE));
                }
                if (costTarDirs & CostDirectionS)
                {
                    bendCount = std::min(bendCount,
                            bends(curr, currDir, costTarPoint, CostDirectionS));
                }
                if (costTarDirs & CostDirectionW)
                {
                    bendCount = std::min(bendCount,
                            bends(curr, currDir, costTarPoint, CostDirectionW));
                }
            }
        }
        double penalty = bendCount *
                lineRef->router()->routingParameter(segmentPenalty);

        return dist + penalty;
    }
}



double AStarPathPrivate::estimatedCost(ConnRef *lineRef, const Point *last,
        const Point& curr) const
{
    double estimate = DBL_MAX;
    COLA_ASSERT(m_cost_targets.size() > 0);

    // Find the minimum cost from the estimates to each of the possible
    // target points from this current point.
    for (size_t i = 0; i < m_cost_targets.size(); ++i)
    {
        double iEstimate = estimatedCostSpecific(lineRef, last,
                curr, m_cost_targets[i], m_cost_targets_directions[i]);
        
        // Add on the distance to the real target, otherwise this difference
        // might may make the comparisons unfair if they vary between targets.
        iEstimate += m_cost_targets_displacements[i];
        
        estimate = std::min(estimate, iEstimate);
    }
    return estimate;
}


class CmpVisEdgeRotation 
{
    public:
        CmpVisEdgeRotation(const VertInf* lastPt)
            : _lastPt(lastPt)
        {
        }
        bool operator() (const EdgeInf* u, const EdgeInf* v) const 
        {
            // Dummy ShapeConnectionPin edges are not orthogonal and 
            // therefore can't be compared in the same way.
            if (u->isOrthogonal() && v->isOrthogonal())
            {
                return u->rotationLessThan(_lastPt, v);
            }
            return u < v;
        }
    private:
        const VertInf *_lastPt;
};


static inline bool pointAlignedWithOneOf(const Point& point, 
        const std::vector<Point>& points, const size_t dim)
{
    for (size_t i = 0; i < points.size(); ++i)
    {
        if (point[dim] == points[i][dim])
        {
            return true;
        }
    }
    return false;
}

AStarPath::AStarPath(void)
    : m_private(new AStarPathPrivate())
{
}

AStarPath::~AStarPath(void)
{
    delete m_private;
}

void AStarPath::search(ConnRef *lineRef, VertInf *src, VertInf *tar, VertInf *start)
{
    m_private->search(lineRef, src, tar, start);
}

void AStarPathPrivate::determineEndPointLocation(double dist, VertInf *start, 
        VertInf *target, VertInf *other, int level)
{
    COLA_UNUSED(dist);
    COLA_UNUSED(start);
    COLA_UNUSED(level);

    Point otherPoint = other->point;
    unsigned int thisDirs = orthogonalDirection(otherPoint, target->point);
    COLA_ASSERT(orthogonalDirectionsCount(thisDirs) > 0);
    double displacement = manhattanDist(otherPoint, target->point);

    m_cost_targets.push_back(other);
    m_cost_targets_directions.push_back(thisDirs);
    m_cost_targets_displacements.push_back(displacement);

#ifdef ESTIMATED_COST_DEBUG
    fprintf(stderr," - %g %g ", otherPoint.x, otherPoint.y);
    if (manhattanDist(start->point, otherPoint) > dist)
    {
        fprintf(stderr,"far ");
    }
    fprintf(stderr, "%s", (level == 1) ? "--" : "- ");
    printDirections(stderr, thisDirs);
    fprintf(stderr,"\n");
#endif
}

// Returns the best path from src to tar using the cost function.
//
// The path is worked out using the aStar algorithm, and is encoded via
// prevNode values for each ANode which point back to the previous ANode.
// At completion, this order is written into the pathNext links in each 
// of the VerInfs along the path.
//
// The aStar STL code is originally based on public domain code available 
// on the internet.
//
void AStarPathPrivate::search(ConnRef *lineRef, VertInf *src, VertInf *tar, VertInf *start)
{
    ANodeCmp pendingCmp;

    bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal);

    if (start == nullptr)
    {
        start = src;
    }

#ifdef DEBUGHANDLER
    if (lineRef->router()->debugHandler())
    {
        lineRef->router()->debugHandler()->beginningSearchWithEndpoints(start, tar);
    }
#endif

    // Find a target point to use for cost estimate for orthogonal routing.
    //
    // If the connectivity is only on the far side we need to estimate to the
    // point on the far side.  Otherwise for orthogonal routing we can explore
    // all the space in between before we pay the extra cost to explore this
    // area.  This is especially true given many orthogonal routes have 
    // equivalent costs.
#ifdef ESTIMATED_COST_DEBUG
    fprintf(stderr,"== aStar  %g %g ==\n", tar->point.x, tar->point.y);
#endif
    if (isOrthogonal && tar->id.isConnPt() && !tar->id.isConnCheckpoint())
    {
        // The target is a connector endpoint and the connector is orthogonal.
        double dist = manhattanDist(start->point, tar->point);
        for (EdgeInfList::const_iterator it = tar->orthogVisList.begin(); 
                it != tar->orthogVisList.end(); ++it)
        {
            // For each edge from the target endpoint, find the other vertex.
            EdgeInf *edge = *it;
            VertInf *other = edge->otherVert(tar);
            if (other->id.isConnectionPin())
            {
                // If this is a connection pin we need to do this process
                // another time since the current edge will be a dummy 
                // zero-length edge.
                VertInf *replacementTar = other;
                for (EdgeInfList::const_iterator it = 
                        replacementTar->orthogVisList.begin(); 
                        it != replacementTar->orthogVisList.end(); ++it)
                {
                    EdgeInf *edge = *it;
                    VertInf *other = edge->otherVert(replacementTar);
                    if ((other == tar) || 
                            (other->point == tar->point))
                    {
                        // Ignore edge we came from, or zero-length edges.
                        continue;
                    }

                    // Determine possible target endpoint directions and 
                    // position.
                    determineEndPointLocation(dist, start, replacementTar,
                            other, 2);
                }
                continue;
            }
                    
            // Determine possible target endpoint directions and position.
            determineEndPointLocation(dist, start, tar, other, 1);
        }
    }


    if (m_cost_targets.empty())
    {
        m_cost_targets.push_back(tar);
        // For polyline routing, assume target has visibility is all 
        // directions for the purpose of cost estimations.
        m_cost_targets_directions.push_back(CostDirectionN |
                CostDirectionE | CostDirectionS | CostDirectionW);
        m_cost_targets_displacements.push_back(0.0);
    }

#ifdef ESTIMATED_COST_DEBUG
    fprintf(stderr, "------------\n");
    for (size_t i = 0; i < m_cost_targets.size(); ++i)
    {
        fprintf(stderr,"== %g %g - ", m_cost_targets[i]->point.x,
                m_cost_targets[i]->point.y);
        printDirections(stderr, m_cost_targets_directions[i]);
        fprintf(stderr,"\n");
    }
#endif


    double (*dist)(const Point& a, const Point& b) = 
        (isOrthogonal) ? manhattanDist : euclideanDist;

    // We need to know the possible endpoints for doing an orthogonal 
    // routing optimisation where we only turn when we are heading beside
    // a shape or are in line with a possible endpoint.
    std::vector<Point> endPoints;
    if (isOrthogonal)
    {
        endPoints = lineRef->possibleDstPinPoints();
    }
    endPoints.push_back(tar->point);
    
    // Heap of PENDING nodes.
    std::vector<ANode *> PENDING;
    PENDING.reserve(1000);

    size_t exploredCount = 0;
    ANode node, ati;
    ANode *bestNode = nullptr;         // Temporary bestNode
    bool bNodeFound = false;        // Flag if node is found in container
    int timestamp = 1;

    Router *router = lineRef->router();
    if (router->RubberBandRouting && (start != src))
    {
        COLA_ASSERT(router->IgnoreRegions == true);
        
        const PolyLine& currRoute = lineRef->route();
        VertInf *last = nullptr;
        int rIndx = 0;
        while (last != start)
        {
            const Point& pnt = currRoute.at(rIndx);
            VertIDProps props = (rIndx > 0) ? 0 : VertID::PROP_ConnPoint;
            VertID vID(pnt.id, pnt.vn, props);

#ifdef PATHDEBUG
            db_printf("/// %d %d\n", pnt.id, pnt.vn);
#endif
            VertInf *curr = router->vertices.getVertexByID(vID);
            COLA_ASSERT(curr != nullptr);

            node = ANode(curr, timestamp++);
            if (!last)
            {
                node.inf = src;
                node.g = 0;
                node.h = estimatedCost(lineRef, nullptr, node.inf->point);

                node.f = node.g + node.h;
            }
            else
            {
                double edgeDist = dist(bestNode->inf->point, curr->point);

                node.g = bestNode->g + cost(lineRef, edgeDist, bestNode->inf, 
                        node.inf, bestNode->prevNode);

                // Calculate the Heuristic.
                node.h = estimatedCost(lineRef, &(bestNode->inf->point),
                        node.inf->point);

                // The A* formula
                node.f = node.g + node.h;
                
                // Point parent to last bestNode
                node.prevNode = bestNode;
            }

            if (curr != start)
            {
                bool addToPending = false;
                bestNode = newANode(node, addToPending);
                bestNode->inf->aStarDoneNodes.push_back(bestNode);
                ++exploredCount;
            }
            else
            {
                ANode * newNode = newANode(node);
                PENDING.push_back(newNode);
            }

            rIndx++;
            last = curr;
        }
    }
    else
    {
        if (start->pathNext)
        {
            // If we are doing checkpoint routing and have already done one
            // path, then we have an existing segment to consider for the 
            // cost of the  choice from the start node, so we add a dummy 
            // nodes as if they were already in the Done set.  This causes 
            // us to first search in a collinear direction from the previous 
            // segment.
            bool addToPending = false;
            bestNode = newANode(ANode(start->pathNext, timestamp++), 
                    addToPending);
            bestNode->inf->aStarDoneNodes.push_back(bestNode);
            ++exploredCount;
        }

        // Create the start node
        node = ANode(src, timestamp++);
        node.g = 0;
        node.h = estimatedCost(lineRef, nullptr, node.inf->point);
        node.f = node.g + node.h;
        // Set a nullptr parent, so cost function knows this is the first segment.
        node.prevNode = bestNode;

        // Populate the PENDING container with the first location
        ANode *newNode = newANode(node);
        PENDING.push_back(newNode);
    }

    tar->pathNext = nullptr;

    // Create a heap from PENDING for sorting
    using std::make_heap; using std::push_heap; using std::pop_heap;
    make_heap( PENDING.begin(), PENDING.end(), pendingCmp);

    // Continue until the queue is empty.
    while (!PENDING.empty())
    {
        TIMER_VAR_ADD(router, 0, 1);
        // Set the Node with lowest f value to BESTNODE.
        // Since the ANode operator< is reversed, the head of the
        // heap is the node with the lowest f value.
        bestNode = PENDING.front();
        VertInf *bestNodeInf = bestNode->inf;

#ifdef DEBUGHANDLER
        if (router->debugHandler())
        {
            PolyLine currentSearchPath;

            ANode *curr = bestNode;
            while (curr)
            {
                currentSearchPath.ps.push_back(curr->inf->point);
                curr = curr->prevNode;
            }
            router->debugHandler()->updateCurrentSearchPath(currentSearchPath);
        }
#endif

        // Remove this node from the aStarPendingList
        std::list<ANode *>::iterator finishIt = 
                bestNodeInf->aStarPendingNodes.end();
        for (std::list<ANode *>::iterator currInd = 
                bestNodeInf->aStarPendingNodes.begin(); currInd != finishIt; 
                ++currInd)
        {
            if (*currInd == bestNode)
            {
                bestNodeInf->aStarPendingNodes.erase(currInd);
                break;
            }
        }

        // Pop off the heap.  Actually this moves the
        // far left value to the far right.  The node
        // is not actually removed since the pop is to
        // the heap and not the container.
        pop_heap(PENDING.begin(), PENDING.end(), pendingCmp);
        // Remove node from right (the value we pop_heap'd)
        PENDING.pop_back();

        // Add the bestNode into the Done set.
        bestNodeInf->aStarDoneNodes.push_back(bestNode);
        ++exploredCount;

        VertInf *prevInf = (bestNode->prevNode) ? bestNode->prevNode->inf : nullptr;
#ifdef ASTAR_DEBUG
        db_printf("Considering... ");
        db_printf(" %g %g  ", bestNodeInf->point.x, bestNodeInf->point.y);
        bestNodeInf->id.db_print();
        db_printf(" - g: %3.1f h: %3.1f back: ", bestNode->g, bestNode->h);
        if (prevInf)
        {
            db_printf(" %g %g", prevInf->point.x, prevInf->point.y);
            //prevInf->id.db_print();
        }
        db_printf("\n");
#endif

        if (bestNodeInf == tar)
        {
            TIMER_VAR_ADD(router, 1, PENDING.size());
            // This node is our goal.
#ifdef ASTAR_DEBUG
            db_printf("LINE %10d  Steps: %4d  Cost: %g\n", lineRef->id(), 
                    (int) exploredCount, bestNode->f);
#endif
     
            // Correct all the pathNext pointers.
            for (ANode *curr = bestNode; curr->prevNode; curr = curr->prevNode)
            {
#ifdef ASTAR_DEBUG
                db_printf("[%.12f, %.12f]\n", curr->inf->point.x, curr->inf->point.y);
#endif
                curr->inf->pathNext = curr->prevNode->inf;
            }
#ifdef ASTAR_DEBUG
            db_printf("\n");
#endif

            // Exit from the search
            break;
        }

        // Check adjacent points in graph and add them to the queue.
        EdgeInfList& visList = (!isOrthogonal) ?
                bestNodeInf->visList : bestNodeInf->orthogVisList;
        if (isOrthogonal)
        {
            // We would like to explore in a structured way, 
            // so sort the points in the visList...
            CmpVisEdgeRotation compare(prevInf);
            visList.sort(compare);
        }
        EdgeInfList::const_iterator finish = visList.end();
        for (EdgeInfList::const_iterator edge = visList.begin(); 
                edge != finish; ++edge)
        {
            if ((*edge)->isDisabled())
            {
                // Skip disabled edges.
                continue;
            }

            node = ANode((*edge)->otherVert(bestNodeInf), timestamp++);
            
            // Set the index to the previous ANode that we reached
            // this ANode via.
            node.prevNode = bestNode;

            VertInf *prevInf = (bestNode->prevNode) ?
                    bestNode->prevNode->inf : nullptr;

            // Don't bother looking at the segment we just arrived along.
            if (prevInf && (prevInf == node.inf))
            {
                continue;
            }
            if (node.inf->id.isConnectionPin() && 
                    !node.inf->id.isConnCheckpoint())
            {
                if ( !( (bestNodeInf == lineRef->src()) &&
                        lineRef->src()->id.isDummyPinHelper()
                      ) &&
                     !( node.inf->hasNeighbour(lineRef->dst(), isOrthogonal) &&
                        lineRef->dst()->id.isDummyPinHelper())
                      )
                {
                    // Don't check connection pins if they don't have the 
                    // target vertex as a direct neighbour, or are directly
                    // leaving the source vertex.
                    continue;
                }
            }
            else if (node.inf->id.isConnPt())
            {
                if ((node.inf != tar))
                {
                    // Don't check connector endpoints vertices unless they
                    // are the target endpoint.
                    continue;
                }
            }

            if (isOrthogonal && !(*edge)->isDummyConnection())
            {
                // Orthogonal routing optimisation.
                // Skip the edges that don't lead to shape edges, or the 
                // connection point we are looking for.  Though allow them
                // if we haven't yet turned from the source point, since it
                // may be a free-floating endpoint with directional visibility.
                // Also, don't check if the previous point was a dummy for a
                // connection pin and this happens to be placed diagonally 
                // from here, i.e., when both of notInline{X,Y} are true.
                Point& bestPt = bestNodeInf->point;
                Point& nextPt = node.inf->point;

                bool notInlineX = prevInf && (prevInf->point.x != bestPt.x);
                bool notInlineY = prevInf && (prevInf->point.y != bestPt.y);
                if ((bestPt.x == nextPt.x) && notInlineX && !notInlineY &&
                        (bestPt[YDIM] != src->point[YDIM]))
                {
                    if (nextPt.y < bestPt.y)
                    {
                        if (!(bestNodeInf->orthogVisPropFlags & YL_EDGE) &&
                                !pointAlignedWithOneOf(bestPt, endPoints, XDIM))
                        {
                            continue;
                        }
                    }
                    else if (nextPt.y > bestPt.y)
                    {
                        if (!(bestNodeInf->orthogVisPropFlags & YH_EDGE) &&
                                !pointAlignedWithOneOf(bestPt, endPoints, XDIM))
                        {
                            continue;
                        }
                    }
                }
                if ((bestPt.y == nextPt.y) && notInlineY && !notInlineX &&
                        (bestPt[XDIM] != src->point[XDIM]))
                {
                    if (nextPt.x < bestPt.x)
                    {
                        if (!(bestNodeInf->orthogVisPropFlags & XL_EDGE) &&
                                !pointAlignedWithOneOf(bestPt, endPoints, YDIM))
                        {
                            continue;
                        }
                    }
                    else if (nextPt.x > bestPt.x)
                    {
                        if (!(bestNodeInf->orthogVisPropFlags & XH_EDGE) &&
                                !pointAlignedWithOneOf(bestPt, endPoints, YDIM))
                        {
                            continue;
                        }
                    }
                }
            }

            double edgeDist = (*edge)->getDist();

            if (edgeDist == 0)
            {
                continue;
            }

            if (!isOrthogonal &&
                  (!router->RubberBandRouting || (start == src)) && 
                  (validateBendPoint(prevInf, bestNodeInf, node.inf) == false))
            {
                // The bendpoint is not valid, i.e., is a zigzag corner, so...
                continue;
                // For RubberBand routing we want to allow these routes and
                // unwind them later, otherwise instead or unwinding, paths
                // can go the *really* long way round.
            }

            // Figure out if we are at one of the cost targets.
            bool atCostTarget = false;
            for (size_t i = 0; i < m_cost_targets.size(); ++i)
            {
                if (bestNode->inf == m_cost_targets[i])
                        
                {
                    atCostTarget = true;
                    break;
                }
            }

            if (atCostTarget &&
                    (node.inf->id.isConnectionPin() || (node.inf == tar)))
            {
                // This is a point on the side of an obstacle that connects
                // to the target or a connection pin.  It should have no 
                // further cost and the heuristic should be zero.
                node.g = bestNode->g;
                node.h = 0;
            }
            else 
            {
                if (node.inf == tar)
                {
                    // We've reached the target.  The heuristic should be zero.
                    node.h = 0;
                }
                else
                {
                    // Otherwise, calculate the heuristic value.
                    node.h = estimatedCost(lineRef, &(bestNodeInf->point),
                            node.inf->point);
                }
                
                if (node.inf->id.isDummyPinHelper())
                {
                    // This is connecting to a connection pin helper vertex.
                    // There should be no additional cost for this step.
                    node.g = bestNode->g;
                }
                else
                {
                    // Otherwise, calculate the cost of this step.
                    node.g = bestNode->g + cost(lineRef, edgeDist, bestNodeInf, 
                            node.inf, bestNode->prevNode);
                }
            }

            // The A* formula
            node.f = node.g + node.h;

#ifdef ASTAR_DEBUG
            db_printf("-- Adding: %g %g  ", node.inf->point.x, 
                    node.inf->point.y);
            node.inf->id.db_print();
            db_printf(" - g: %3.1f h: %3.1f \n", node.g, node.h);
#endif

            bNodeFound = false;

    
            // Check to see if already on PENDING
            std::list<ANode *>::const_iterator finish = node.inf->aStarPendingNodes.end();
            for (std::list<ANode *>::const_iterator currInd = 
                    node.inf->aStarPendingNodes.begin(); currInd != finish; ++currInd)
            {
                ati = **currInd;
                // The (node.prevNode == ati.prevNode) is redundant, but may
                // save checking the mosre costly prevNode->inf test if the
                // Nodes are the same.
                if ((node.inf == ati.inf) && 
                        ((node.prevNode == ati.prevNode) ||
                         (node.prevNode->inf == ati.prevNode->inf)))
                {
                    // If already on PENDING
                    if (node.g < ati.g)
                    {
                        // Replace the existing node in PENDING
                        **currInd = node;
                        make_heap( PENDING.begin(), PENDING.end(), pendingCmp);
                    }
                    bNodeFound = true;
                    break;
                }
            }
            if ( !bNodeFound ) // If Node NOT found on PENDING
            {
                // Check to see if it is already in the Done set for this
                // vertex.
                for (std::list<ANode *>::const_iterator currInd = 
                        node.inf->aStarDoneNodes.begin();
                        currInd != node.inf->aStarDoneNodes.end(); ++currInd)
                {
                    ati = **currInd;
                    // The (node.prevNode == ati.prevNode) is redundant, but may
                    // save checking the mosre costly prevNode->inf test if the
                    // Nodes are the same.
                    if ((node.inf == ati.inf) && ati.prevNode &&
                            ((node.prevNode == ati.prevNode) ||
                             (node.prevNode->inf == ati.prevNode->inf)))
                    {
                        //COLA_ASSERT(node.g >= (ati.g - 10e-10));
                        // This node is already in the Done set and the 
                        // current node also has a higher g-value, so we 
                        // don't need to consider this node.
                        bNodeFound = true;
                        break;
                    }
                }
            }

            if (!bNodeFound ) // If Node NOT in either Pending or Done.
            {
                // Push NewNode onto PENDING
                ANode *newNode = newANode(node);
                PENDING.push_back(newNode);
                // Push NewNode onto heap
                push_heap( PENDING.begin(), PENDING.end(), pendingCmp);

#if 0
                using std::cout; using std::endl;
                // Display PENDING container (For Debugging)
                cout << "PENDING:   ";
                for (unsigned int i = 0; i < PENDING.size(); i++)
                {
                    cout << PENDING[i]->g << "," << PENDING[i]->h << ",";
                    cout << PENDING[i]->inf << "," << PENDING[i]->pp << "  ";
                }
                cout << endl << endl;
#endif
            }
        }
    }

    // Cleanup lists used to store Done and Pending sets for each vertex.
    VertInf *endVert = router->vertices.end();
    for (VertInf *k = router->vertices.connsBegin(); k != endVert;
            k = k->lstNext)
    {
        k->aStarDoneNodes.clear();
        k->aStarPendingNodes.clear();
    }
}


}


