/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.TXT for contributors.
 *
 * 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 <geometry/shape_arc.h>
#include <geometry/shape_line_chain.h>
#include <trigo.h>

#include <qa_utils/geometry/geometry.h>
#include <qa_utils/numeric.h>
#include <qa_utils/wx_utils/unit_test_utils.h>

#include "geom_test_utils.h"

/**
 * NOTE: Collision of SHAPE_LINE_CHAIN with arcs is tested in test_shape_arc.cpp
 */

struct SLC_CASES
{
    SHAPE_LINE_CHAIN Circle1Arc;
    SHAPE_LINE_CHAIN Circle2Arcs;
    SHAPE_LINE_CHAIN ArcsCoincident;
    SHAPE_LINE_CHAIN ArcsCoincidentClosed;
    SHAPE_LINE_CHAIN ArcsIndependent;
    SHAPE_LINE_CHAIN DuplicateArcs;
    SHAPE_LINE_CHAIN ArcsAndSegMixed;
    SHAPE_LINE_CHAIN ArcAndPoint;
    SHAPE_LINE_CHAIN SegAndArcCoincident;
    SHAPE_LINE_CHAIN EmptyChain;
    SHAPE_LINE_CHAIN OnePoint;
    SHAPE_LINE_CHAIN TwoPoints;
    SHAPE_LINE_CHAIN ThreePoints;

    SHAPE_ARC ArcCircle; ///< Full Circle arc
    SHAPE_ARC Arc0a; ///< First half of a circle
    SHAPE_ARC Arc0b; ///< Second half of a circle
    SHAPE_ARC Arc1; ///< start coincident with Arc0a end
    SHAPE_ARC Arc2; ///< Independent arc
    SHAPE_ARC Arc3; ///< Arc with angle >180

    SLC_CASES()
    {
        ArcCircle = SHAPE_ARC( VECTOR2I( 183450000, 128360000 ),
                               VECTOR2I( 183850000, 128360000 ),
                               VECTOR2I( 183450000, 128360000 ), 0 );

        Arc0a =     SHAPE_ARC( VECTOR2I( 183450000, 128360000 ),
                               VECTOR2I( 183650000, 128560000 ),
                               VECTOR2I( 183850000, 128360000 ), 0 );

        Arc0b =     SHAPE_ARC( VECTOR2I( 183850000, 128360000 ),
                               VECTOR2I( 183650000, 128160000 ),
                               VECTOR2I( 183450000, 128360000 ), 0 );

        Arc1 =      SHAPE_ARC( VECTOR2I( 183850000, 128360000 ),
                               VECTOR2I( 183638550, 128640305 ),
                               VECTOR2I( 183500000, 129204974 ), 0 );

        Arc2 =      SHAPE_ARC( VECTOR2I( 283450000, 228360000 ),
                               VECTOR2I( 283650000, 228560000 ),
                               VECTOR2I( 283850000, 228360000 ), 0 );

        Arc3 =      SHAPE_ARC( VECTOR2I( 0,         0 ),
                               VECTOR2I( 24142136,  10000000 ),
                               VECTOR2I( 0,         20000000 ), 0 );

        Circle1Arc.Append( ArcCircle );
        Circle1Arc.SetClosed( true );

        Circle2Arcs.Append( Arc0a );
        Circle2Arcs.Append( Arc0b );
        Circle2Arcs.SetClosed( true );

        ArcsCoincident.Append( Arc0a );
        ArcsCoincident.Append( Arc1 );

        ArcsCoincidentClosed=ArcsCoincident;
        ArcsCoincidentClosed.SetClosed( true );

        ArcsIndependent.Append( Arc0a );
        ArcsIndependent.Append( Arc2 );

        DuplicateArcs=ArcsCoincident;
        DuplicateArcs.Append( Arc1 ); //should add a segment between end of the chain and new copy of the arc

        ArcAndPoint.Append( Arc0a );
        ArcAndPoint.Append( VECTOR2I( 233450000, 228360000 ) );

        ArcsAndSegMixed = ArcAndPoint;
        ArcsAndSegMixed.Append( Arc2 );

        OnePoint.Append( VECTOR2I( 233450000, 228360000 ) );

        TwoPoints.Append( VECTOR2I( 233450000, 228360000 ) );
        TwoPoints.Append( VECTOR2I( 263450000, 258360000 ) );

        ThreePoints = TwoPoints;
        ThreePoints.Append( VECTOR2I( 263450000, 308360000 ) );

        SegAndArcCoincident.Append( VECTOR2I( 0, 20000000 ) );
        SegAndArcCoincident.Append( Arc3 );
    }
};


BOOST_FIXTURE_TEST_SUITE( TestShapeLineChain, SLC_CASES )


BOOST_AUTO_TEST_CASE( ClipperConstructorCase1 )
{
    // Case of an arc followed by a segment
    // The clipper path is not in order (on purpose), to simulate the typical return from clipper

    ClipperLib::Path pathClipper1 = {
        { { 125663951, 120099260, 24 }, { 125388111, 120170850, 25 }, { 125124975, 120280270, 26 },
          { 124879705, 120425376, 27 }, { 124657110, 120603322, 28 }, { 124461556, 120810617, 29 },
          { 124296876, 121043198, 30 }, { 124166301, 121296503, 31 }, { 124072391, 121565564, 32 },
          { 124016988, 121845106, 33 }, { 124001177, 122129646, 34 }, { 124025270, 122413605, 35 },
          { 124088794, 122691414, 36 }, { 124190502, 122957625, 37 }, { 124328401, 123207018, 38 },
          { 124499787, 123434703, 39 }, { 124598846, 123537154, 40 }, { 127171000, 123786000, 4 },
          { 127287862, 123704439, 5 },  { 127499716, 123513831, 6 },  { 127682866, 123295498, 7 },
          { 127833720, 123053722, 8 },  { 127949321, 122793242, 9 },  { 128027402, 122519168, 10 },
          { 128066430, 122236874, 11 }, { 128065642, 121951896, 12 }, { 128025053, 121669823, 13 },
          { 127945457, 121396185, 14 }, { 127828417, 121136349, 15 }, { 127676227, 120895410, 16 },
          { 127491873, 120678094, 17 }, { 127278968, 120488661, 18 }, { 127041689, 120330827, 19 },
          { 126784688, 120207687, 20 }, { 126513005, 120121655, 21 }, { 126231968, 120074419, 22 },
          { 125947087, 120066905, 23 } }
    };
    Clipper2Lib::Path64 pathClipper2 = {
        { { 125663951, 120099260, 24 }, { 125388111, 120170850, 25 }, { 125124975, 120280270, 26 },
          { 124879705, 120425376, 27 }, { 124657110, 120603322, 28 }, { 124461556, 120810617, 29 },
          { 124296876, 121043198, 30 }, { 124166301, 121296503, 31 }, { 124072391, 121565564, 32 },
          { 124016988, 121845106, 33 }, { 124001177, 122129646, 34 }, { 124025270, 122413605, 35 },
          { 124088794, 122691414, 36 }, { 124190502, 122957625, 37 }, { 124328401, 123207018, 38 },
          { 124499787, 123434703, 39 }, { 124598846, 123537154, 40 }, { 127171000, 123786000, 4 },
          { 127287862, 123704439, 5 },  { 127499716, 123513831, 6 },  { 127682866, 123295498, 7 },
          { 127833720, 123053722, 8 },  { 127949321, 122793242, 9 },  { 128027402, 122519168, 10 },
          { 128066430, 122236874, 11 }, { 128065642, 121951896, 12 }, { 128025053, 121669823, 13 },
          { 127945457, 121396185, 14 }, { 127828417, 121136349, 15 }, { 127676227, 120895410, 16 },
          { 127491873, 120678094, 17 }, { 127278968, 120488661, 18 }, { 127041689, 120330827, 19 },
          { 126784688, 120207687, 20 }, { 126513005, 120121655, 21 }, { 126231968, 120074419, 22 },
          { 125947087, 120066905, 23 } }
    };

    std::vector<CLIPPER_Z_VALUE> z_values = {
        { { -1, -1 }, 0 }, { { -1, -1 }, 0 }, { { -1, -1 }, 0 }, { { -1, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },  { { 0, -1 }, 0 },
        { { 0, -1 }, 0 }
    };

    std::vector<SHAPE_ARC> arcs = {
        SHAPE_ARC( { 127171000, 123786000 }, { 126231718, 120077003 }, { 124598846, 123537154 }, 0 )
    };

    SHAPE_LINE_CHAIN clipper1chain( pathClipper1, z_values, arcs );
    SHAPE_LINE_CHAIN clipper2chain( pathClipper2, z_values, arcs );

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( clipper1chain ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( clipper2chain ) );

    BOOST_CHECK_EQUAL( clipper1chain.PointCount(), 37 );
    BOOST_CHECK_EQUAL( clipper2chain.PointCount(), 37 );

    BOOST_CHECK_EQUAL( clipper1chain.ArcCount(), 1 );
    BOOST_CHECK_EQUAL( clipper2chain.ArcCount(), 1 );

    BOOST_CHECK_EQUAL( clipper1chain.ShapeCount(), 2 );
    BOOST_CHECK_EQUAL( clipper2chain.ShapeCount(), 2 );

    BOOST_CHECK_EQUAL( clipper1chain.IsClosed(), true );
    BOOST_CHECK_EQUAL( clipper2chain.IsClosed(), true );
}


BOOST_AUTO_TEST_CASE( ArcToPolyline )
{
    SHAPE_LINE_CHAIN base_chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 1000 ), VECTOR2I( 1000, 0 ) } );

    SHAPE_LINE_CHAIN chain_insert( {
            VECTOR2I( 0, 1500 ),
            VECTOR2I( 1500, 1500 ),
            VECTOR2I( 1500, 0 ),
    } );

    SHAPE_LINE_CHAIN arc_insert1( SHAPE_ARC( VECTOR2I( 0, -100 ), VECTOR2I( 0, -200 ), ANGLE_180 ) );

    SHAPE_LINE_CHAIN arc_insert2( SHAPE_ARC( VECTOR2I( 0, 500 ), VECTOR2I( 0, 400 ), ANGLE_180 ) );

    BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );
    BOOST_CHECK_EQUAL( arc_insert1.CShapes().size(), arc_insert1.CPoints().size() );
    BOOST_CHECK_EQUAL( arc_insert2.CShapes().size(), arc_insert2.CPoints().size() );

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( arc_insert1 ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( arc_insert2 ) );

    base_chain.Insert( 0, SHAPE_ARC( VECTOR2I( 0, -100 ), VECTOR2I( 0, -200 ), ANGLE_180 ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );

    base_chain.Replace( 0, 2, chain_insert );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );
}


// Similar test to above but with larger coordinates, so we have more than one point per arc
BOOST_AUTO_TEST_CASE( ArcToPolylineLargeCoords )
{
    SHAPE_LINE_CHAIN base_chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( 100000, 0 ) } );

    SHAPE_LINE_CHAIN chain_insert( {
            VECTOR2I( 0, 1500000 ),
            VECTOR2I( 1500000, 1500000 ),
            VECTOR2I( 1500000, 0 ),
    } );

    base_chain.Append( SHAPE_ARC( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 100000 ), ANGLE_180 ) );

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 );

    base_chain.Insert( 9, VECTOR2I( 250000, 0 ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.PointCount(), 12 );
    BOOST_CHECK_EQUAL( base_chain.ArcCount(), 2 ); // Should have two arcs after the split

    base_chain.Replace( 5, 6, chain_insert );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.PointCount(), 13 ); // Adding 3 points, removing 2
    BOOST_CHECK_EQUAL( base_chain.ArcCount(), 3 ); // Should have three arcs after the split

    base_chain.Replace( 4, 6, VECTOR2I( 550000, 0 ) );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 ); // Adding 1 point, removing 3
    BOOST_CHECK_EQUAL( base_chain.ArcCount(), 3 );    // Should still have three arcs

    // Test ClearArcs
    base_chain.SetClosed( true );
    double areaPriorToArcRemoval = base_chain.Area();
    base_chain.ClearArcs();

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
    BOOST_CHECK_EQUAL( base_chain.CPoints().size(), base_chain.CShapes().size() );
    BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 ); // We should have the same number of points
    BOOST_CHECK_EQUAL( base_chain.ArcCount(), 0 ); // All arcs should have been removed
    BOOST_CHECK_EQUAL( base_chain.Area(), areaPriorToArcRemoval ); // Area should not have changed
}

// Test that duplicate point gets removed when line is set to be closed and added where required
BOOST_AUTO_TEST_CASE( SetClosedDuplicatePoint )
{
    // Test from issue #9843
    SHAPE_LINE_CHAIN chain;

    chain.Append(
            SHAPE_ARC( { -859598, 2559876 }, { -1632771, 1022403 }, { -3170244, 249230 }, 0 ) );

    chain.Append(
            SHAPE_ARC( { -3170244, -1657832 }, { -292804, -317564 }, { 1047464, 2559876 }, 0 ) );

    chain.Append( VECTOR2I( -859598, 2559876 ) ); // add point that is equal to first arc start

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
    BOOST_CHECK_EQUAL( chain.PointCount(), 31 );

    // CLOSED CHAIN
    chain.SetClosed( true );
    BOOST_CHECK_EQUAL( chain.CPoints().size(), chain.CShapes().size() );
    BOOST_CHECK_EQUAL( chain.PointCount(), 30 ); // (-1) should have removed coincident points
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    // Special case: arc wrapping around to start (e.g. circle)
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( Circle2Arcs ) );
    BOOST_CHECK_EQUAL( Circle2Arcs.IsClosed(), true );
    BOOST_CHECK_EQUAL( Circle2Arcs.PointCount(), 16 );
    BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 15 ), true );
    BOOST_CHECK_EQUAL( Circle2Arcs.ShapeCount(), 2 );
    Circle2Arcs.SetClosed( false );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( Circle2Arcs ) );
    BOOST_CHECK_EQUAL( Circle2Arcs.IsClosed(), false );
    BOOST_CHECK_EQUAL( Circle2Arcs.PointCount(), 17 );
    BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 15 ), true );
    BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 16 ), false ); // last point doesn't join up
}

struct CLOSE_TOGGLE_SHAPE_CASE
{
    std::string m_ctx_name;
    SHAPE_LINE_CHAIN m_chain;
    bool m_closed;
    int m_shape_count;
    int m_point_count;
    int m_expected_shape_count;
    int m_expected_point_count;
};

static const std::vector<CLOSE_TOGGLE_SHAPE_CASE> close_toggle_shape_cases =
        {
            { "Circle1Arc",           SLC_CASES().Circle1Arc,           true,  1, 15, 1, 16 },
            { "Circle2Arcs",          SLC_CASES().Circle2Arcs,          true,  2, 16, 2, 17 },
            { "ArcsCoincident",       SLC_CASES().ArcsCoincident,       false, 2, 14, 3, 14 },
            { "ArcsCoincidentClosed", SLC_CASES().ArcsCoincidentClosed, true,  3, 14, 2, 14 },
            { "ArcsIndependent",      SLC_CASES().ArcsIndependent,      false, 3, 18, 4, 18 },
            // SegAndArcCoincident will remove the segment after SetClosed(true) and SetClosed(false)
            // disable test for now
            //{ "SegAndArcCoincident",  SLC_CASES().SegAndArcCoincident,  false, 2, 92, 2, 91 },
            { "DuplicateArcs",        SLC_CASES().DuplicateArcs,        false, 4, 20, 5, 20 },
            { "ArcAndPoint",          SLC_CASES().ArcAndPoint,          false, 2, 10, 3, 10 },
            { "ArcsAndSegMixed",      SLC_CASES().ArcsAndSegMixed,      false, 4, 19, 5, 19 },
            { "OnePoint",             SLC_CASES().OnePoint,             false, 0,  1, 0,  1 }, // no shapes
            { "TwoPoints",            SLC_CASES().TwoPoints,            false, 1,  2, 2,  2 }, // there and back
            { "ThreePoints",          SLC_CASES().ThreePoints,          false, 2,  3, 3,  3 },
        };

BOOST_AUTO_TEST_CASE( ToggleClosed )
{
    for( const CLOSE_TOGGLE_SHAPE_CASE& c : close_toggle_shape_cases )
    {
        BOOST_TEST_CONTEXT( c.m_ctx_name )
        {
            SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
            BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
            BOOST_CHECK_EQUAL( slc_case.IsClosed(), c.m_closed );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
            BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_point_count );
            slc_case.SetClosed( !c.m_closed );
            BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
            BOOST_CHECK_EQUAL( slc_case.IsClosed(), !c.m_closed );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
            BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_expected_point_count );
            slc_case.SetClosed( c.m_closed ); // toggle back to normal
            BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
            BOOST_CHECK_EQUAL( slc_case.IsClosed(), c.m_closed );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
            BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_point_count );
        }
    }
}


BOOST_AUTO_TEST_CASE( PointInPolygon )
{
    SHAPE_LINE_CHAIN outline1( { { 1316455, 913576 }, { 1316455, 901129 }, { 1321102, 901129 },
                                 { 1322152, 901191 }, { 1323055, 901365 }, { 1323830, 901639 },
                                 { 1324543, 902036 }, { 1325121, 902521 }, { 1325581, 903100 },
                                 { 1325914, 903759 }, { 1326120, 904516 }, { 1326193, 905390 },
                                 { 1326121, 906253 }, { 1325915, 907005 }, { 1325581, 907667 },
                                 { 1325121, 908248 }, { 1324543, 908735 }, { 1323830, 909132 },
                                 { 1323055, 909406 }, { 1322153, 909579 }, { 1321102, 909641 },
                                 { 1317174, 909641 }, { 1317757, 909027 }, { 1317757, 913576 } } );
    SHAPE_LINE_CHAIN outline2( { { 1297076, 916244 }, { 1284629, 916244 }, { 1284629, 911597 },
                                 { 1284691, 910547 }, { 1284865, 909644 }, { 1285139, 908869 },
                                 { 1285536, 908156 }, { 1286021, 907578 }, { 1286600, 907118 },
                                 { 1287259, 906785 }, { 1288016, 906579 }, { 1288890, 906506 },
                                 { 1289753, 906578 }, { 1290505, 906784 }, { 1291167, 907118 },
                                 { 1291748, 907578 }, { 1292235, 908156 }, { 1292632, 908869 },
                                 { 1292906, 909644 }, { 1293079, 910546 }, { 1293141, 911597 },
                                 { 1293141, 915525 }, { 1292527, 914942 }, { 1297076, 914942 } } );

    // Test a point inside the polygon
    VECTOR2I point1( 1317757, 909133 );
    VECTOR2I point2( 1292633, 914942 );

    outline1.SetClosed( true );
    outline2.SetClosed( true );

    BOOST_CHECK( outline1.PointInside( point1, 0, false ) );
    BOOST_CHECK( outline2.PointInside( point2, 0, false ) );
}

// Test that duplicate point gets removed when we call simplify
BOOST_AUTO_TEST_CASE( SimplifyDuplicatePoint )
{
    SHAPE_LINE_CHAIN chain;

    chain.Append( { 100, 100 } );
    chain.Append( { 100, 100 }, true ); //duplicate point to simplify
    chain.Append( { 200, 100 } );

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
    BOOST_CHECK_EQUAL( chain.PointCount(), 3 );

    chain.Simplify();

    BOOST_CHECK_EQUAL( chain.CPoints().size(), chain.CShapes().size() );
    BOOST_CHECK_EQUAL( chain.PointCount(), 2 ); // (-1) should have removed coincident points
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
}


BOOST_AUTO_TEST_CASE( SimplifyComplexChain )
{
    SHAPE_LINE_CHAIN chain;

    // Append points
    chain.Append( { 130000, 147320 } );
    chain.Append( { 125730, 147320 } );
    chain.Append( { 125730, 150630 } );
    chain.Append( { 128800, 153700 } );
    chain.Append( { 150300, 153700 } );
    chain.Append( { 151500, 152500 } );
    chain.Append( { 151500, 148900 } );
    chain.Append( { 149920, 147320 } );
    chain.Append( { 140000, 147320 } );

    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
    BOOST_CHECK_EQUAL( chain.PointCount(), 9 );

    // The chain should be open, so the points should not be simplified
    // between the begining and the end.
    chain.Simplify();

    BOOST_CHECK_EQUAL( chain.PointCount(), 9 );

    chain.SetClosed( true );
    chain.Simplify();

    BOOST_CHECK_EQUAL( chain.PointCount(), 8 );
}

struct REMOVE_SHAPE_CASE
{
    std::string m_ctx_name;
    SHAPE_LINE_CHAIN m_chain;
    int m_shape_count;
    int m_arc_count;
    int m_remove_index;
    int m_expected_shape_count;
    int m_expected_arc_count;
};

static const std::vector<REMOVE_SHAPE_CASE> remove_shape_cases =
        {
            { "Circle1Arc - 1st arc - index on start", SLC_CASES().Circle1Arc, 1, 1,  0, 0, 0 },
            { "Circle1Arc - 1st arc - index on mid",   SLC_CASES().Circle1Arc, 1, 1,  8, 0, 0 },
            { "Circle1Arc - 1st arc - index on end",   SLC_CASES().Circle1Arc, 1, 1, 14, 0, 0 },
            { "Circle1Arc - 1st arc - index on  -1",   SLC_CASES().Circle1Arc, 1, 1, -1, 0, 0 },
            { "Circle1Arc - invalid index",            SLC_CASES().Circle1Arc, 1, 1, 15, 1, 1 },

            { "Circle2Arcs - 1st arc - index on start", SLC_CASES().Circle2Arcs, 2, 2,  0, 2, 1 },
            { "Circle2Arcs - 1st arc - index on mid",   SLC_CASES().Circle2Arcs, 2, 2,  3, 2, 1 },
            { "Circle2Arcs - 1st arc - index on end",   SLC_CASES().Circle2Arcs, 2, 2,  7, 2, 1 },
            { "Circle2Arcs - 2nd arc - index on start", SLC_CASES().Circle2Arcs, 2, 2,  8, 2, 1 },
            { "Circle2Arcs - 2nd arc - index on mid",   SLC_CASES().Circle2Arcs, 2, 2, 11, 2, 1 },
            { "Circle2Arcs - 2nd arc - index on end",   SLC_CASES().Circle2Arcs, 2, 2, 15, 2, 1 },
            { "Circle2Arcs - 2nd arc - index on  -1",   SLC_CASES().Circle2Arcs, 2, 2, -1, 2, 1 },
            { "Circle2Arcs - invalid index",            SLC_CASES().Circle2Arcs, 2, 2, 16, 2, 2 },

            { "ArcsCoinc. - 1st arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2,  0, 1, 1 },
            { "ArcsCoinc. - 1st arc - idx on mid",   SLC_CASES().ArcsCoincident, 2, 2,  3, 1, 1 },
            { "ArcsCoinc. - 1st arc - idx on end",   SLC_CASES().ArcsCoincident, 2, 2,  7, 1, 1 },
            { "ArcsCoinc. - 2nd arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2,  8, 1, 1 },
            { "ArcsCoinc. - 2nd arc - idx on mid",   SLC_CASES().ArcsCoincident, 2, 2, 10, 1, 1 },
            { "ArcsCoinc. - 2nd arc - idx on end",   SLC_CASES().ArcsCoincident, 2, 2, 13, 1, 1 },
            { "ArcsCoinc. - 2nd arc - idx on  -1",   SLC_CASES().ArcsCoincident, 2, 2, -1, 1, 1 },
            { "ArcsCoinc. - invalid idx",            SLC_CASES().ArcsCoincident, 2, 2, 14, 2, 2 },
            { "ArcsCoinc. - 1st arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2,  0, 1, 1 },

            { "A.Co.Closed - 1st arc - idx on start", SLC_CASES().ArcsCoincidentClosed, 3, 2,  1, 2, 1 },
            { "A.Co.Closed - 1st arc - idx on mid",   SLC_CASES().ArcsCoincidentClosed, 3, 2,  3, 2, 1 },
            { "A.Co.Closed - 1st arc - idx on end",   SLC_CASES().ArcsCoincidentClosed, 3, 2,  7, 2, 1 },
            { "A.Co.Closed - 2nd arc - idx on start", SLC_CASES().ArcsCoincidentClosed, 3, 2,  8, 2, 1 },
            { "A.Co.Closed - 2nd arc - idx on mid",   SLC_CASES().ArcsCoincidentClosed, 3, 2, 10, 2, 1 },
            { "A.Co.Closed - 2nd arc - idx on end",   SLC_CASES().ArcsCoincidentClosed, 3, 2, 13, 2, 1 },
            { "A.Co.Closed - 2nd arc - idx on  -1",   SLC_CASES().ArcsCoincidentClosed, 3, 2, -1, 2, 1 },
            { "A.Co.Closed - invalid idx",            SLC_CASES().ArcsCoincidentClosed, 3, 2, 14, 3, 2 },

            { "ArcsIndep. - 1st arc - idx on start", SLC_CASES().ArcsIndependent, 3, 2,  0, 1, 1 },
            { "ArcsIndep. - 1st arc - idx on mid",   SLC_CASES().ArcsIndependent, 3, 2,  3, 1, 1 },
            { "ArcsIndep. - 1st arc - idx on end",   SLC_CASES().ArcsIndependent, 3, 2,  8, 1, 1 },
            { "ArcsIndep. - 2nd arc - idx on start", SLC_CASES().ArcsIndependent, 3, 2,  9, 1, 1 },
            { "ArcsIndep. - 2nd arc - idx on mid",   SLC_CASES().ArcsIndependent, 3, 2, 12, 1, 1 },
            { "ArcsIndep. - 2nd arc - idx on end",   SLC_CASES().ArcsIndependent, 3, 2, 17, 1, 1 },
            { "ArcsIndep. - 2nd arc - idx on  -1",   SLC_CASES().ArcsIndependent, 3, 2, -1, 1, 1 },
            { "ArcsIndep. - invalid idx",            SLC_CASES().ArcsIndependent, 3, 2, 18, 3, 2 },

            { "Dup.Arcs - 1st arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3,  0, 3, 2 },
            { "Dup.Arcs - 1st arc - idx on mid",   SLC_CASES().DuplicateArcs, 4, 3,  3, 3, 2 },
            { "Dup.Arcs - 1st arc - idx on end",   SLC_CASES().DuplicateArcs, 4, 3,  7, 3, 2 },
            { "Dup.Arcs - 2nd arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3,  8, 3, 2 },
            { "Dup.Arcs - 2nd arc - idx on mid",   SLC_CASES().DuplicateArcs, 4, 3, 10, 3, 2 },
            { "Dup.Arcs - 2nd arc - idx on end",   SLC_CASES().DuplicateArcs, 4, 3, 13, 3, 2 },
            { "Dup.Arcs - 3rd arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3, 14, 2, 2 },
            { "Dup.Arcs - 3rd arc - idx on mid",   SLC_CASES().DuplicateArcs, 4, 3, 17, 2, 2 },
            { "Dup.Arcs - 3rd arc - idx on end",   SLC_CASES().DuplicateArcs, 4, 3, 19, 2, 2 },
            { "Dup.Arcs - 3rd arc - idx on  -1",   SLC_CASES().DuplicateArcs, 4, 3, -1, 2, 2 },
            { "Dup.Arcs - invalid idx",            SLC_CASES().DuplicateArcs, 4, 3, 20, 4, 3 },

            { "Arcs Mixed - 1st arc - idx on start", SLC_CASES().ArcsAndSegMixed, 4, 2,  0, 2, 1 },
            { "Arcs Mixed - 1st arc - idx on mid",   SLC_CASES().ArcsAndSegMixed, 4, 2,  3, 2, 1 },
            { "Arcs Mixed - 1st arc - idx on end",   SLC_CASES().ArcsAndSegMixed, 4, 2,  8, 2, 1 },
            { "Arcs Mixed - Straight segment",       SLC_CASES().ArcsAndSegMixed, 4, 2,  9, 3, 2 },
            { "Arcs Mixed - 2nd arc - idx on start", SLC_CASES().ArcsAndSegMixed, 4, 2, 10, 2, 1 },
            { "Arcs Mixed - 2nd arc - idx on mid",   SLC_CASES().ArcsAndSegMixed, 4, 2, 14, 2, 1 },
            { "Arcs Mixed - 2nd arc - idx on end",   SLC_CASES().ArcsAndSegMixed, 4, 2, 18, 2, 1 },
            { "Arcs Mixed - 2nd arc - idx on  -1",   SLC_CASES().ArcsAndSegMixed, 4, 2, -1, 2, 1 },
            { "Arcs Mixed - invalid idx",            SLC_CASES().ArcsAndSegMixed, 4, 2, 19, 4, 2 }
        };


BOOST_AUTO_TEST_CASE( RemoveShape )
{
    for( const REMOVE_SHAPE_CASE& c : remove_shape_cases )
    {
        BOOST_TEST_CONTEXT( c.m_ctx_name )
        {
            SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
            BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
            BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
            slc_case.RemoveShape( c.m_remove_index );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
            BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_expected_arc_count );
            BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
        }
    }
}


BOOST_AUTO_TEST_CASE( RemoveShapeAfterSimplify )
{
    for( const REMOVE_SHAPE_CASE& c : remove_shape_cases )
    {
        BOOST_TEST_CONTEXT( c.m_ctx_name )
        {
            SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
            BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
            BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
            slc_case.Simplify();
            BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
            BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
            slc_case.RemoveShape( c.m_remove_index );
            BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
            BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
            BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_expected_arc_count );
        }
    }
}


BOOST_AUTO_TEST_CASE( ShapeCount )
{
    BOOST_CHECK_EQUAL( Circle1Arc.ShapeCount(), 1 );
    BOOST_CHECK_EQUAL( Circle2Arcs.ShapeCount(), 2 );
    BOOST_CHECK_EQUAL( ArcsCoincident.ShapeCount(), 2 );
    BOOST_CHECK_EQUAL( ArcsCoincidentClosed.ShapeCount(), 3 );
    BOOST_CHECK_EQUAL( DuplicateArcs.ShapeCount(), 4 );
    BOOST_CHECK_EQUAL( ArcAndPoint.ShapeCount(), 2 );
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.ShapeCount(), 4 );
    BOOST_CHECK_EQUAL( SegAndArcCoincident.ShapeCount(), 2 );
    BOOST_CHECK_EQUAL( EmptyChain.ShapeCount(), 0 );
    BOOST_CHECK_EQUAL( OnePoint.ShapeCount(), 0 );
    BOOST_CHECK_EQUAL( TwoPoints.ShapeCount(), 1 );
    BOOST_CHECK_EQUAL( ThreePoints.ShapeCount(), 2 );
}


BOOST_AUTO_TEST_CASE( NextShape )
{
    BOOST_CHECK_EQUAL( Circle1Arc.NextShape( 0 ), -1 ); //only one arc

    BOOST_CHECK_EQUAL( Circle2Arcs.NextShape( 0 ), 8 ); // next shape "Arc0b"
    BOOST_CHECK_EQUAL( Circle2Arcs.NextShape( 8 ), -1 ); //no more shapes (last point joins with first, part of arc)

    BOOST_CHECK_EQUAL( ArcsCoincident.NextShape( 0 ), 8 ); // next shape "Arc1"
    BOOST_CHECK_EQUAL( ArcsCoincident.NextShape( 8 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 0 ), 8 ); // next shape "Arc1"
    BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 8 ), 13 ); //next shape is hidden segment joining last/first
    BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 13 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 0 ), 8 ); // next shape straight seg
    BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 8 ), 9 ); //next shape second arc
    BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 9 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 0 ), 8 ); // next shape "Arc1"
    BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 8 ), 13 ); // next shape hidden segment joining the 2 duplicate arcs
    BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 13 ), 14 ); // next shape "Arc1" (duplicate)
    BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 14 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( ArcAndPoint.NextShape( 0 ), 8 ); // next shape straight segment (end of arc->point)
    BOOST_CHECK_EQUAL( ArcAndPoint.NextShape( 8 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 0 ), 8 ); // next shape straight segment (end of arc->point)
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 8 ), 9 ); // next shape straight segment (point->begining of arc)
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 9 ), 10 ); //next shape second arc
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 10 ), -1 ); //no more shapes
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 20 ), -1 ); //invalid indices should still work
    BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( -50 ), -1 ); //invalid indices should still work

    BOOST_CHECK_EQUAL( SegAndArcCoincident.NextShape( 0 ), 1 ); // next shape Arc3
    BOOST_CHECK_EQUAL( SegAndArcCoincident.NextShape( 1 ), -1 ); //no more shapes

    BOOST_CHECK_EQUAL( EmptyChain.NextShape( 0 ), -1 );
    BOOST_CHECK_EQUAL( EmptyChain.NextShape( 1 ), -1 ); //invalid indices should still work
    BOOST_CHECK_EQUAL( EmptyChain.NextShape( 2 ), -1 ); //invalid indices should still work
    BOOST_CHECK_EQUAL( EmptyChain.NextShape( -2 ), -1 ); //invalid indices should still work

    BOOST_CHECK_EQUAL( OnePoint.NextShape( 0 ), -1 );
    BOOST_CHECK_EQUAL( OnePoint.NextShape( -1 ), -1 );
    BOOST_CHECK_EQUAL( OnePoint.NextShape( 1 ), -1 );  //invalid indices should still work
    BOOST_CHECK_EQUAL( OnePoint.NextShape( 2 ), -1 ); //invalid indices should still work
    BOOST_CHECK_EQUAL( OnePoint.NextShape( -2 ), -1 ); //invalid indices should still work

    BOOST_CHECK_EQUAL( TwoPoints.NextShape( 0 ), -1 );
    BOOST_CHECK_EQUAL( TwoPoints.NextShape( 1 ), -1 );
    BOOST_CHECK_EQUAL( TwoPoints.NextShape( -1 ), -1 );

    BOOST_CHECK_EQUAL( ThreePoints.NextShape( 0 ), 1 );
    BOOST_CHECK_EQUAL( ThreePoints.NextShape( 1 ), -1 );
    BOOST_CHECK_EQUAL( ThreePoints.NextShape( 2 ), -1 );
    BOOST_CHECK_EQUAL( ThreePoints.NextShape( -1 ), -1 );
}



BOOST_AUTO_TEST_CASE( AppendArc )
{
    BOOST_TEST_CONTEXT( "Case 1: Arc mid point nearly collinear" )
    {
        SHAPE_ARC arc( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 2499 ), VECTOR2I( -100000, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 100000, 0 ) ); //arc start
        BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( -100000, 0 ) ); //arc end
        BOOST_CHECK_EQUAL( chain.GetPoint( -1 ), VECTOR2I( -100000, 0 ) ); //arc end
    }

    BOOST_TEST_CONTEXT( "Case 2: Arc = Large Circle" )
    {
        SHAPE_ARC        arc( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 0 ), VECTOR2I( 100000, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 1 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 10 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 100000, 0 ) );   //arc start
        BOOST_CHECK_EQUAL( chain.GetPoint( 9 ), VECTOR2I( 100000, 0 ) );  //arc end
        BOOST_CHECK_EQUAL( chain.GetPoint( -1 ), VECTOR2I( 100000, 0 ) ); //arc end
    }

    BOOST_TEST_CONTEXT( "Case 3: Arc = Small Circle (approximate to point)" )
    {
        SHAPE_ARC        arc( VECTOR2I( 2499, 0 ), VECTOR2I( 0, 0 ), VECTOR2I( 2499, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 1 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) );  //arc start
    }

    BOOST_TEST_CONTEXT( "Case 3: Small Arc (approximate to segment)" )
    {
        SHAPE_ARC        arc( VECTOR2I( 1767, 0 ), VECTOR2I( 2499, 2499 ), VECTOR2I( 0, 1767 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 1767, 0 ) ); //arc start
        BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 0, 1767 ) ); //arc end
    }

    BOOST_TEST_CONTEXT( "Case 4: Arc = null arc (all points coincident)" )
    {
        SHAPE_ARC        arc( VECTOR2I( 2499, 0 ), VECTOR2I( 2499, 0 ), VECTOR2I( 2499, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 1 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) );    //arc start
    }

    BOOST_TEST_CONTEXT( "Case 5: Arc = infinite radius (all points very close)" )
    {
        SHAPE_ARC        arc( VECTOR2I( 2499, 0 ), VECTOR2I( 2500, 0 ), VECTOR2I( 2501, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) ); //arc start
        BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 2501, 0 ) ); //arc end
    }

    BOOST_TEST_CONTEXT( "Case 6: Arc = large radius (all points very close)" )
    {
        SHAPE_ARC arc( VECTOR2I( -100000, 0 ), VECTOR2I( 0, 1 ), VECTOR2I( 100000, 0 ), 0 );
        SHAPE_LINE_CHAIN chain;
        chain.Append( arc, 5000 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
        BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
        BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
        BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( -100000, 0 ) ); //arc start
        BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 100000, 0 ) );  //arc end
    }
}


// Test special case where the last arc in the chain has a shared point with the first arc
BOOST_AUTO_TEST_CASE( ArcWrappingToStartSharedPoints )
{
    // represent a circle with two semicircular arcs
    SHAPE_ARC arc1( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( -100000, 0 ), 0 );
    SHAPE_ARC arc2( VECTOR2I( -100000, 0 ), VECTOR2I( 0, -100000 ), VECTOR2I( 100000, 0 ), 0 );

    // Start a chain with the two arcs
    SHAPE_LINE_CHAIN chain;
    chain.Append( arc1 );
    chain.Append( arc2 );
    BOOST_CHECK_EQUAL( chain.PointCount(), 13 );
    //BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    // OPEN CHAIN
    // Start of the chain is not yet a shared point, so can't be an arc end either
    BOOST_CHECK_EQUAL( chain.IsSharedPt( 0 ), false );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( 0 ), false );
    BOOST_CHECK_EQUAL( chain.IsArcStart( 0 ), true );

    // Index 6 is the shared point between the two arcs in the middle of the chain
    BOOST_CHECK_EQUAL( chain.IsSharedPt( 6 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( 6 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcStart( 6 ), true );

    // End index is not yet a shared point
    int endIndex = chain.PointCount() - 1;
    BOOST_CHECK_EQUAL( chain.IsSharedPt( endIndex ), false );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( endIndex ), true );
    BOOST_CHECK_EQUAL( chain.IsArcStart( endIndex ), false );

    for( int i = 0; i < chain.PointCount(); i++ )
    {
        BOOST_CHECK_EQUAL( chain.IsPtOnArc( i ), true ); // all points in the chain are arcs
    }

    // CLOSED CHAIN
    chain.SetClosed( true );
    BOOST_CHECK_EQUAL( chain.PointCount(), 12 ); // (-1) should have removed coincident points
    //BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    // Start of the chain should be a shared point now, so can't be an arc end either
    BOOST_CHECK_EQUAL( chain.IsSharedPt( 0 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( 0 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcStart( 0 ), true );

    // Index 6 is the shared point between the two arcs in the middle of the chain
    BOOST_CHECK_EQUAL( chain.IsSharedPt( 6 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( 6 ), true );
    BOOST_CHECK_EQUAL( chain.IsArcStart( 6 ), true );

    // End index is in the middle of an arc, so not an end point or shared point
    endIndex = chain.PointCount() - 1;
    BOOST_CHECK_EQUAL( chain.IsSharedPt( endIndex ), false );
    BOOST_CHECK_EQUAL( chain.IsArcEnd( endIndex ), false );
    BOOST_CHECK_EQUAL( chain.IsArcStart( endIndex ), false );
}

// Test SHAPE_LINE_CHAIN::Split()
BOOST_AUTO_TEST_CASE( Split )
{
    SEG       seg1( VECTOR2I( 0, 100000 ), VECTOR2I( 50000, 0 ) );
    SEG       seg2( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
    SHAPE_ARC arc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );

    // Start a chain with 2 points (seg1)
    SHAPE_LINE_CHAIN chain( { seg1.A, seg1.B } );
    BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
    // Add first arc
    chain.Append( arc );
    BOOST_CHECK_EQUAL( chain.PointCount(), 9 );
    // Add two points (seg2)
    chain.Append( seg2.A );
    chain.Append( seg2.B );
    BOOST_CHECK_EQUAL( chain.PointCount(), 11 );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    BOOST_TEST_CONTEXT( "Case 1: Point not in the chain" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        BOOST_CHECK_EQUAL( chainCopy.Split( VECTOR2I( 400000, 0 ) ), -1 );
        BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
        BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
    }

    BOOST_TEST_CONTEXT( "Case 2: Point close to start of a segment" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        VECTOR2I         splitPoint = seg1.A + VECTOR2I( 5, -10 );
        BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 1 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
        BOOST_CHECK_EQUAL( chainCopy.GetPoint( 1 ), splitPoint );
        BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() + 1 ); // new point added
        BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
    }

    BOOST_TEST_CONTEXT( "Case 3: Point exactly on the segment" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        VECTOR2I         splitPoint = seg1.B;
        BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 1 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
        BOOST_CHECK_EQUAL( chainCopy.GetPoint( 1 ), splitPoint );
        BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
        BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
    }

    BOOST_TEST_CONTEXT( "Case 4: Point at start of arc" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        VECTOR2I         splitPoint = arc.GetP0();
        BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 2 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
        BOOST_CHECK_EQUAL( chainCopy.GetPoint( 2 ), splitPoint );
        BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
        BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
    }

    BOOST_TEST_CONTEXT( "Case 5: Point close to start of arc" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        VECTOR2I         splitPoint = arc.GetP0() + VECTOR2I( -10, 130 );
        BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 3 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
        BOOST_CHECK_EQUAL( chainCopy.GetPoint( 3 ), splitPoint );
        BOOST_CHECK_EQUAL( chainCopy.IsSharedPt( 3 ), true ); // must be a shared point
        BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() + 1 ); // new point added
        BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() + 1 ); // new arc should have been created
    }
}


// Test SHAPE_LINE_CHAIN::Slice()
BOOST_AUTO_TEST_CASE( Slice )
{
    SEG       targetSegment( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
    SHAPE_ARC firstArc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );
    SHAPE_ARC secondArc( VECTOR2I( -200000, -200000 ), VECTOR2I( -300000, -100000 ), -ANGLE_180 );
    int       tol = SHAPE_ARC::DefaultAccuracyForPCB(); // Tolerance for arc collisions

    // Start a chain with 3 points
    SHAPE_LINE_CHAIN chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( 100000, 0 ) } );
    BOOST_CHECK_EQUAL( chain.PointCount(), 3 );
    // Add first arc
    chain.Append( firstArc );
    BOOST_CHECK_EQUAL( chain.PointCount(), 10 );
    // Add two points (target segment)
    chain.Append( targetSegment.A );
    chain.Append( targetSegment.B );
    BOOST_CHECK_EQUAL( chain.PointCount(), 12 );
    // Add a second arc
    chain.Append( secondArc );
    BOOST_CHECK_EQUAL( chain.PointCount(), 20 );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    //////////////////////////////////////////////////////////
    /// CASE 1: Start at arc endpoint, finish middle of arc  /
    //////////////////////////////////////////////////////////
    BOOST_TEST_CONTEXT( "Case 1: Start at arc endpoint, finish middle of arc" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 9, 18 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
        SHAPE_ARC expectedSliceArc0;
        expectedSliceArc0.ConstructFromStartEndCenter( secondArc.GetP0(), chain.GetPoint( 18 ),
                                                       secondArc.GetCenter(),
                                                       secondArc.IsClockwise() );

        BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP0(), expectedSliceArc0.GetP0() ); // equal arc start points
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP1(), tol ) );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 10 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), firstArc.GetP1() ); // equal to arc end
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 1 ), targetSegment.A );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 2 ), targetSegment.B );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 3 ), expectedSliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 3 ), true );

        for( int i = 4; i <= 8; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 3; i <= 7; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 9 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 9 ), expectedSliceArc0.GetP1() ); // equal to arc end
    }

    //////////////////////////////////////////////////////////////////
    /// CASE 2: Start at middle of an arc, finish at arc startpoint  /
    //////////////////////////////////////////////////////////////////
    BOOST_TEST_CONTEXT( "Case 2: Start at middle of an arc, finish at arc startpoint" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 5, 12 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
        SHAPE_ARC expectedSliceArc0;
        expectedSliceArc0.ConstructFromStartEndCenter( chain.GetPoint( 5 ), firstArc.GetP1(),
                                                       firstArc.GetCenter(),
                                                       firstArc.IsClockwise() );

        BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP1(),
                           expectedSliceArc0.GetP1() ); // equal arc end points
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP0(), tol ) );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 8 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ),
                           expectedSliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 4; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 3; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 4 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 4 ),
                           expectedSliceArc0.GetP1() ); // equal to arc end

        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 5 ), targetSegment.A );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), targetSegment.B );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 7 ), secondArc.GetP0() );
    }

    //////////////////////////////////////////////////////////////////
    /// CASE 3: Full arc, nothing else                               /
    //////////////////////////////////////////////////////////////////
    BOOST_TEST_CONTEXT( "Case 3: Full arc, nothing else" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 3, 9 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
        SHAPE_ARC sliceArc0 = sliceResult.Arc( 0 );

        // Equal arc to original inserted arc
        BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
        BOOST_CHECK_EQUAL( firstArc.GetArcMid(), sliceArc0.GetArcMid() );
        BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 7 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), sliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 6; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 5; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 6 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), sliceArc0.GetP1() ); // equal to arc end
    }

    //////////////////////////////////////////////////////////////////
    /// CASE 4: Full arc, and straight segments to next arc start    /
    //////////////////////////////////////////////////////////////////
    BOOST_TEST_CONTEXT( "Case 4:  Full arc, and straight segments to next arc start" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 3, 12 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
        SHAPE_ARC sliceArc0 = sliceResult.Arc( 0 );

        // Equal arc to original inserted arc
        BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
        BOOST_CHECK_EQUAL( firstArc.GetArcMid(), sliceArc0.GetArcMid() );
        BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 10 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), sliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 6; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 5; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 6 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), sliceArc0.GetP1() ); // equal to arc end

        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 7 ), targetSegment.A );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 8 ), targetSegment.B );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 9 ), secondArc.GetP0() );
    }

    BOOST_TEST_CONTEXT( "Case 5: Chain ends in arc and point" )
    {
        SHAPE_LINE_CHAIN chainCopy = chain;
        chainCopy.Append( VECTOR2I( 400000, 400000 ) );

        SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 11, -1 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 400000, 400000 ) );
    }

    BOOST_TEST_CONTEXT( "Case 6: Start to end, chain with one point" )
    {
        SHAPE_LINE_CHAIN chainCopy = SLC_CASES().OnePoint;

        SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 0, -1 );
        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 1 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), VECTOR2I( 233450000, 228360000 ) );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 233450000, 228360000 ) ); // Same as index 0
    }

    BOOST_TEST_CONTEXT( "Case 7: Start to end, chain with two points" )
    {
        SHAPE_LINE_CHAIN chainCopy = SLC_CASES().TwoPoints;

        SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 0, -1 );
        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 2 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), VECTOR2I( 233450000, 228360000 ) );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 1 ), VECTOR2I( 263450000, 258360000 ) );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 263450000, 258360000 ) ); // Same as index 1
    }

    BOOST_TEST_CONTEXT( "Case 8: Full 2nd arc, nothing else" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 12, 19 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
        SHAPE_ARC sliceArc0 = sliceResult.Arc( 0 );

        // Equal arc to original inserted arc
        BOOST_CHECK_EQUAL( secondArc.GetP1(), sliceArc0.GetP1() );
        BOOST_CHECK_EQUAL( secondArc.GetArcMid(), sliceArc0.GetArcMid() );
        BOOST_CHECK_EQUAL( secondArc.GetP1(), sliceArc0.GetP1() );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 8 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), sliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 7; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 6; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 7 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 7 ), sliceArc0.GetP1() ); // equal to arc end
    }

    BOOST_TEST_CONTEXT( "Case 9: Start at middle of a 2nd arc, finish at end" )
    {
        SHAPE_LINE_CHAIN sliceResult = chain.Slice( 16, 19 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );

        SHAPE_ARC expectedSliceArc0;
        expectedSliceArc0.ConstructFromStartEndCenter( chain.GetPoint( 16 ), secondArc.GetP1(),
                                                       secondArc.GetCenter(),
                                                       secondArc.IsClockwise() );

        BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP1(),
                           expectedSliceArc0.GetP1() ); // equal arc end points
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP0(), tol ) );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 4 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ),
                           expectedSliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 3; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 2; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 3 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 3 ),
                           expectedSliceArc0.GetP1() ); // equal to arc end
    }

    BOOST_TEST_CONTEXT( "Case 10: New chain, start at arc middle, finish at end" )
    {
        SHAPE_LINE_CHAIN chain10;
        chain10.Append( firstArc );

        SHAPE_LINE_CHAIN sliceResult = chain10.Slice( 3, 6 );
        BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );

        BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );

        SHAPE_ARC expectedSliceArc0;
        expectedSliceArc0.ConstructFromStartEndCenter( chain10.GetPoint( 3 ), firstArc.GetP1(),
                                                       firstArc.GetCenter(),
                                                       firstArc.IsClockwise() );

        BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP1(),
                           expectedSliceArc0.GetP1() ); // equal arc end points
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
        BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP0(), tol ) );

        BOOST_CHECK_EQUAL( sliceResult.PointCount(), 4 );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ),
                           expectedSliceArc0.GetP0() ); // equal to arc start
        BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );

        for( int i = 1; i <= 3; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );

        for( int i = 0; i <= 2; i++ )
            BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );

        BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 3 ), true );
        BOOST_CHECK_EQUAL( sliceResult.GetPoint( 3 ),
                           expectedSliceArc0.GetP1() ); // equal to arc end
    }
}


// Test SHAPE_LINE_CHAIN::NearestPoint( VECTOR2I )
BOOST_AUTO_TEST_CASE( NearestPointPt )
{
    SEG       seg1( VECTOR2I( 0, 100000 ), VECTOR2I( 50000, 0 ) );
    SEG       seg2( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
    SHAPE_ARC arc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );

    // Start a chain with 2 points (seg1)
    SHAPE_LINE_CHAIN chain( { seg1.A, seg1.B } );
    BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
    // Add first arc
    chain.Append( arc );
    BOOST_CHECK_EQUAL( chain.PointCount(), 9 );
    // Add two points (seg2)
    chain.Append( seg2.A );
    chain.Append( seg2.B );
    BOOST_CHECK_EQUAL( chain.PointCount(), 11 );
    BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );

    VECTOR2I ptOnArcCloseToStart( 297553, 31697 ); //should be index 3 in chain
    VECTOR2I ptOnArcCloseToEnd( 139709, 82983 ); //should be index 6 in chain

    BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, true ), ptOnArcCloseToStart );
    BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, false ), arc.GetP0() );

    BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, true ), ptOnArcCloseToEnd );
    BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, false ), arc.GetP1() );
}


// Test SHAPE_LINE_CHAIN::Replace( SHAPE_LINE_CHAIN )
BOOST_AUTO_TEST_CASE( ReplaceChain )
{
    BOOST_TEST_INFO( "8949 crash" );

    std::vector<VECTOR2I> linePts = {
        { 206000000, 140110000 }, { 192325020, 140110000 }, { 192325020, 113348216 },
        { 192251784, 113274980 }, { 175548216, 113274980 }, { 175474980, 113348216 },
        { 175474980, 136694980 }, { 160774511, 121994511 }, { 160774511, 121693501 },
        { 160086499, 121005489 }, { 159785489, 121005489 }, { 159594511, 120814511 },
        { 160086499, 120814511 }, { 160774511, 120126499 }, { 160774511, 119153501 },
        { 160086499, 118465489 }, { 159113501, 118465489 }, { 158425489, 119153501 },
        { 158425489, 119645489 }, { 157325020, 118545020 }, { 157325020, 101925020 },
        { 208674980, 101925020 }, { 208674980, 145474980 }, { 192325020, 145474980 },
        { 192325020, 140110000 }
    };

    SHAPE_LINE_CHAIN baseChain( linePts, false );
    baseChain.SetWidth( 250000 );
    BOOST_CHECK_EQUAL( baseChain.PointCount(), linePts.size() );

    SHAPE_LINE_CHAIN replaceChain( { VECTOR2I( 192325020, 140110000 ) }, false );
    BOOST_CHECK_EQUAL( replaceChain.PointCount(), 1 );

    baseChain.Replace( 1, 23, replaceChain );

    BOOST_CHECK_EQUAL( baseChain.PointCount(), linePts.size() - ( 23 - 1 ) );

    // Replacing the last point in a chain is special-cased
    baseChain.Replace( baseChain.PointCount() - 1, baseChain.PointCount() - 1, VECTOR2I( -1, -1 ) );

    BOOST_CHECK_EQUAL( baseChain.CLastPoint(), VECTOR2I( -1, -1 ) );
}


BOOST_AUTO_TEST_SUITE_END()
