/*
 * 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 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

    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 );

        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 ) );
    }
};


BOOST_FIXTURE_TEST_SUITE( TestShapeLineChain, SLC_CASES )

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 },
            { "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 );
        }
    }
}


// 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 ) );
}


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( 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( 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
    }
}


// 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()
