/*
    * Copyright The KiCad Developers.
    * Copyright (C) 2024 Fabien Corona f.corona<at>laposte.net
    *
    * 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, you may find one here:
    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    * or you may search the http://www.gnu.org website for the version 2 license,
    * or you may write to the Free Software Foundation, Inc.,
    * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
    */

#include <drc/drc_creepage_utils.h>
#include <geometry/intersection.h>
#include <thread_pool.h>


extern bool segmentIntersectsArc( const VECTOR2I& p1, const VECTOR2I& p2, const VECTOR2I& center,
                                  double radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle,
                                  std::vector<VECTOR2I>* aIntersectPoints );


//Check if line segments 'p1q1' and 'p2q2' intersect, excluding endpoint overlap

bool segments_intersect( VECTOR2I p1, VECTOR2I q1, VECTOR2I p2, VECTOR2I q2,
                         std::vector<VECTOR2I>* aIntersectPoints )
{
    if( p1 == p2 || p1 == q2 || q1 == p2 || q1 == q2 )
        return false;

    SEG segment1( p1, q1 );
    SEG segment2( p2, q2 );

    std::vector<VECTOR2I> intersectionPoints;


    INTERSECTABLE_GEOM geom1 = segment1;
    INTERSECTABLE_GEOM geom2 = segment2;

    INTERSECTION_VISITOR visitor( geom2, intersectionPoints );

    std::visit( visitor, geom1 );

    if( aIntersectPoints )
    {
        for( VECTOR2I& point : intersectionPoints )
            aIntersectPoints->push_back( point );
    }


    return intersectionPoints.size() > 0;
}


bool compareShapes( const CREEP_SHAPE* a, const CREEP_SHAPE* b )
{
    if( !a )
        return true;
    if( !b )
        return false;

    if( a->GetType() != b->GetType() )
    {
        return a->GetType() < b->GetType();
    }

    if( a->GetType() == CREEP_SHAPE::TYPE::UNDEFINED )
        return true;

    auto posA = a->GetPos();
    auto posB = b->GetPos();

    if( posA != posB )
    {
        return posA < posB;
    }
    if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE )
    {
        return a->GetRadius() < b->GetRadius();
    }
    return false;
}

bool areEquivalent( const CREEP_SHAPE* a, const CREEP_SHAPE* b )
{
    if( !a && !b )
    {
        return true;
    }
    if( ( !a && b ) || ( a && !b ) )
    {
        return false;
    }
    if( a->GetType() != b->GetType() )
    {
        return false;
    }
    if( a->GetType() == CREEP_SHAPE::TYPE::POINT )
    {
        return a->GetPos() == b->GetPos();
    }
    if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE )
    {
        return a->GetPos() == b->GetPos() && ( a->GetRadius() == b->GetRadius() );
    }
    return false;
}


std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
                                                    double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    double weight = ( this->GetPos() - aS2.GetPos() ).SquaredEuclideanNorm();

    if( weight > aMaxSquaredWeight )
        return result;

    PATH_CONNECTION pc;
    pc.a1 = this->GetPos();
    pc.a2 = aS2.GetPos();
    pc.weight = sqrt( weight );

    result.push_back( pc );
    return result;
}

std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                    double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    int                          radius = aS2.GetRadius();
    VECTOR2I                     pointPos = this->GetPos();
    VECTOR2I                     circleCenter = aS2.GetPos();

    if( radius <= 0 )
    {
        return result;
    }

    double pointToCenterDistanceSquared = ( pointPos - circleCenter ).SquaredEuclideanNorm();
    double weightSquared = pointToCenterDistanceSquared - (float) radius * (float) radius;

    if( weightSquared > aMaxSquaredWeight )
        return result;


    VECTOR2D direction1 = VECTOR2D( pointPos.x - circleCenter.x, pointPos.y - circleCenter.y );
    direction1 = direction1.Resize( 1 );

    VECTOR2D direction2 = direction1.Perpendicular();

    double radiusSquared = double( radius ) * double( radius );

    double distance = sqrt( pointToCenterDistanceSquared );
    double value1 = radiusSquared / distance;
    double value2 = sqrt( radiusSquared - value1 * value1 );

    VECTOR2D resultPoint;

    PATH_CONNECTION pc;
    pc.a1 = pointPos;
    pc.weight = sqrt( weightSquared );

    resultPoint = direction1 * value1 + direction2 * value2 + circleCenter;
    pc.a2.x = int( resultPoint.x );
    pc.a2.y = int( resultPoint.y );
    result.push_back( pc );

    resultPoint = direction1 * value1 - direction2 * value2 + circleCenter;
    pc.a2.x = int( resultPoint.x );
    pc.a2.y = int( resultPoint.y );
    result.push_back( pc );

    return result;
}

std::pair<bool, bool>
BE_SHAPE_ARC::IsThereATangentPassingThroughPoint( const BE_SHAPE_POINT aPoint ) const
{
    std::pair<bool, bool> result;
    double                R = m_radius;

    VECTOR2I newPoint = aPoint.GetPos() - m_pos;

    if( newPoint.SquaredEuclideanNorm() <= R * R )
    {
        // If the point is inside the arc
        result.first = false;
        result.second = false;
        return result;
    }

    EDA_ANGLE testAngle = AngleBetweenStartAndEnd( aPoint.GetPos() );

    double startAngle = m_startAngle.AsRadians();
    double endAngle = m_endAngle.AsRadians();
    double pointAngle = testAngle.AsRadians();

    bool greaterThan180 = ( m_endAngle - m_startAngle ) > EDA_ANGLE( 180 );
    bool connectToEndPoint;

    connectToEndPoint = ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y >= R );

    if( greaterThan180 )
        connectToEndPoint &= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R );

    connectToEndPoint |= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R )
                         && ( pointAngle >= endAngle || pointAngle <= startAngle );


    result.first = !connectToEndPoint;

    connectToEndPoint = ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y >= R );

    if( greaterThan180 )
        connectToEndPoint &=
                ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R );

    connectToEndPoint |= ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R )
                         && ( pointAngle >= endAngle || pointAngle <= startAngle );


    result.second = !connectToEndPoint;
    return result;
}

std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                    double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     center = aS2.GetPos();
    double                       radius = aS2.GetRadius();

    // First path tries to connect to start point
    // Second path tries to connect to end point
    std::pair<bool, bool> behavesLikeCircle;
    behavesLikeCircle = aS2.IsThereATangentPassingThroughPoint( *this );

    if( behavesLikeCircle.first && behavesLikeCircle.second )
    {
        BE_SHAPE_CIRCLE csc( center, radius );
        return this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
    }

    if( behavesLikeCircle.first )
    {
        BE_SHAPE_CIRCLE              csc( center, radius );
        std::vector<PATH_CONNECTION> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );

        if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections
        {
            result.push_back( paths[1] );
        }
    }
    else
    {
        BE_SHAPE_POINT csp1( aS2.GetStartPoint() );

        for( PATH_CONNECTION pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) )
        {
            result.push_back( pc );
        }
    }
    if( behavesLikeCircle.second )
    {
        BE_SHAPE_CIRCLE              csc( center, radius );
        std::vector<PATH_CONNECTION> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );

        if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections
        {
            result.push_back( paths[0] );
        }
    }
    else
    {
        BE_SHAPE_POINT csp1( aS2.GetEndPoint() );

        for( PATH_CONNECTION pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) )
        {
            result.push_back( pc );
        }
    }
    return result;
}

std::vector<PATH_CONNECTION> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     circleCenter = this->GetPos();
    double                       circleRadius = this->GetRadius();
    VECTOR2I                     arcCenter = aS2.GetPos();
    double                       arcRadius = aS2.GetRadius();
    EDA_ANGLE                    arcStartAngle = aS2.GetStartAngle();
    EDA_ANGLE                    arcEndAngle = aS2.GetEndAngle();

    double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm();

    if( centerDistance + arcRadius < circleRadius )
    {
        // The arc is inside the circle
        return result;
    }

    BE_SHAPE_POINT  csp1( aS2.GetStartPoint() );
    BE_SHAPE_POINT  csp2( aS2.GetEndPoint() );
    BE_SHAPE_CIRCLE csc( arcCenter, arcRadius );


    for( PATH_CONNECTION pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 - arcCenter );

        if( pointAngle <= aS2.GetEndAngle() )
            result.push_back( pc );
    }

    if( result.size() == 4 )
    {
        // It behaved as a circle
        return result;
    }

    for( BE_SHAPE_POINT csp : { csp1, csp2 } )
    {
        for( PATH_CONNECTION pc : this->Paths( csp, aMaxWeight, aMaxSquaredWeight ) )
        {
            if( !segmentIntersectsArc( pc.a1, pc.a2, arcCenter, arcRadius, arcStartAngle,
                                       arcEndAngle, nullptr ) )
                result.push_back( pc );
        }
    }


    return result;
}


std::vector<PATH_CONNECTION> BE_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                  double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     circleCenter = this->GetPos();
    double                       circleRadius = this->GetRadius();
    VECTOR2I                     arcCenter = aS2.GetPos();
    double                       arcRadius = aS2.GetRadius();

    double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm();

    if( centerDistance + arcRadius < circleRadius )
    {
        // The arc is inside the circle
        return result;
    }

    BE_SHAPE_POINT  csp1( aS2.GetStartPoint() );
    BE_SHAPE_POINT  csp2( aS2.GetEndPoint() );
    BE_SHAPE_CIRCLE csc( arcCenter, arcRadius );


    for( PATH_CONNECTION pc : this->Paths( BE_SHAPE_CIRCLE( aS2.GetPos(), aS2.GetRadius() ),
                                           aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 - arcCenter );

        if( pointAngle <= aS2.GetEndAngle() )
            result.push_back( pc );
    }

    for( PATH_CONNECTION pc : BE_SHAPE_CIRCLE( this->GetPos(), this->GetRadius() )
                                      .Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE pointAngle = this->AngleBetweenStartAndEnd( pc.a2 - arcCenter );

        if( pointAngle <= this->GetEndAngle() )
            result.push_back( pc );
    }

    return result;
}


std::vector<PATH_CONNECTION> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    VECTOR2I p1 = this->GetPos();
    VECTOR2I p2 = aS2.GetPos();

    VECTOR2D distSquared( double( ( p2 - p1 ).x ), double( ( p2 - p1 ).y ) );
    double   weightSquared = distSquared.SquaredEuclideanNorm();

    double R1 = this->GetRadius();
    double R2 = aS2.GetRadius();

    double Rdiff = abs( R1 - R2 );
    double Rsum = R1 + R2;

    // "Straight" paths
    double weightSquared1 = weightSquared - Rdiff * Rdiff;
    // "Crossed" paths
    double weightSquared2 = weightSquared - Rsum * Rsum;

    if( weightSquared1 <= aMaxSquaredWeight )
    {
        VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y );
        direction1 = direction1.Resize( 1 );
        VECTOR2D direction2 = direction1.Perpendicular();

        double D = sqrt( weightSquared );
        double ratio1 = ( R1 - R2 ) / D;
        double ratio2 = sqrt( 1 - ratio1 * ratio1 );


        PATH_CONNECTION pc;
        pc.weight = sqrt( weightSquared1 );

        pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2;
        pc.a2 = p2 + direction1 * R2 * ratio1 + direction2 * R2 * ratio2;

        result.push_back( pc );

        pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2;
        pc.a2 = p2 + direction1 * R2 * ratio1 - direction2 * R2 * ratio2;

        result.push_back( pc );
    }
    if( weightSquared2 <= aMaxSquaredWeight )
    {
        VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y );
        direction1 = direction1.Resize( 1 );
        VECTOR2D direction2 = direction1.Perpendicular();

        double D = sqrt( weightSquared );
        double ratio1 = ( R1 + R2 ) / D;
        double ratio2 = sqrt( 1 - ratio1 * ratio1 );


        PATH_CONNECTION pc;
        pc.weight = sqrt( weightSquared2 );

        pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2;
        pc.a2 = p2 - direction1 * R2 * ratio1 - direction2 * R2 * ratio2;

        result.push_back( pc );

        pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2;
        pc.a2 = p2 - direction1 * R2 * ratio1 + direction2 * R2 * ratio2;

        result.push_back( pc );
    }

    return result;
}


void CREEPAGE_GRAPH::TransformCreepShapesToNodes( std::vector<CREEP_SHAPE*>& aShapes )
{
    for( CREEP_SHAPE* p1 : aShapes )
    {
        if( !p1 )
            continue;

        switch( p1->GetType() )
        {
        case CREEP_SHAPE::TYPE::POINT: AddNode( GRAPH_NODE::TYPE::POINT, p1, p1->GetPos() ); break;
        case CREEP_SHAPE::TYPE::CIRCLE: AddNode( GRAPH_NODE::TYPE::CIRCLE, p1, p1->GetPos() ); break;
        case CREEP_SHAPE::TYPE::ARC: AddNode( GRAPH_NODE::TYPE::ARC, p1, p1->GetPos() ); break;
        default: break;
        }
    }
}

void CREEPAGE_GRAPH::RemoveDuplicatedShapes()
{
    // Sort the vector
    sort( m_shapeCollection.begin(), m_shapeCollection.end(), compareShapes );
    std::vector<CREEP_SHAPE*> newVector;

    size_t i = 0;

    for( i = 0; i < m_shapeCollection.size() - 1; i++ )
    {
        if( m_shapeCollection[i] == nullptr )
            continue;

        if( areEquivalent( m_shapeCollection[i], m_shapeCollection[i + 1] ) )
        {
            delete m_shapeCollection[i];
            m_shapeCollection[i] = nullptr;
        }
        else
        {
            newVector.push_back( m_shapeCollection[i] );
        }
    }

    if( m_shapeCollection[i] )
        newVector.push_back( m_shapeCollection[i] );

    std::swap( m_shapeCollection, newVector );
}

void CREEPAGE_GRAPH::TransformEdgeToCreepShapes()
{
    for( BOARD_ITEM* drawing : m_boardEdge )
    {
        PCB_SHAPE* d = dynamic_cast<PCB_SHAPE*>( drawing );

        if( !d )
            continue;

        switch( d->GetShape() )
        {
        case SHAPE_T::SEGMENT:
        {
            BE_SHAPE_POINT* a = new BE_SHAPE_POINT( d->GetStart() );
            m_shapeCollection.push_back( a );
            a = new BE_SHAPE_POINT( d->GetEnd() );
            m_shapeCollection.push_back( a );
            break;
        }
        case SHAPE_T::RECTANGLE:
        {
            BE_SHAPE_POINT* a = new BE_SHAPE_POINT( d->GetStart() );
            m_shapeCollection.push_back( a );
            a = new BE_SHAPE_POINT( d->GetEnd() );
            m_shapeCollection.push_back( a );
            a = new BE_SHAPE_POINT( VECTOR2I( d->GetEnd().x, d->GetStart().y ) );
            m_shapeCollection.push_back( a );
            a = new BE_SHAPE_POINT( VECTOR2I( d->GetStart().x, d->GetEnd().y ) );
            m_shapeCollection.push_back( a );
            break;
        }
        case SHAPE_T::POLY:
        {
            std::vector<VECTOR2I> points;
            d->DupPolyPointsList( points );

            for( auto p : points )
            {
                BE_SHAPE_POINT* a = new BE_SHAPE_POINT( p );
                m_shapeCollection.push_back( a );
            }
            break;
        }
        case SHAPE_T::CIRCLE:
        {
            BE_SHAPE_CIRCLE* a = new BE_SHAPE_CIRCLE( d->GetCenter(), d->GetRadius() );
            a->SetParent( d );
            m_shapeCollection.push_back( a );
            break;
        }

        case SHAPE_T::ARC:
        {
            // If the arc is not locally convex, only use the endpoints
            double   tolerance = 10;
            VECTOR2D center( double( d->GetCenter().x ), double( d->GetCenter().y ) );
            VECTOR2D mid( double( d->GetArcMid().x ), double( d->GetArcMid().y ) );
            VECTOR2D dir( mid - center );
            dir = dir / d->GetRadius() * ( d->GetRadius() - tolerance );

            EDA_ANGLE alpha, beta;
            d->CalcArcAngles( alpha, beta );
            BE_SHAPE_ARC* a = new BE_SHAPE_ARC( d->GetCenter(), d->GetRadius(), alpha, beta,
                                                d->GetStart(), d->GetEnd() );
            a->SetParent( d );

            m_shapeCollection.push_back( a );
            break;
        }
        default: break;
        }
    }
}


std::vector<PCB_SHAPE> GRAPH_CONNECTION::GetShapes()
{
    std::vector<PCB_SHAPE> shapes = std::vector<PCB_SHAPE>();
    int                    lineWidth = 0;

    if( !m_path.m_show )
        return shapes;

    if( !n1 || !n2 )
        return shapes;

    if( n1->m_type == GRAPH_NODE::TYPE::VIRTUAL || n2->m_type == GRAPH_NODE::TYPE::VIRTUAL )
    {
        return shapes;
    }

    if( !m_forceStraightLine && n1->m_parent && ( n1->m_parent == n2->m_parent )
        && ( n1->m_parent->GetType() == CREEP_SHAPE::TYPE::CIRCLE ) )
    {
        VECTOR2I  center = n1->m_parent->GetPos();
        VECTOR2I  R1 = n1->m_pos - center;
        VECTOR2I  R2 = n2->m_pos - center;
        PCB_SHAPE s( nullptr, SHAPE_T::ARC );

        if( R1.Cross( R2 ) > 0 )
        {
            s.SetStart( n1->m_pos );
            s.SetEnd( n2->m_pos );
        }
        else
        {
            s.SetStart( n2->m_pos );
            s.SetEnd( n1->m_pos );
        }
        s.SetCenter( center );


        s.SetWidth( lineWidth );
        s.SetLayer( Eco1_User );

        shapes.push_back( s );
        return shapes;
    }

    else if( !m_forceStraightLine && n1->m_parent && ( n1->m_parent == n2->m_parent )
             && n1->m_parent->GetType() == CREEP_SHAPE::TYPE::ARC )
    {
        BE_SHAPE_ARC* arc = dynamic_cast<BE_SHAPE_ARC*>( n1->m_parent );

        if( !arc )
        {
            PCB_SHAPE s;
            s.SetStart( m_path.a1 );
            s.SetEnd( m_path.a2 );

            s.SetWidth( lineWidth );

            s.SetLayer( Eco1_User );

            shapes.push_back( s );
            return shapes;
        }

        VECTOR2I  center = arc->GetPos();
        VECTOR2I  R1 = n1->m_pos - center;
        VECTOR2I  R2 = n2->m_pos - center;
        PCB_SHAPE s( nullptr, SHAPE_T::ARC );


        if( R1.Cross( R2 ) > 0 )
        {
            s.SetStart( n1->m_pos );
            s.SetEnd( n2->m_pos );
        }
        else
        {
            s.SetStart( n2->m_pos );
            s.SetEnd( n1->m_pos );
        }

        s.SetCenter( center );

        //Check that we are on the correct side of the arc.
        VECTOR2I  mid = s.GetArcMid();
        EDA_ANGLE midAngle = arc->AngleBetweenStartAndEnd( mid );

        if( midAngle > arc->GetEndAngle() )
        {
            VECTOR2I tmp;
            tmp = s.GetStart();
            s.SetStart( s.GetEnd() );
            s.SetEnd( tmp );
            s.SetCenter( center );
        }

        s.SetWidth( lineWidth );
        s.SetLayer( Eco1_User );

        shapes.push_back( s );
        return shapes;
    }

    PCB_SHAPE s;
    s.SetStart( m_path.a1 );
    s.SetEnd( m_path.a2 );
    s.SetWidth( lineWidth );

    shapes.push_back( s );

    return shapes;
}

void CREEP_SHAPE::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>&,
                                   CREEPAGE_GRAPH&              aG ) const
{
}


void BE_SHAPE_POINT::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>&,
                                      CREEPAGE_GRAPH&              aG ) const
{
}

void BE_SHAPE_CIRCLE::ShortenChildDueToGV( std::shared_ptr<GRAPH_NODE>& a1,
                                           std::shared_ptr<GRAPH_NODE>& a2, CREEPAGE_GRAPH& aG,
                                           double aNormalWeight ) const
{
    EDA_ANGLE angle1 = EDA_ANGLE( a1->m_pos - m_pos );
    EDA_ANGLE angle2 = EDA_ANGLE( a2->m_pos - m_pos );

    while( angle1 < ANGLE_0 )
        angle1 += ANGLE_360;
    while( angle2 < ANGLE_0 )
        angle2 += ANGLE_360;
    while( angle1 > ANGLE_360 )
        angle1 -= ANGLE_360;
    while( angle2 > ANGLE_360 )
        angle2 -= ANGLE_360;


    EDA_ANGLE maxAngle = angle1 > angle2 ? angle1 : angle2;
    EDA_ANGLE skipAngle =
            EDA_ANGLE( asin( float( aG.m_minGrooveWidth ) / ( 2 * m_radius ) ), RADIANS_T );
    skipAngle += skipAngle; // Cannot multiply EDA_ANGLE by scalar, but this really is angle *2
    EDA_ANGLE pointAngle = maxAngle - skipAngle;

    VECTOR2I skipPoint = m_pos;
    skipPoint.x += m_radius * cos( pointAngle.AsRadians() );
    skipPoint.y += m_radius * sin( pointAngle.AsRadians() );


    std::shared_ptr<GRAPH_NODE> gnt = aG.AddNode( GRAPH_NODE::POINT, a1->m_parent, skipPoint );

    PATH_CONNECTION pc;

    pc.a1 = maxAngle == angle2 ? a1->m_pos : a2->m_pos;
    pc.a2 = skipPoint;
    pc.weight = aNormalWeight - aG.m_minGrooveWidth;
    aG.AddConnection( maxAngle == angle2 ? a1 : a2, gnt, pc );

    pc.a1 = skipPoint;
    pc.a2 = maxAngle == angle2 ? a2->m_pos : a1->m_pos;
    pc.weight = aG.m_minGrooveWidth;

    std::shared_ptr<GRAPH_CONNECTION> gc = aG.AddConnection( gnt, maxAngle == angle2 ? a2 : a1, pc );

    if( gc )
        gc->m_forceStraightLine = true;
    return;
}

void BE_SHAPE_CIRCLE::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1,
                                       std::shared_ptr<GRAPH_NODE>& a2, CREEPAGE_GRAPH& aG ) const
{
    if( !a1 || !a2 )
        return;

    if( m_radius == 0 )
        return;

    VECTOR2D distI( a1->m_pos - a2->m_pos );
    VECTOR2D distD( double( distI.x ), double( distI.y ) );

    double weight = m_radius * 2 * asin( distD.EuclideanNorm() / ( 2.0 * m_radius ) );

    if( ( weight > aG.GetTarget() ) )
        return;

    if( aG.m_minGrooveWidth <= 0 )
    {
        PATH_CONNECTION pc;
        pc.a1 = a1->m_pos;
        pc.a2 = a2->m_pos;
        pc.weight = std::max( weight, 0.0 );

        aG.AddConnection( a1, a2, pc );
        return;
    }

    if( weight > aG.m_minGrooveWidth )
    {
        ShortenChildDueToGV( a1, a2, aG, weight );
    }
    // Else well.. this paths will be "shorted" by another one
    return;
}


void BE_SHAPE_ARC::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>& a2,
                                    CREEPAGE_GRAPH& aG ) const
{
    if( !a1 || !a2 )
        return;

    EDA_ANGLE angle1 = AngleBetweenStartAndEnd( a1->m_pos );
    EDA_ANGLE angle2 = AngleBetweenStartAndEnd( a2->m_pos );

    double weight = abs( m_radius * ( angle2 - angle1 ).AsRadians() );

    if( true || aG.m_minGrooveWidth <= 0 )
    {
        if( ( weight > aG.GetTarget() ) )
            return;

        PATH_CONNECTION pc;
        pc.a1 = a1->m_pos;
        pc.a2 = a2->m_pos;
        pc.weight = weight;

        aG.AddConnection( a1, a2, pc );
        return;
    }

    if( weight > aG.m_minGrooveWidth )
    {
        ShortenChildDueToGV( a1, a2, aG, weight );
    }
}

void CREEPAGE_GRAPH::SetTarget( double aTarget )
{
    m_creepageTarget = aTarget;
    m_creepageTargetSquared = aTarget * aTarget;
}

bool segmentIntersectsArc( const VECTOR2I& p1, const VECTOR2I& p2, const VECTOR2I& center,
                           double radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle,
                           std::vector<VECTOR2I>* aIntersectPoints )
{
    SEG segment( p1, p2 );

    VECTOR2I startPoint( radius * cos( startAngle.AsRadians() ),
                         radius * sin( startAngle.AsRadians() ) );
    startPoint += center;
    SHAPE_ARC arc( center, startPoint, endAngle - startAngle );

    std::vector<VECTOR2I> intersectionPoints;
    INTERSECTABLE_GEOM    geom1 = segment;
    INTERSECTABLE_GEOM    geom2 = arc;

    INTERSECTION_VISITOR visitor( geom2, intersectionPoints );
    std::visit( visitor, geom1 );

    if( aIntersectPoints )
    {
        for( VECTOR2I& point : intersectionPoints )
            aIntersectPoints->push_back( point );
    }

    return intersectionPoints.size() > 0;
}

std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     start = this->GetStart();
    VECTOR2I                     end = this->GetEnd();
    double                       halfWidth = this->GetWidth() / 2;
    EDA_ANGLE                    trackAngle( end - start );
    VECTOR2I                     pointPos = aS2.GetPos();

    double length = ( start - end ).EuclideanNorm();
    double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - start.x )
                          + sin( trackAngle.AsRadians() ) * ( pointPos.y - start.y );

    VECTOR2I newPoint;

    if( projectedPos <= 0 )
    {
        newPoint = start + ( pointPos - start ).Resize( halfWidth );
    }
    else if( projectedPos >= length )
    {
        newPoint = end + ( pointPos - end ).Resize( halfWidth );
    }
    else
    {
        double posOnSegment = ( start - pointPos ).SquaredEuclideanNorm()
                              - ( end - pointPos ).SquaredEuclideanNorm();
        posOnSegment = posOnSegment / ( 2 * length ) + length / 2;

        newPoint = start + ( end - start ).Resize( posOnSegment );
        newPoint += ( pointPos - newPoint ).Resize( halfWidth );
    }

    double weightSquared = ( pointPos - newPoint ).SquaredEuclideanNorm();

    if( weightSquared > aMaxSquaredWeight )
        return result;

    PATH_CONNECTION pc;
    pc.a1 = newPoint;
    pc.a2 = pointPos;
    pc.weight = sqrt( weightSquared );

    result.push_back( pc );
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     start = this->GetStart();
    VECTOR2I                     end = this->GetEnd();
    double                       halfWidth = this->GetWidth() / 2;

    double    circleRadius = aS2.GetRadius();
    VECTOR2I  circleCenter = aS2.GetPos();
    double    length = ( start - end ).EuclideanNorm();
    EDA_ANGLE trackAngle( end - start );

    double   weightSquared = std::numeric_limits<double>::infinity();
    VECTOR2I PointOnTrack, PointOnCircle;

    // There are two possible paths
    // First the one on the side of the start of the track.
    double projectedPos1 = cos( trackAngle.AsRadians() ) * ( circleCenter.x - start.x )
                           + sin( trackAngle.AsRadians() ) * ( circleCenter.y - start.y );
    double projectedPos2 = projectedPos1 + circleRadius;
    projectedPos1 = projectedPos1 - circleRadius;

    double trackSide = ( end - start ).Cross( circleCenter - start ) > 0 ? 1 : -1;

    if( ( projectedPos1 < 0 && projectedPos2 < 0 ) )
    {
        CU_SHAPE_CIRCLE csc( start, halfWidth );
        for( PATH_CONNECTION pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
        {
            result.push_back( pc );
        }
    }
    else if( ( projectedPos1 > length && projectedPos2 > length ) )
    {
        CU_SHAPE_CIRCLE csc( end, halfWidth );
        for( PATH_CONNECTION pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
        {
            result.push_back( pc );
        }
    }

    else if( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) && ( projectedPos2 >= 0 )
             && ( projectedPos2 <= length ) )
    {
        // Both point connects to the segment part of the track
        PointOnTrack = start;
        PointOnTrack += ( end - start ).Resize( projectedPos1 );
        PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
        PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius );
        weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();

        if( weightSquared < aMaxSquaredWeight )
        {
            PATH_CONNECTION pc;
            pc.a1 = PointOnTrack;
            pc.a2 = PointOnCircle;
            pc.weight = sqrt( weightSquared );

            result.push_back( pc );

            PointOnTrack = start;
            PointOnTrack += ( end - start ).Resize( projectedPos2 );
            PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
            PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius );


            pc.a1 = PointOnTrack;
            pc.a2 = PointOnCircle;

            result.push_back( pc );
        }
    }
    else if( ( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) )
             && ( ( projectedPos2 > length ) || projectedPos2 < 0 ) )
    {
        CU_SHAPE_CIRCLE              csc( end, halfWidth );
        std::vector<PATH_CONNECTION> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

        if( pcs.size() < 2 )
            return result;

        result.push_back( pcs.at( trackSide == 1 ? 1 : 0 ) );


        PointOnTrack = start;
        PointOnTrack += ( end - start ).Resize( projectedPos1 );
        PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
        PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius );
        weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();

        if( weightSquared < aMaxSquaredWeight )
        {
            PATH_CONNECTION pc;
            pc.a1 = PointOnTrack;
            pc.a2 = PointOnCircle;
            pc.weight = sqrt( weightSquared );

            result.push_back( pc );
        }
    }
    else if( ( ( projectedPos2 >= 0 ) && ( projectedPos2 <= length ) )
             && ( ( projectedPos1 > length ) || projectedPos1 < 0 ) )
    {
        CU_SHAPE_CIRCLE              csc( start, halfWidth );
        std::vector<PATH_CONNECTION> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

        if( pcs.size() < 2 )
            return result;

        result.push_back( pcs.at( trackSide == 1 ? 0 : 1 ) );

        PointOnTrack = start;
        PointOnTrack += ( end - start ).Resize( projectedPos2 );
        PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
        PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius );
        weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();

        if( weightSquared < aMaxSquaredWeight )
        {
            PATH_CONNECTION pc;
            pc.a1 = PointOnTrack;
            pc.a2 = PointOnCircle;
            pc.weight = sqrt( weightSquared );

            result.push_back( pc );
        }
    }

    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() );

    for( auto& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );

        if( testAngle < aS2.GetEndAngle() )
        {
            result.push_back( pc );
        }
    }

    if( result.size() < 2 )
    {
        BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
        BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );

        VECTOR2I  beArcPos = aS2.GetPos();
        int       beArcRadius = aS2.GetRadius();
        EDA_ANGLE beArcStartAngle = aS2.GetStartAngle();
        EDA_ANGLE beArcEndAngle = aS2.GetEndAngle();

        for( auto& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );

        for( auto& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );
    }

    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     beArcPos = aS2.GetPos();
    int                          beArcRadius = aS2.GetRadius();
    EDA_ANGLE                    beArcStartAngle = aS2.GetStartAngle();
    EDA_ANGLE                    beArcEndAngle = aS2.GetEndAngle();

    BE_SHAPE_CIRCLE bsc( beArcPos, beArcRadius );

    for( auto& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );

        if( testAngle < aS2.GetEndAngle() )
        {
            result.push_back( pc );
        }
    }

    if( result.size() < 2 )
    {
        BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
        BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );

        for( auto& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );

        for( auto& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );
    }
    return result;
}

std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                  double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    CU_SHAPE_CIRCLE csc( this->GetPos(), this->GetRadius() + this->GetWidth() / 2 );

    for( auto& pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE testAngle = this->AngleBetweenStartAndEnd( pc.a2 );

        if( testAngle < this->GetEndAngle() )
        {
            result.push_back( pc );
        }
    }

    if( result.size() < 2 )
    {
        CU_SHAPE_CIRCLE csc1( this->GetStartPoint(), this->GetWidth() / 2 );
        CU_SHAPE_CIRCLE csc2( this->GetEndPoint(), this->GetWidth() / 2 );

        for( auto& pc : this->Paths( csc1, aMaxWeight, aMaxSquaredWeight ) )
            result.push_back( pc );

        for( auto& pc : this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ) )
            result.push_back( pc );
    }

    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
                                                  double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     beArcPos = aS2.GetPos();
    int                          beArcRadius = aS2.GetRadius();
    EDA_ANGLE                    beArcStartAngle = aS2.GetStartAngle();
    EDA_ANGLE                    beArcEndAngle = aS2.GetEndAngle();

    BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() );

    for( auto& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
    {
        EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );

        if( testAngle < aS2.GetEndAngle() )
        {
            result.push_back( pc );
        }
    }

    if( result.size() < 2 )
    {
        BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
        BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );

        for( auto& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );

        for( auto& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
            if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle,
                                       beArcEndAngle, nullptr ) )
                result.push_back( pc );
    }

    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    double   R = this->GetRadius();
    VECTOR2I center = this->GetPos();
    VECTOR2I point = aS2.GetPos();
    double   weight = ( center - point ).EuclideanNorm() - R;

    if( weight > aMaxWeight )
        return result;

    PATH_CONNECTION pc;
    pc.weight = std::max( weight, 0.0 );
    pc.a2 = point;
    pc.a1 = center + ( point - center ).Resize( R );

    result.push_back( pc );
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    double   R1 = this->GetRadius();
    double   R2 = aS2.GetRadius();
    VECTOR2I C1 = this->GetPos();
    VECTOR2I C2 = aS2.GetPos();

    if( ( C1 - C2 ).SquaredEuclideanNorm() < ( R1 - R2 ) * ( R1 - R2 ) )
    {
        // One of the circles is inside the other
        return result;
    }

    double weight = ( C1 - C2 ).EuclideanNorm() - R1 - R2;

    if( weight > aMaxWeight || weight < 0 )
        return result;

    PATH_CONNECTION pc;
    pc.weight = std::max( weight, 0.0 );
    pc.a1 = ( C2 - C1 ).Resize( R1 ) + C1;
    pc.a2 = ( C1 - C2 ).Resize( R2 ) + C2;
    result.push_back( pc );
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    VECTOR2I s_start = this->GetStart();
    VECTOR2I s_end = this->GetEnd();
    double   halfWidth = this->GetWidth() / 2;

    EDA_ANGLE trackAngle( s_end - s_start );
    VECTOR2I  pointPos = aS2.GetPos();

    double length = ( s_start - s_end ).EuclideanNorm();
    double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - s_start.x )
                          + sin( trackAngle.AsRadians() ) * ( pointPos.y - s_start.y );

    if( ( projectedPos <= 0 ) || ( s_start == s_end ) )
    {
        CU_SHAPE_CIRCLE csc( s_start, halfWidth );
        return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
    }
    if( projectedPos >= length )
    {
        CU_SHAPE_CIRCLE csc( s_end, halfWidth );
        return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
    }

    double radius = aS2.GetRadius();
    double trackSide = ( s_end - s_start ).Cross( pointPos - s_start ) > 0 ? 1 : -1;

    PATH_CONNECTION pc;
    pc.a1 = s_start + ( s_end - s_start ).Resize( projectedPos )
            + ( s_end - s_start ).Perpendicular().Resize( halfWidth ) * trackSide;
    pc.a2 = ( pc.a1 - pointPos ).Resize( radius ) + pointPos;
    pc.weight = ( pc.a2 - pc.a1 ).SquaredEuclideanNorm();

    if( pc.weight <= aMaxSquaredWeight )
    {
        pc.weight = sqrt( pc.weight );
        result.push_back( pc );
    }
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    VECTOR2I circlePos = this->GetPos();
    VECTOR2I arcPos = aS2.GetPos();

    double circleRadius = this->GetRadius();
    double arcRadius = aS2.GetRadius();

    VECTOR2I startPoint = aS2.GetStartPoint();
    VECTOR2I endPoint = aS2.GetEndPoint();

    CU_SHAPE_CIRCLE csc( arcPos, arcRadius + aS2.GetWidth() / 2 );

    if( ( circlePos - arcPos ).EuclideanNorm() > arcRadius + circleRadius )
    {
        std::vector<PATH_CONNECTION> pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );

        if( pcs.size() == 1 )
        {
            EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pcs[0].a2 );

            if( testAngle < aS2.GetEndAngle() )
            {
                result.push_back( pcs[0] );
                return result;
            }
        }
    }

    CU_SHAPE_CIRCLE csc1( startPoint, aS2.GetWidth() / 2 );
    CU_SHAPE_CIRCLE csc2( endPoint, aS2.GetWidth() / 2 );

    PATH_CONNECTION* bestPath = nullptr;


    std::vector<PATH_CONNECTION> pcs1 = this->Paths( csc1, aMaxWeight, aMaxSquaredWeight );
    std::vector<PATH_CONNECTION> pcs2 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight );

    for( PATH_CONNECTION& pc : pcs1 )
    {
        if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) )
            bestPath = &pc;
    }

    for( PATH_CONNECTION& pc : pcs2 )
    {
        if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) )
            bestPath = &pc;
    }

    // If the circle center is insde the arc ring

    PATH_CONNECTION pc3;

    if( ( circlePos - arcPos ).SquaredEuclideanNorm() < arcRadius * arcRadius )
    {
        if( circlePos != arcPos ) // The best path is already found otherwise
        {
            EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( circlePos );

            if( testAngle < aS2.GetEndAngle() )
            {
                pc3.weight = std::max( arcRadius - ( circlePos - arcPos ).EuclideanNorm() - circleRadius, 0.0 );
                pc3.a1 = circlePos + ( circlePos - arcPos ).Resize( circleRadius );
                pc3.a2 = arcPos + ( circlePos - arcPos ).Resize( arcRadius - aS2.GetWidth() / 2 );

                if( !bestPath || ( ( bestPath->weight > pc3.weight ) && ( pc3.weight > 0 ) ) )
                    bestPath = &pc3;
            }
        }
    }

    if( bestPath && bestPath->weight > 0 )
    {
        result.push_back( *bestPath );
    }

    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    VECTOR2I s_start = this->GetStart();
    VECTOR2I s_end = this->GetEnd();
    double   halfWidth1 = this->GetWidth() / 2;

    VECTOR2I arcPos = aS2.GetPos();
    double   arcRadius = aS2.GetRadius();
    double   halfWidth2 = aS2.GetWidth() / 2;


    CU_SHAPE_CIRCLE csc( arcPos, arcRadius + halfWidth2 );

    std::vector<PATH_CONNECTION> pcs;
    pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );

    if( pcs.size() < 1 )
        return result;

    VECTOR2I  circlePoint;
    EDA_ANGLE testAngle;

    if( pcs.size() > 0 )
    {
        circlePoint = pcs[0].a1;
        testAngle = ( aS2.AngleBetweenStartAndEnd( pcs[0].a1 ) );
    }
    if( testAngle < aS2.GetEndAngle() && pcs.size() > 0 )
    {
        result.push_back( pcs[0] );
        return result;
    }

    CU_SHAPE_CIRCLE  csc1( aS2.GetStartPoint(), halfWidth2 );
    CU_SHAPE_CIRCLE  csc2( aS2.GetEndPoint(), halfWidth2 );
    PATH_CONNECTION* bestPath = nullptr;


    std::vector<PATH_CONNECTION> pcs1 = this->Paths( csc1, aMaxWeight, aMaxSquaredWeight );

    for( PATH_CONNECTION& pc : pcs1 )
    {
        if( !bestPath || ( bestPath->weight > pc.weight ) )
        {
            bestPath = &pc;
        }
    }

    std::vector<PATH_CONNECTION> pcs2 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight );

    for( PATH_CONNECTION& pc : pcs2 )
    {
        if( !bestPath || ( bestPath->weight > pc.weight ) )
        {
            bestPath = &pc;
        }
    }

    CU_SHAPE_CIRCLE csc3( s_start, halfWidth1 );
    CU_SHAPE_CIRCLE csc4( s_end, halfWidth1 );

    std::vector<PATH_CONNECTION> pcs3 = csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

    for( PATH_CONNECTION& pc : pcs3 )
    {
        if( !bestPath || ( bestPath->weight > pc.weight ) )
        {
            bestPath = &pc;
        }
    }


    std::vector<PATH_CONNECTION> pcs4 = csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

    for( PATH_CONNECTION& pc : pcs4 )
    {
        if( !bestPath || ( bestPath->weight > pc.weight ) )
        {
            bestPath = &pc;
        }
    }

    if( bestPath )
    {
        result.push_back( *bestPath );
    }

    return result;
}

// Function to compute the projection of point P onto the line segment AB
VECTOR2I closestPointOnSegment( const VECTOR2I& A, const VECTOR2I& B, const VECTOR2I& P )
{
    if( A == B )
        return A;
    if( A == P )
        return A;

    VECTOR2I AB = B - A;
    VECTOR2I AP = P - A;

    double t = float( AB.Dot( AP ) ) / float( AB.SquaredEuclideanNorm() );

    // Clamp t to the range [0, 1] to restrict the projection to the segment
    t = std::max( 0.0, std::min( 1.0, t ) );

    return A + ( AB * t );
}


std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_SEGMENT& aS2,
                                                      double                  aMaxWeight,
                                                      double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    VECTOR2I A( this->GetStart() );
    VECTOR2I B( this->GetEnd() );
    double   halfWidth1 = this->GetWidth() / 2;


    VECTOR2I C( aS2.GetStart() );
    VECTOR2I D( aS2.GetEnd() );
    double   halfWidth2 = aS2.GetWidth() / 2;

    VECTOR2I P1 = closestPointOnSegment( A, B, C );
    VECTOR2I P2 = closestPointOnSegment( A, B, D );
    VECTOR2I P3 = closestPointOnSegment( C, D, A );
    VECTOR2I P4 = closestPointOnSegment( C, D, B );

    // Calculate all possible squared distances between the segments
    double dist1 = ( P1 - C ).SquaredEuclideanNorm();
    double dist2 = ( P2 - D ).SquaredEuclideanNorm();
    double dist3 = ( P3 - A ).SquaredEuclideanNorm();
    double dist4 = ( P4 - B ).SquaredEuclideanNorm();

    // Find the minimum squared distance and update closest points
    double   min_dist = dist1;
    VECTOR2I closest1 = P1;
    VECTOR2I closest2 = C;

    if( dist2 < min_dist )
    {
        min_dist = dist2;
        closest1 = P2;
        closest2 = D;
    }

    if( dist3 < min_dist )
    {
        min_dist = dist3;
        closest1 = A;
        closest2 = P3;
    }

    if( dist4 < min_dist )
    {
        min_dist = dist4;
        closest1 = B;
        closest2 = P4;
    }


    PATH_CONNECTION pc;
    pc.a1 = closest1 + ( closest2 - closest1 ).Resize( halfWidth1 );
    pc.a2 = closest2 + ( closest1 - closest2 ).Resize( halfWidth2 );
    pc.weight = std::max( sqrt( min_dist ) - halfWidth1 - halfWidth2, 0.0 );

    if( pc.weight <= aMaxWeight )
    {
        result.push_back( pc );
    }
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
                                                     double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    double   R1 = this->GetRadius();
    double   R2 = aS2.GetRadius();
    VECTOR2I center1 = this->GetPos();
    VECTOR2I center2 = aS2.GetPos();
    double   dist = ( center1 - center2 ).EuclideanNorm();

    if( dist > aMaxWeight || dist == 0 )
    {
        return result;
    }

    double weight = sqrt( dist * dist - R2 * R2 ) - R1;
    double theta = asin( R2 / dist );
    double psi = acos( R2 / dist );

    if( weight > aMaxWeight )
    {
        return result;
    }

    PATH_CONNECTION pc;
    pc.weight = std::max( weight, 0.0 );

    double circleAngle = EDA_ANGLE( center2 - center1 ).AsRadians();

    VECTOR2I pStart;
    VECTOR2I pEnd;

    pStart = VECTOR2I( R1 * cos( theta + circleAngle ), R1 * sin( theta + circleAngle ) );
    pStart += center1;
    pEnd = VECTOR2I( -R2 * cos( psi - circleAngle ), R2 * sin( psi - circleAngle ) );
    pEnd += center2;


    pc.a1 = pStart;
    pc.a2 = pEnd;
    result.push_back( pc );

    pStart = VECTOR2I( R1 * cos( -theta + circleAngle ), R1 * sin( -theta + circleAngle ) );
    pStart += center1;
    pEnd = VECTOR2I( -R2 * cos( -psi - circleAngle ), R2 * sin( -psi - circleAngle ) );
    pEnd += center2;

    pc.a1 = pStart;
    pc.a2 = pEnd;

    result.push_back( pc );
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
                                                  double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;
    VECTOR2I                     point = aS2.GetPos();
    VECTOR2I                     arcCenter = this->GetPos();

    double radius = this->GetRadius();
    double width = this->GetWidth();

    EDA_ANGLE angle( point - arcCenter );

    while( angle < this->GetStartAngle() )
        angle += ANGLE_360;
    while( angle > this->GetEndAngle() + ANGLE_360 )
        angle -= ANGLE_360;

    if( angle < this->GetEndAngle() )
    {
        if( ( point - arcCenter ).SquaredEuclideanNorm() > radius * radius )
        {
            CU_SHAPE_CIRCLE circle( arcCenter, radius + width / 2 );
            return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
        }
        else
        {
            PATH_CONNECTION pc;
            pc.weight = std::max( ( radius - width / 2 ) - ( point - arcCenter ).EuclideanNorm(), 0.0 );
            pc.a1 = ( point - arcCenter ).Resize( radius - width / 2 ) + arcCenter;
            pc.a2 = point;

            if( pc.weight > 0 && pc.weight < aMaxWeight )
                result.push_back( pc );

            return result;
        }
    }
    else
    {
        VECTOR2I nearestPoint;

        if( ( point - this->GetStartPoint() ).SquaredEuclideanNorm()
            > ( point - this->GetEndPoint() ).SquaredEuclideanNorm() )
            nearestPoint = this->GetEndPoint();
        else
            nearestPoint = this->GetStartPoint();

        CU_SHAPE_CIRCLE circle( nearestPoint, width / 2 );
        return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
    }
    return result;
}


std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
                                                  double aMaxSquaredWeight ) const
{
    std::vector<PATH_CONNECTION> result;

    double R1 = this->GetRadius();
    double R2 = aS2.GetRadius();

    VECTOR2I C1 = this->GetPos();
    VECTOR2I C2 = aS2.GetPos();

    PATH_CONNECTION bestPath;
    bestPath.weight = std::numeric_limits<double>::infinity();
    CU_SHAPE_CIRCLE csc1( C1, R1 + this->GetWidth() / 2 );
    CU_SHAPE_CIRCLE csc2( C2, R2 + aS2.GetWidth() / 2 );

    CU_SHAPE_CIRCLE csc3( this->GetStartPoint(), this->GetWidth() / 2 );
    CU_SHAPE_CIRCLE csc4( this->GetEndPoint(), this->GetWidth() / 2 );
    CU_SHAPE_CIRCLE csc5( aS2.GetStartPoint(), aS2.GetWidth() / 2 );
    CU_SHAPE_CIRCLE csc6( aS2.GetEndPoint(), aS2.GetWidth() / 2 );

    std::vector<PATH_CONNECTION> pcs0 = csc1.Paths( csc2, aMaxWeight, aMaxSquaredWeight );

    std::vector<PATH_CONNECTION> pcs1 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight );
    std::vector<PATH_CONNECTION> pcs2 = csc1.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

    std::vector<PATH_CONNECTION> pcs3 = this->Paths( csc5, aMaxWeight, aMaxSquaredWeight );
    std::vector<PATH_CONNECTION> pcs4 = this->Paths( csc6, aMaxWeight, aMaxSquaredWeight );

    std::vector<PATH_CONNECTION> pcs5 = csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
    std::vector<PATH_CONNECTION> pcs6 = csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight );

    for( std::vector<PATH_CONNECTION> pcs : { pcs0, pcs1, pcs2 } )
    {
        for( PATH_CONNECTION& pc : pcs )
        {
            EDA_ANGLE testAngle1 = this->AngleBetweenStartAndEnd( pc.a1 );
            EDA_ANGLE testAngle2 = aS2.AngleBetweenStartAndEnd( pc.a2 );

            if( ( testAngle1 < this->GetEndAngle() ) && ( testAngle2 < aS2.GetEndAngle() )
                && ( bestPath.weight > pc.weight ) )
            {
                bestPath = pc;
            }
        }
    }

    for( std::vector<PATH_CONNECTION> pcs : { pcs3, pcs4, pcs5, pcs6 } )
    {
        for( PATH_CONNECTION& pc : pcs )
        {
            if( bestPath.weight > pc.weight )
            {
                bestPath = pc;
            }
        }
    }

    if( bestPath.weight != std::numeric_limits<double>::infinity() )
    {
        result.push_back( bestPath );
    }

    return result;
}


bool segmentIntersectsCircle( VECTOR2I p1, VECTOR2I p2, VECTOR2I center, double radius,
                              std::vector<VECTOR2I>* aIntersectPoints )
{
    SEG    segment( p1, p2 );
    CIRCLE circle( center, radius );

    std::vector<VECTOR2I> intersectionPoints;
    INTERSECTABLE_GEOM    geom1 = segment;
    INTERSECTABLE_GEOM    geom2 = circle;

    INTERSECTION_VISITOR visitor( geom2, intersectionPoints );
    std::visit( visitor, geom1 );

    if( aIntersectPoints )
    {
        for( VECTOR2I& point : intersectionPoints )
        {
            aIntersectPoints->push_back( point );
        }
    }

    return intersectionPoints.size() > 0;
}

bool SegmentIntersectsBoard( const VECTOR2I& aP1, const VECTOR2I& aP2,
                             const std::vector<BOARD_ITEM*>&       aBe,
                             const std::vector<const BOARD_ITEM*>& aDontTestAgainst,
                             int                                   aMinGrooveWidth )
{
    std::vector<VECTOR2I> intersectionPoints;
    bool TestGrooveWidth = aMinGrooveWidth > 0;

    for( BOARD_ITEM* be : aBe )
    {
        if( count( aDontTestAgainst.begin(), aDontTestAgainst.end(), be ) > 0 )
            continue;

        PCB_SHAPE* d = static_cast<PCB_SHAPE*>( be );
        if( !d )
            continue;

        switch( d->GetShape() )
        {
        case SHAPE_T::SEGMENT:
        {
            bool intersects =
                    segments_intersect( aP1, aP2, d->GetStart(), d->GetEnd(), &intersectionPoints );

            if( intersects && !TestGrooveWidth )
                return false;
            break;
        }
        case SHAPE_T::RECTANGLE:
        {
            VECTOR2I c1 = d->GetStart();
            VECTOR2I c2( d->GetStart().x, d->GetEnd().y );
            VECTOR2I c3 = d->GetEnd();
            VECTOR2I c4( d->GetEnd().x, d->GetStart().y );

            bool intersects = false;
            intersects |= segments_intersect( aP1, aP2, c1, c2, &intersectionPoints );
            intersects |= segments_intersect( aP1, aP2, c2, c3, &intersectionPoints );
            intersects |= segments_intersect( aP1, aP2, c3, c4, &intersectionPoints );
            intersects |= segments_intersect( aP1, aP2, c4, c1, &intersectionPoints );

            if( intersects && !TestGrooveWidth )
            {
                return false;
            }
            break;
        }
        case SHAPE_T::POLY:
        {
            std::vector<VECTOR2I> points;
            d->DupPolyPointsList( points );

            if( points.size() < 2 )
                break;
            VECTOR2I prevPoint = points.back();

            bool intersects = false;

            for( auto p : points )
            {
                intersects |= segments_intersect( aP1, aP2, prevPoint, p, &intersectionPoints );
                prevPoint = p;
            }
            if( intersects && !TestGrooveWidth )
            {
                return false;
            }
            break;
        }
        case SHAPE_T::CIRCLE:
        {
            VECTOR2I center = d->GetCenter();
            double   radius = d->GetRadius();

            bool intersects =
                    segmentIntersectsCircle( aP1, aP2, center, radius, &intersectionPoints );

            if( intersects && !TestGrooveWidth )
                return false;

            break;
        }


        case SHAPE_T::ARC:
        {
            VECTOR2I center = d->GetCenter();
            double   radius = d->GetRadius();

            EDA_ANGLE A, B;
            d->CalcArcAngles( A, B );

            bool intersects =
                    segmentIntersectsArc( aP1, aP2, center, radius, A, B, &intersectionPoints );

            if( intersects && !TestGrooveWidth )
                return false;

            break;
        }


        default: break;
        }
    }

    if( intersectionPoints.size() <= 0 )
        return true;

    if( intersectionPoints.size() % 2 != 0 )
        return false; // Should not happen if the start and end are both on the board

    int minx = intersectionPoints[0].x;
    int maxx = intersectionPoints[0].x;
    int miny = intersectionPoints[0].y;
    int maxy = intersectionPoints[0].y;

    for( VECTOR2I v : intersectionPoints )
    {
        minx = v.x < minx ? v.x : minx;
        maxx = v.x > maxx ? v.x : maxx;
        miny = v.x < miny ? v.x : miny;
        maxy = v.x > maxy ? v.x : maxy;
    }
    if( abs( maxx - minx ) > abs( maxy - miny ) )
    {
        std::sort( intersectionPoints.begin(), intersectionPoints.end(),
                   []( VECTOR2I a, VECTOR2I b )
                   {
                       return a.x > b.x;
                   } );
    }
    else
    {
        std::sort( intersectionPoints.begin(), intersectionPoints.end(),
                   []( VECTOR2I a, VECTOR2I b )
                   {
                       return a.y > b.y;
                   } );
    }

    int GVSquared = aMinGrooveWidth * aMinGrooveWidth;

    for( size_t i = 0; i < intersectionPoints.size(); i += 2 )
    {
        if( intersectionPoints[i].SquaredDistance( intersectionPoints[i + 1] ) > GVSquared )
        {
            return false;
        }
    }
    return true;
}

bool CheckPathValidity( VECTOR2I aP1, VECTOR2I aP2, std::vector<BOARD_ITEM*> aBe,
                        std::vector<const BOARD_ITEM*> aDontTestAgainst )
{
    return false;
}

std::vector<PATH_CONNECTION> GetPaths( CREEP_SHAPE* aS1, CREEP_SHAPE* aS2, double aMaxWeight )
{
    double                       maxWeight = aMaxWeight;
    double                       maxWeightSquared = maxWeight * maxWeight;
    std::vector<PATH_CONNECTION> result;

    CU_SHAPE_SEGMENT* cusegment1 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS1 );
    CU_SHAPE_SEGMENT* cusegment2 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS2 );
    CU_SHAPE_CIRCLE*  cucircle1 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS1 );
    CU_SHAPE_CIRCLE*  cucircle2 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS2 );
    CU_SHAPE_ARC*     cuarc1 = dynamic_cast<CU_SHAPE_ARC*>( aS1 );
    CU_SHAPE_ARC*     cuarc2 = dynamic_cast<CU_SHAPE_ARC*>( aS2 );


    BE_SHAPE_POINT*  bepoint1 = dynamic_cast<BE_SHAPE_POINT*>( aS1 );
    BE_SHAPE_POINT*  bepoint2 = dynamic_cast<BE_SHAPE_POINT*>( aS2 );
    BE_SHAPE_CIRCLE* becircle1 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS1 );
    BE_SHAPE_CIRCLE* becircle2 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS2 );
    BE_SHAPE_ARC*    bearc1 = dynamic_cast<BE_SHAPE_ARC*>( aS1 );
    BE_SHAPE_ARC*    bearc2 = dynamic_cast<BE_SHAPE_ARC*>( aS2 );

    // Cu to Cu

    if( cuarc1 && cuarc2 )
        return cuarc1->Paths( *cuarc2, maxWeight, maxWeightSquared );
    if( cuarc1 && cucircle2 )
        return cuarc1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cuarc1 && cusegment2 )
        return cuarc1->Paths( *cusegment2, maxWeight, maxWeightSquared );
    if( cucircle1 && cuarc2 )
        return cucircle1->Paths( *cuarc2, maxWeight, maxWeightSquared );
    if( cucircle1 && cucircle2 )
        return cucircle1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cucircle1 && cusegment2 )
        return cucircle1->Paths( *cusegment2, maxWeight, maxWeightSquared );
    if( cusegment1 && cuarc2 )
        return cusegment1->Paths( *cuarc2, maxWeight, maxWeightSquared );
    if( cusegment1 && cucircle2 )
        return cusegment1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cusegment1 && cusegment2 )
        return cusegment1->Paths( *cusegment2, maxWeight, maxWeightSquared );


    // Cu to Be

    if( cuarc1 && bearc2 )
        return cuarc1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cuarc1 && becircle2 )
        return cuarc1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( cuarc1 && bepoint2 )
        return cuarc1->Paths( *bepoint2, maxWeight, maxWeightSquared );
    if( cucircle1 && bearc2 )
        return cucircle1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cucircle1 && becircle2 )
        return cucircle1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( cucircle1 && bepoint2 )
        return cucircle1->Paths( *bepoint2, maxWeight, maxWeightSquared );
    if( cusegment1 && bearc2 )
        return cusegment1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cusegment1 && becircle2 )
        return cusegment1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( cusegment1 && bepoint2 )
        return cusegment1->Paths( *bepoint2, maxWeight, maxWeightSquared );

    // Reversed


    if( cuarc2 && bearc1 )
        return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cuarc2 && becircle1 )
        return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cuarc2 && bepoint1 )
        return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( cucircle2 && bearc1 )
        return bearc1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cucircle2 && becircle1 )
        return becircle1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cucircle2 && bepoint1 )
        return bepoint1->Paths( *cucircle2, maxWeight, maxWeightSquared );
    if( cusegment2 && bearc1 )
        return bearc1->Paths( *cusegment2, maxWeight, maxWeightSquared );
    if( cusegment2 && becircle1 )
        return becircle1->Paths( *cusegment2, maxWeight, maxWeightSquared );
    if( cusegment2 && bepoint1 )
        return bepoint1->Paths( *cusegment2, maxWeight, maxWeightSquared );


    // Be to Be

    if( bearc1 && bearc2 )
        return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( bearc1 && becircle2 )
        return bearc1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( bearc1 && bepoint2 )
        return bearc1->Paths( *bepoint2, maxWeight, maxWeightSquared );
    if( becircle1 && bearc2 )
        return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( becircle1 && becircle2 )
        return becircle1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( becircle1 && bepoint2 )
        return becircle1->Paths( *bepoint2, maxWeight, maxWeightSquared );
    if( bepoint1 && bearc2 )
        return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared );
    if( bepoint1 && becircle2 )
        return bepoint1->Paths( *becircle2, maxWeight, maxWeightSquared );
    if( bepoint1 && bepoint2 )
        return bepoint1->Paths( *bepoint2, maxWeight, maxWeightSquared );

    return result;
}

double CREEPAGE_GRAPH::Solve(
        std::shared_ptr<GRAPH_NODE>& aFrom, std::shared_ptr<GRAPH_NODE>& aTo,
        std::vector<std::shared_ptr<GRAPH_CONNECTION>>& aResult ) // Change to vector of pointers
{
    if( !aFrom || !aTo )
        return 0;

    if( aFrom == aTo )
        return 0;

    // Dijkstra's algorithm for shortest path
    std::unordered_map<GRAPH_NODE*, double>     distances;
    std::unordered_map<GRAPH_NODE*, GRAPH_NODE*> previous;

    auto cmp = [&distances]( GRAPH_NODE* left, GRAPH_NODE* right )
    {
        double distLeft = distances[left];
        double distRight = distances[right];

        if( distLeft == distRight )
            return left > right; // Compare addresses to avoid ties.
        return distLeft > distRight;
    };
    std::priority_queue<GRAPH_NODE*, std::vector<GRAPH_NODE*>, decltype( cmp )> pq( cmp );

    // Initialize distances to infinity for all nodes except the starting node
    for( std::shared_ptr<GRAPH_NODE> node : m_nodes )
    {
        if( node != nullptr )
            distances[node.get()] = std::numeric_limits<double>::infinity(); // Set to infinity
    }
    distances[aFrom.get()] = 0.0;
    distances[aTo.get()] = std::numeric_limits<double>::infinity();
    pq.push( aFrom.get() );

    // Dijkstra's main loop
    while( !pq.empty() )
    {
        GRAPH_NODE* current = pq.top();
        pq.pop();

        if( current == aTo.get() )
        {
            break; // Shortest path found
        }

        // Traverse neighbors
        for( std::shared_ptr<GRAPH_CONNECTION> connection : current->m_node_conns )
        {
            GRAPH_NODE* neighbor = ( connection->n1 ).get() == current ? ( connection->n2 ).get()
                                                                      : ( connection->n1 ).get();

            if( !neighbor )
                continue;

            // Ignore connections with negative weights as Dijkstra doesn't support them.
            if( connection->m_path.weight < 0.0 )
            {
                wxLogTrace( "CREEPAGE",
                            "Negative weight connection found. Ignoring connection." );
                continue;
            }


            double alt = distances[current]
                         + connection->m_path.weight; // Calculate alternative path cost

            if( alt < distances[neighbor] )
            {
                distances[neighbor] = alt;
                previous[neighbor] = current;
                pq.push( neighbor );
            }
        }
    }

    double pathWeight = distances[aTo.get()];

    // If aTo is unreachable, return infinity
    if( pathWeight == std::numeric_limits<double>::infinity() )
    {
        return std::numeric_limits<double>::infinity();
    }

    // Trace back the path from aTo to aFrom
    GRAPH_NODE* step = aTo.get();

    while( step != aFrom.get() )
    {
        GRAPH_NODE* prevNode = previous[step];

        for( const std::shared_ptr<GRAPH_CONNECTION>& node_conn : step->m_node_conns )
        {
            if( ( ( node_conn->n1 ).get() == prevNode && ( node_conn->n2 ).get() == step )
                || ( ( node_conn->n1 ).get() == step && ( node_conn->n2 ).get() == prevNode ) )
            {
                aResult.push_back( node_conn );
                break;
            }
        }
        step = prevNode;
    }

    return pathWeight;
}

void CREEPAGE_GRAPH::Addshape( const SHAPE& aShape, std::shared_ptr<GRAPH_NODE>& aConnectTo,
                              BOARD_ITEM* aParent )
{
    CREEP_SHAPE* newshape = nullptr;

    if( !aConnectTo )
        return;

    switch( aShape.Type() )
    {
    case SH_SEGMENT:
    {
        const SHAPE_SEGMENT& segment = dynamic_cast<const SHAPE_SEGMENT&>( aShape );
        CU_SHAPE_SEGMENT*    cuseg =
                new CU_SHAPE_SEGMENT( segment.GetSeg().A, segment.GetSeg().B, segment.GetWidth() );
        newshape = dynamic_cast<CREEP_SHAPE*>( cuseg );
        break;
    }
    case SH_CIRCLE:
    {
        const SHAPE_CIRCLE& circle = dynamic_cast<const SHAPE_CIRCLE&>( aShape );
        CU_SHAPE_CIRCLE* cucircle = new CU_SHAPE_CIRCLE( circle.GetCenter(), circle.GetRadius() );
        newshape = dynamic_cast<CREEP_SHAPE*>( cucircle );
        break;
    }
    case SH_ARC:
    {
        const SHAPE_ARC& arc = dynamic_cast<const SHAPE_ARC&>( aShape );
        EDA_ANGLE        alpha, beta;
        VECTOR2I         start, end;

        EDA_SHAPE edaArc( SHAPE_T::ARC, 0, FILL_T::NO_FILL );

        if( arc.IsClockwise() )
        {
            edaArc.SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
            start = arc.GetP0();
            end = arc.GetP1();
        }
        else
        {
            edaArc.SetArcGeometry( arc.GetP1(), arc.GetArcMid(), arc.GetP0() );
            start = arc.GetP1();
            end = arc.GetP0();
        }

        edaArc.CalcArcAngles( alpha, beta );

        CU_SHAPE_ARC* cuarc = new CU_SHAPE_ARC( edaArc.getCenter(), edaArc.GetRadius(), alpha, beta,
                                                arc.GetP0(), arc.GetP1() );
        cuarc->SetWidth( arc.GetWidth() );
        newshape = dynamic_cast<CREEP_SHAPE*>( cuarc );
        break;
    }
    case SH_COMPOUND:
    {
        int nbShapes = static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes().size();
        for( const SHAPE* subshape : ( static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes() ) )
        {
            if( subshape )
            {
                // We don't want to add shape for the inner rectangle of rounded rectangles
                if( !( ( subshape->Type() == SH_RECT ) && ( nbShapes == 5 ) ) )
                    Addshape( *subshape, aConnectTo, aParent );
            }
        }
        break;
    }
    case SH_POLY_SET:
    {
        const SHAPE_POLY_SET& polySet = dynamic_cast<const SHAPE_POLY_SET&>( aShape );

        for( auto it = polySet.CIterateSegmentsWithHoles(); it; it++ )
        {
            const SEG     object = *it;
            SHAPE_SEGMENT segment( object.A, object.B );
            Addshape( segment, aConnectTo, aParent );
        }
        break;
    }
    case SH_LINE_CHAIN:
    {
        const SHAPE_LINE_CHAIN& lineChain = dynamic_cast<const SHAPE_LINE_CHAIN&>( aShape );

        VECTOR2I prevPoint = lineChain.CLastPoint();

        for( auto point : lineChain.CPoints() )
        {
            SHAPE_SEGMENT segment( point, prevPoint );
            prevPoint = point;
            Addshape( segment, aConnectTo, aParent );
        }
        break;
    }
    case SH_RECT:
    {
        const SHAPE_RECT& rect = dynamic_cast<const SHAPE_RECT&>( aShape );

        VECTOR2I point0 = rect.GetPosition();
        VECTOR2I point1 = rect.GetPosition() + VECTOR2I( rect.GetSize().x, 0 );
        VECTOR2I point2 = rect.GetPosition() + rect.GetSize();
        VECTOR2I point3 = rect.GetPosition() + VECTOR2I( 0, rect.GetSize().y );

        Addshape( SHAPE_SEGMENT( point0, point1 ), aConnectTo, aParent );
        Addshape( SHAPE_SEGMENT( point1, point2 ), aConnectTo, aParent );
        Addshape( SHAPE_SEGMENT( point2, point3 ), aConnectTo, aParent );
        Addshape( SHAPE_SEGMENT( point3, point0 ), aConnectTo, aParent );
        break;
    }
    default: break;
    }

    if( !newshape )
        return;

    std::shared_ptr<GRAPH_NODE> gnShape = nullptr;

    newshape->SetParent( aParent );

    switch( aShape.Type() )
    {
    case SH_SEGMENT: gnShape = AddNode( GRAPH_NODE::SEGMENT, newshape, newshape->GetPos() ); break;
    case SH_CIRCLE: gnShape = AddNode( GRAPH_NODE::CIRCLE, newshape, newshape->GetPos() ); break;
    case SH_ARC: gnShape = AddNode( GRAPH_NODE::ARC, newshape, newshape->GetPos() ); break;
    default: break;
    }

    if( gnShape )
    {
        m_shapeCollection.push_back( newshape );
        gnShape->m_net = aConnectTo->m_net;
        std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gnShape, aConnectTo );

        if( gc )
            gc->m_path.m_show = false;
    }
    else
    {
        delete newshape;
        newshape = nullptr;
    }
}

void CREEPAGE_GRAPH::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool aClearance )
{
    std::vector<std::shared_ptr<GRAPH_NODE>> nodes;
    std::mutex                               nodes_lock;
    thread_pool&                             tp = GetKiCadThreadPool();

    std::copy_if( m_nodes.begin(), m_nodes.end(), std::back_inserter( nodes ),
                  [&]( std::shared_ptr<GRAPH_NODE> gn )
                  {
                      return !!gn && gn->m_parent && gn->m_connectDirectly
                             && ( gn->m_type != GRAPH_NODE::TYPE::VIRTUAL );
                  } );

    std::sort( nodes.begin(), nodes.end(),
              []( std::shared_ptr<GRAPH_NODE> gn1, std::shared_ptr<GRAPH_NODE> gn2 )
              {
                  return ( gn1->m_parent < gn2->m_parent ) || ( gn1->m_parent == gn2->m_parent
                                                                && gn1->m_net < gn2->m_net );
              } );

    auto processNodes = [&]( size_t i, size_t j ) -> bool
    {
            for( size_t ii = i; ii < j; ii++ )
            {
                std::shared_ptr<GRAPH_NODE> gn1 = nodes[ii];

                for( size_t jj = ii + 1; jj < nodes.size(); jj++ )
                {
                    std::shared_ptr<GRAPH_NODE> gn2 = nodes[jj];

                    if( gn1->m_parent->GetParent() == gn2->m_parent->GetParent() )
                        continue;

                    if( ( gn1->m_net == gn2->m_net ) && ( gn1->m_parent->IsConductive() )
                        && ( gn2->m_parent->IsConductive() ) )
                        continue;

                    for( PATH_CONNECTION pc : GetPaths( gn1->m_parent, gn2->m_parent, aMaxWeight ) )
                    {
                        std::vector<const BOARD_ITEM*> IgnoreForTest;
                        IgnoreForTest.push_back( gn1->m_parent->GetParent() );
                        IgnoreForTest.push_back( gn2->m_parent->GetParent() );

                        if( !pc.isValid( m_board, aLayer, m_boardEdge, IgnoreForTest, m_boardOutline,
                                        { false, true }, m_minGrooveWidth ) )
                            continue;

                        std::shared_ptr<GRAPH_NODE>* connect1 = &gn1;
                        std::shared_ptr<GRAPH_NODE>* connect2 = &gn2;
                        std::shared_ptr<GRAPH_NODE>  gnt1 = nullptr;
                        std::shared_ptr<GRAPH_NODE>  gnt2 = nullptr;
                        std::lock_guard<std::mutex> lock( nodes_lock );

                        if( gn1->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
                        {
                            gnt1 = AddNode( GRAPH_NODE::POINT, gn1->m_parent, pc.a1 );
                            gnt1->m_connectDirectly = false;

                            if( gn1->m_parent->IsConductive() )
                            {
                                std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn1, gnt1 );

                                if( gc )
                                    gc->m_path.m_show = false;
                            }
                            connect1 = &gnt1;
                        }

                        if( gn2->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT )
                        {
                            gnt2 = AddNode( GRAPH_NODE::POINT, gn2->m_parent, pc.a2 );
                            gnt2->m_connectDirectly = false;

                            if( gn2->m_parent->IsConductive() )
                            {
                                std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn2, gnt2 );

                                if( gc )
                                    gc->m_path.m_show = false;
                            }
                            connect2 = &gnt2;
                        }

                        AddConnection( *connect1, *connect2, pc );
                    }
                } // for jj
            } // for ii

            return true;
        };

    // Running in the clearance test, we are already in a parallelized loop, so parallelizing again
    // will run into deadlock as all threads start waiting
    if( aClearance )
        processNodes( 0, nodes.size() );
    else
    {
        auto ret = tp.parallelize_loop( nodes.size(), processNodes );

        for( size_t ii = 0; ii < ret.size(); ii++ )
        {
            std::future<bool>& r = ret[ii];

            if( r.valid() )
            {
                std::future_status status = r.wait_for( std::chrono::seconds( 0 ) );

                while( status != std::future_status::ready )
                {
                    status = r.wait_for( std::chrono::milliseconds( 100 ) );
                }
            }
        }
    }
}

void CREEPAGE_GRAPH::Trim( double aWeightLimit )
{
    std::vector<std::shared_ptr<GRAPH_CONNECTION>> toRemove;

    // Collect connections to remove
    for( std::shared_ptr<GRAPH_CONNECTION>& gc : m_connections )
    {
        if( gc && ( gc->m_path.weight > aWeightLimit ) )
        {
            toRemove.push_back( gc );
        }
    }

    // Remove collected connections
    for( const std::shared_ptr<GRAPH_CONNECTION>& gc : toRemove )
    {
        RemoveConnection( gc );
    }
}

void CREEPAGE_GRAPH::RemoveConnection( std::shared_ptr<GRAPH_CONNECTION> aGc, bool aDelete )
{
    if( !aGc )
        return;

    for( std::shared_ptr<GRAPH_NODE> gn : { aGc->n1, aGc->n2 } )
    {
        if( gn )
        {
            gn->m_node_conns.erase( aGc );

            if( gn->m_node_conns.empty() && aDelete )
            {
                auto it = std::find_if( m_nodes.begin(), m_nodes.end(),
                                        [&gn]( const std::shared_ptr<GRAPH_NODE> node )
                                        {
                                            return node.get() == gn.get();
                                        } );

                if( it != m_nodes.end() )
                    m_nodes.erase( it );

                m_nodeset.erase( gn );
            }
        }
    }

    if( aDelete )
    {
        // Remove the connection from the graph's connections
        m_connections.erase( std::remove( m_connections.begin(), m_connections.end(), aGc ),
                             m_connections.end() );
    }
}


std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNode( GRAPH_NODE::TYPE aType, CREEP_SHAPE* parent,
                                                   VECTOR2I pos )
{
    std::shared_ptr<GRAPH_NODE> gn = FindNode( aType, parent, pos );
    if( gn )
        return gn;

    gn = std::make_shared<GRAPH_NODE>( aType, parent, pos );
    m_nodes.push_back( gn );
    m_nodeset.insert( gn );
    return gn;
}

std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNodeVirtual()
{
    //Virtual nodes are always unique, do not try to find them
    std::shared_ptr<GRAPH_NODE> gn =
            std::make_shared<GRAPH_NODE>( GRAPH_NODE::TYPE::VIRTUAL, nullptr );
    m_nodes.push_back( gn );
    m_nodeset.insert( gn );
    return gn;
}


std::shared_ptr<GRAPH_CONNECTION> CREEPAGE_GRAPH::AddConnection( std::shared_ptr<GRAPH_NODE>& aN1,
                                                               std::shared_ptr<GRAPH_NODE>& aN2,
                                                               const PATH_CONNECTION&      aPc )
{
    if( !aN1 || !aN2 )
        return nullptr;

    wxASSERT_MSG( ( aN1 != aN2 ), "Creepage: a connection connects a node to itself" );

    std::shared_ptr<GRAPH_CONNECTION> gc = std::make_shared<GRAPH_CONNECTION>( aN1, aN2, aPc );
    m_connections.push_back( gc );
    aN1->m_node_conns.insert( gc );
    aN2->m_node_conns.insert( gc );

    return gc;
}

std::shared_ptr<GRAPH_CONNECTION> CREEPAGE_GRAPH::AddConnection( std::shared_ptr<GRAPH_NODE>& aN1,
                                                               std::shared_ptr<GRAPH_NODE>& aN2 )
{
    if( !aN1 || !aN2 )
        return nullptr;

    PATH_CONNECTION pc;
    pc.a1 = aN1->m_pos;
    pc.a2 = aN2->m_pos;
    pc.weight = 0;

    return AddConnection( aN1, aN2, pc );
}

std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::FindNode( GRAPH_NODE::TYPE aType, CREEP_SHAPE* aParent,
                                                    VECTOR2I aPos )
{
    auto it = m_nodeset.find( std::make_shared<GRAPH_NODE>( aType, aParent, aPos ) );

    if( it != m_nodeset.end() )
        return *it;

    return nullptr;
}


std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNetElements( int aNetCode, PCB_LAYER_ID aLayer,
                                                          int aMaxCreepage )
{
    std::shared_ptr<GRAPH_NODE> virtualNode = AddNodeVirtual();
    virtualNode->m_net = aNetCode;

    for( FOOTPRINT* footprint : m_board.Footprints() )
    {
        for( PAD* pad : footprint->Pads() )
        {
            if( pad->GetNetCode() != aNetCode )
                continue;

            std::shared_ptr<SHAPE> padShape = pad->GetEffectiveShape( aLayer );

            if( padShape )
            {
                Addshape( *padShape, virtualNode, pad );
            }
        }
    }

    for( PCB_TRACK* track : m_board.Tracks() )
    {
        if( !track )
            continue;

        if( track->GetNetCode() != aNetCode )
            continue;

        if( !track->IsOnLayer( aLayer ) )
            continue;

        if( track->GetEffectiveShape() == nullptr )
            continue;

        Addshape( *( track->GetEffectiveShape() ), virtualNode, track );
    }


    for( ZONE* zone : m_board.Zones() )
    {
        if( !zone )
            continue;

        if( zone->GetNetCode() != aNetCode )
            continue;

        if( zone->GetEffectiveShape( aLayer ) == nullptr )
            continue;

        Addshape( *( zone->GetEffectiveShape( aLayer ) ), virtualNode, zone );
    }

    const DRAWINGS drawings = m_board.Drawings();

    for( BOARD_ITEM* drawing : drawings )
    {
        if( !drawing )
            continue;

        if( !drawing->IsConnected() )
            continue;

        BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( drawing );

        if( !bci )
            continue;

        if( bci->GetNetCode() != aNetCode )
            continue;

        if( bci->IsOnLayer( aLayer ) )
        {
            Addshape( *( bci->GetEffectiveShape() ), virtualNode, bci );
        }
    }


    return virtualNode;
}