/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright The 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 3
 * of the License, or (at your option) any later version.
 */

#include <geometry/shape_poly_set.h>
#include <geometry/shape_line_chain.h>
#include <geometry/polygon_triangulation.h>
#include <trigo.h>
#include <thread>
#include <chrono>
#include <future>

#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"

BOOST_AUTO_TEST_SUITE( PolygonTriangulation )

// Helper class to properly manage TRIANGULATED_POLYGON lifecycle
class TRIANGULATION_TEST_FIXTURE
{
public:
    TRIANGULATION_TEST_FIXTURE() : m_result( std::make_unique<SHAPE_POLY_SET::TRIANGULATED_POLYGON>(0) )
    {
    }

    SHAPE_POLY_SET::TRIANGULATED_POLYGON& GetResult() { return *m_result; }

    std::unique_ptr<POLYGON_TRIANGULATION> CreateTriangulator()
    {
        return std::make_unique<POLYGON_TRIANGULATION>( *m_result );
    }

private:
    std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON> m_result;
};

// Helper function to create a simple square
SHAPE_LINE_CHAIN createSquare( int size = 100, VECTOR2I offset = VECTOR2I(0, 0) )
{
    SHAPE_LINE_CHAIN chain;
    chain.Append( offset.x, offset.y );
    chain.Append( offset.x + size, offset.y );
    chain.Append( offset.x + size, offset.y + size );
    chain.Append( offset.x, offset.y + size );
    chain.SetClosed( true );
    return chain;
}

// Helper function to create a triangle
SHAPE_LINE_CHAIN createTriangle( int size = 100, VECTOR2I offset = VECTOR2I(0, 0) )
{
    SHAPE_LINE_CHAIN chain;
    chain.Append( offset.x, offset.y );
    chain.Append( offset.x + size, offset.y );
    chain.Append( offset.x + size/2, offset.y + size );
    chain.SetClosed( true );
    return chain;
}

// Helper function to create a complex concave polygon
SHAPE_LINE_CHAIN createConcavePolygon( int size = 100 )
{
    SHAPE_LINE_CHAIN chain;
    chain.Append( 0, 0 );
    chain.Append( size, 0 );
    chain.Append( size, size/2 );
    chain.Append( size/2, size/2 );  // Create concave section
    chain.Append( size/2, size );
    chain.Append( 0, size );
    chain.SetClosed( true );
    return chain;
}

// Helper function to validate triangulation result with comprehensive checks
bool validateTriangulation( const SHAPE_POLY_SET::TRIANGULATED_POLYGON& result,
                           const SHAPE_LINE_CHAIN& original, bool strict = true )
{
    // Basic validation
    if( result.GetVertexCount() == 0 )
        return false;

    size_t triangleCount = result.GetTriangleCount();
    if( triangleCount == 0 )
        return false;

    // Validate triangle topology
    for( size_t i = 0; i < triangleCount; i++ )
    {
        const auto& triangle = result.Triangles()[i];

        // Check valid vertex indices
        if( triangle.a >= (int)result.GetVertexCount() ||
            triangle.b >= (int)result.GetVertexCount() ||
            triangle.c >= (int)result.GetVertexCount() )
        {
            return false;
        }

        // Triangle vertices should not be the same
        if( triangle.a == triangle.b || triangle.b == triangle.c || triangle.a == triangle.c )
            return false;

        // Check triangle area is positive (counter-clockwise orientation)
        if( strict && triangle.Area() <= 0 )
            return false;
    }

    // Validate that original vertices are preserved
    if( strict && result.GetVertexCount() >= original.PointCount() )
    {
        const auto& vertices = result.Vertices();
        for( int i = 0; i < original.PointCount(); i++ )
        {
            bool found = false;
            for( size_t j = 0; j < vertices.size(); j++ )
            {
                if( vertices[j] == original.CPoint( i ) )
                {
                    found = true;
                    break;
                }
            }
            if( !found )
                return false;
        }
    }

    return true;
}

// Core functionality tests
BOOST_AUTO_TEST_CASE( BasicTriangleTriangulation )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    SHAPE_LINE_CHAIN triangle = createTriangle();

    bool success = triangulator->TesselatePolygon( triangle, nullptr );

    BOOST_TEST( success );
    BOOST_TEST( fixture.GetResult().GetVertexCount() == 3 );
    BOOST_TEST( fixture.GetResult().GetTriangleCount() == 1 );
    BOOST_TEST( validateTriangulation( fixture.GetResult(), triangle ) );
}

BOOST_AUTO_TEST_CASE( BasicSquareTriangulation )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    SHAPE_LINE_CHAIN square = createSquare();

    bool success = triangulator->TesselatePolygon( square, nullptr );

    BOOST_TEST( success );
    BOOST_TEST( fixture.GetResult().GetVertexCount() == 4 );
    BOOST_TEST( fixture.GetResult().GetTriangleCount() == 2 );
    BOOST_TEST( validateTriangulation( fixture.GetResult(), square ) );
}

BOOST_AUTO_TEST_CASE( ConcavePolygonTriangulation )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    SHAPE_LINE_CHAIN concave = createConcavePolygon(100000);

    bool success = triangulator->TesselatePolygon( concave, nullptr );

    BOOST_TEST( success );

    const auto& result = fixture.GetResult();
    bool isValid = validateTriangulation( result, concave );
    size_t triangleCount = result.GetTriangleCount();

    // Print diagnostic information if validation fails or triangle count is unexpected
    if( !success || !isValid || triangleCount < 4 )
    {
        std::cout << "\n=== ConcavePolygonTriangulation Diagnostic Output ===" << std::endl;
        std::cout << "Success: " << (success ? "true" : "false") << std::endl;
        std::cout << "Validation: " << (isValid ? "true" : "false") << std::endl;
        std::cout << "Triangle count: " << triangleCount << " (expected >= 4)" << std::endl;
        std::cout << "Vertex count: " << result.GetVertexCount() << std::endl;

        // Print input polygon vertices
        std::cout << "\nInput polygon vertices (" << concave.PointCount() << " points):" << std::endl;
        for( int i = 0; i < concave.PointCount(); i++ )
        {
            VECTOR2I pt = concave.CPoint( i );
            std::cout << "  [" << i << "]: (" << pt.x << ", " << pt.y << ")" << std::endl;
        }

        // Print result vertices
        std::cout << "\nResult vertices (" << result.GetVertexCount() << " points):" << std::endl;
        const auto& vertices = result.Vertices();
        for( size_t i = 0; i < vertices.size(); i++ )
        {
            std::cout << "  [" << i << "]: (" << vertices[i].x << ", " << vertices[i].y << ")" << std::endl;
        }

        // Print triangles
        std::cout << "\nTriangles found (" << triangleCount << " triangles):" << std::endl;
        const auto& triangles = result.Triangles();
        for( size_t i = 0; i < triangles.size(); i++ )
        {
            const auto& tri = triangles[i];
            VECTOR2I va = vertices[tri.a];
            VECTOR2I vb = vertices[tri.b];
            VECTOR2I vc = vertices[tri.c];
            double area = tri.Area();

            std::cout << "  Triangle[" << i << "]: indices(" << tri.a << "," << tri.b << "," << tri.c << ")" << std::endl;
            std::cout << "    A: (" << va.x << ", " << va.y << ")" << std::endl;
            std::cout << "    B: (" << vb.x << ", " << vb.y << ")" << std::endl;
            std::cout << "    C: (" << vc.x << ", " << vc.y << ")" << std::endl;
            std::cout << "    Area: " << area << std::endl;

            // Check for degenerate triangles
            if( area <= 0 )
                std::cout << "    *** DEGENERATE TRIANGLE (area <= 0) ***" << std::endl;
            if( tri.a == tri.b || tri.b == tri.c || tri.a == tri.c )
                std::cout << "    *** INVALID TRIANGLE (duplicate vertex indices) ***" << std::endl;
        }

        // Additional diagnostic information
        if( triangleCount > 0 )
        {
            double totalArea = 0.0;
            for( const auto& tri : triangles )
                totalArea += tri.Area();
            std::cout << "\nTotal triangulated area: " << totalArea << std::endl;

            // Calculate expected area of concave polygon for comparison
            double originalArea = std::abs( concave.Area() );
            std::cout << "Original polygon area: " << originalArea << std::endl;
            std::cout << "Area difference: " << std::abs( totalArea - originalArea ) << std::endl;
        }

        std::cout << "================================================\n" << std::endl;
    }

    BOOST_TEST( success );
    BOOST_TEST( isValid );
    // L-shaped concave polygons should have 4 triangles
    BOOST_TEST( triangleCount == 4 );
}


BOOST_AUTO_TEST_CASE( HintDataOptimization )
{
    // First triangulation without hint
    TRIANGULATION_TEST_FIXTURE fixture1;
    auto triangulator1 = fixture1.CreateTriangulator();
    SHAPE_LINE_CHAIN square = createSquare();

    bool success1 = triangulator1->TesselatePolygon( square, nullptr );
    BOOST_TEST( success1 );

    // Second triangulation with hint data from first
    TRIANGULATION_TEST_FIXTURE fixture2;
    auto triangulator2 = fixture2.CreateTriangulator();

    bool success2 = triangulator2->TesselatePolygon( square, &fixture1.GetResult() );
    BOOST_TEST( success2 );

    // Results should be identical when hint is applicable
    BOOST_TEST( fixture1.GetResult().GetVertexCount() == fixture2.GetResult().GetVertexCount() );
    BOOST_TEST( fixture1.GetResult().GetTriangleCount() == fixture2.GetResult().GetTriangleCount() );
}

BOOST_AUTO_TEST_CASE( HintDataInvalidation )
{
    // Create hint data with different vertex count
    TRIANGULATION_TEST_FIXTURE hintFixture;
    auto hintTriangulator = hintFixture.CreateTriangulator();
    SHAPE_LINE_CHAIN triangle = createTriangle();
    hintTriangulator->TesselatePolygon( triangle, nullptr );

    // Try to use hint with different polygon (should ignore hint)
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();
    SHAPE_LINE_CHAIN square = createSquare();

    bool success = triangulator->TesselatePolygon( square, &hintFixture.GetResult() );
    BOOST_TEST( success );
    BOOST_TEST( validateTriangulation( fixture.GetResult(), square ) );
}

// Degenerate case handling
BOOST_AUTO_TEST_CASE( DegeneratePolygons )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Test empty polygon
    SHAPE_LINE_CHAIN empty;
    bool success = triangulator->TesselatePolygon( empty, nullptr );
    BOOST_TEST( success ); // Should handle gracefully

    // Test single point
    TRIANGULATION_TEST_FIXTURE fixture2;
    auto triangulator2 = fixture2.CreateTriangulator();
    SHAPE_LINE_CHAIN singlePoint;
    singlePoint.Append( 0, 0 );
    singlePoint.SetClosed( true );
    success = triangulator2->TesselatePolygon( singlePoint, nullptr );
    BOOST_TEST( success ); // Should handle gracefully

    // Test two points (line segment)
    TRIANGULATION_TEST_FIXTURE fixture3;
    auto triangulator3 = fixture3.CreateTriangulator();
    SHAPE_LINE_CHAIN line;
    line.Append( 0, 0 );
    line.Append( 100, 0 );
    line.SetClosed( true );
    success = triangulator3->TesselatePolygon( line, nullptr );
    BOOST_TEST( success ); // Should handle gracefully
}

BOOST_AUTO_TEST_CASE( ZeroAreaPolygon )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Create a polygon with zero area (all points collinear)
    SHAPE_LINE_CHAIN zeroArea;
    zeroArea.Append( 0, 0 );
    zeroArea.Append( 100, 0 );
    zeroArea.Append( 50, 0 );
    zeroArea.Append( 25, 0 );
    zeroArea.SetClosed( true );

    bool success = triangulator->TesselatePolygon( zeroArea, nullptr );

    BOOST_TEST( success ); // Should handle gracefully without crashing
}

// Memory management and lifecycle tests
BOOST_AUTO_TEST_CASE( MemoryManagement )
{
    // Test that multiple triangulations properly manage memory
    for( int i = 0; i < 100; i++ )
    {
        TRIANGULATION_TEST_FIXTURE fixture;
        auto triangulator = fixture.CreateTriangulator();

        SHAPE_LINE_CHAIN poly = createSquare( 100 + i, VECTOR2I( i, i ) );
        bool success = triangulator->TesselatePolygon( poly, nullptr );

        BOOST_TEST( success );
        BOOST_TEST( validateTriangulation( fixture.GetResult(), poly, false ) );
    }
}

BOOST_AUTO_TEST_CASE( LargePolygonStressTest )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Create a large polygon (regular polygon with many vertices)
    SHAPE_LINE_CHAIN largePoly;
    int numVertices = 1000;
    int radius = 10000;

    for( int i = 0; i < numVertices; i++ )
    {
        double angle = 2.0 * M_PI * i / numVertices;
        int x = static_cast<int>( radius * cos( angle ) );
        int y = static_cast<int>( radius * sin( angle ) );
        largePoly.Append( x, y );
    }
    largePoly.SetClosed( true );

    auto start = std::chrono::high_resolution_clock::now();
    bool success = triangulator->TesselatePolygon( largePoly, nullptr );
    auto end = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );

    BOOST_TEST( success );
    if( success )
    {
        BOOST_TEST( validateTriangulation( fixture.GetResult(), largePoly, false ) );
        BOOST_TEST( fixture.GetResult().GetTriangleCount() > 0 );
    }

    // Performance check - should complete in reasonable time
    BOOST_TEST( duration.count() < 10000 ); // Less than 10 seconds
}

// Thread safety tests (following SHAPE_POLY_SET patterns)
BOOST_AUTO_TEST_CASE( ConcurrentTriangulation )
{
    const int numThreads = 4;
    const int numTriangulationsPerThread = 10;

    std::vector<std::future<bool>> futures;

    for( int t = 0; t < numThreads; t++ )
    {
        futures.push_back( std::async( std::launch::async, [t, numTriangulationsPerThread]()
        {
            for( int i = 0; i < numTriangulationsPerThread; i++ )
            {
                TRIANGULATION_TEST_FIXTURE fixture;
                auto triangulator = fixture.CreateTriangulator();

                // Create unique polygon for each thread/iteration
                SHAPE_LINE_CHAIN poly = createSquare( 100 + t * 10 + i, VECTOR2I( t * 100, i * 100 ) );

                bool success = triangulator->TesselatePolygon( poly, nullptr );
                if( !success || !validateTriangulation( fixture.GetResult(), poly, false ) )
                    return false;
            }
            return true;
        }));
    }

    // Wait for all threads and check results
    for( auto& future : futures )
    {
        BOOST_TEST( future.get() );
    }
}

// Edge case and robustness tests
BOOST_AUTO_TEST_CASE( SelfIntersectingPolygon )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Create a bowtie (self-intersecting polygon)
    SHAPE_LINE_CHAIN bowtie;
    bowtie.Append( 0, 0 );
    bowtie.Append( 100, 100 );
    bowtie.Append( 100, 0 );
    bowtie.Append( 0, 100 );
    bowtie.SetClosed( true );

    bool success = triangulator->TesselatePolygon( bowtie, nullptr );

    // Algorithm should handle self-intersecting polygons
    BOOST_TEST( success );
    if( success )
    {
        BOOST_TEST( validateTriangulation( fixture.GetResult(), bowtie, false ) );
    }
}


/**
 * Test case for GitLab issue #18083: Self-intersecting filled shape is not completely filled.
 *
 * A self-touching polygon where one vertex lies on a non-adjacent edge creates a "pinch point".
 * The polygon appears to form a figure-8 shape that should be fully filled on both sides.
 *
 * The polygon from the issue has points: (165,87), (179,87), (174,94), (169,87), (167,94)
 * where vertex (169,87) lies on the segment from (165,87) to (179,87).
 *
 * This test verifies that the triangulation correctly fills both regions of the self-touching
 * polygon by checking that the total triangulated area equals the sum of both triangular lobes.
 */
BOOST_AUTO_TEST_CASE( Issue18083_SelfIntersectingPolygonArea )
{
    SHAPE_POLY_SET polySet;
    SHAPE_LINE_CHAIN outline;

    // Coordinates from the issue (converted to internal units: 1mm = 1000000)
    const int SCALE = 1000000;
    outline.Append( 165 * SCALE, 87 * SCALE );
    outline.Append( 179 * SCALE, 87 * SCALE );
    outline.Append( 174 * SCALE, 94 * SCALE );
    outline.Append( 169 * SCALE, 87 * SCALE );
    outline.Append( 167 * SCALE, 94 * SCALE );
    outline.SetClosed( true );

    polySet.AddOutline( outline );

    // Verify the polygon is detected as self-intersecting
    BOOST_TEST( polySet.IsSelfIntersecting() );

    // Triangulate via SHAPE_POLY_SET
    polySet.CacheTriangulation( false );
    BOOST_TEST( polySet.IsTriangulationUpToDate() );

    // Calculate the triangulated area
    double triangulatedArea = 0.0;

    for( int ii = 0; ii < polySet.TriangulatedPolyCount(); ii++ )
    {
        const auto triPoly = polySet.TriangulatedPolygon( ii );

        for( const auto& tri : triPoly->Triangles() )
            triangulatedArea += std::abs( tri.Area() );
    }

    // The expected total area is 49 mm² (14 mm² + 35 mm² for the two triangular lobes)
    // Triangle 1: (165,87) - (169,87) - (167,94) = base 4mm, height 7mm = 14 mm²
    // Triangle 2: (169,87) - (179,87) - (174,94) = base 10mm, height 7mm = 35 mm²
    double expectedAreaMmSq = 49.0 * SCALE * SCALE;

    // The triangulated area should match the expected area
    BOOST_TEST( std::abs( triangulatedArea - expectedAreaMmSq ) < expectedAreaMmSq * 0.01,
                "Triangulated area should match expected area of 49 mm²" );
}

BOOST_AUTO_TEST_CASE( NearlyCollinearVertices )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Create a polygon with vertices that are nearly collinear
    SHAPE_LINE_CHAIN nearlyCollinear;
    nearlyCollinear.Append( 0, 0 );
    nearlyCollinear.Append( 1000000, 0 );
    nearlyCollinear.Append( 2000000, 1 ); // Very small deviation
    nearlyCollinear.Append( 3000000, 0 );
    nearlyCollinear.Append( 1500000, 1000000 );
    nearlyCollinear.SetClosed( true );

    bool success = triangulator->TesselatePolygon( nearlyCollinear, nullptr );

    BOOST_TEST( success );
    if( success )
    {
        BOOST_TEST( validateTriangulation( fixture.GetResult(), nearlyCollinear, false ) );
    }
}

BOOST_AUTO_TEST_CASE( DuplicateVertices )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Create a square with duplicate vertices
    SHAPE_LINE_CHAIN duplicate;
    duplicate.Append( 0, 0 );
    duplicate.Append( 0, 0 );  // Duplicate
    duplicate.Append( 100, 0 );
    duplicate.Append( 100, 0 ); // Duplicate
    duplicate.Append( 100, 100 );
    duplicate.Append( 100, 100 ); // Duplicate
    duplicate.Append( 0, 100 );
    duplicate.Append( 0, 100 ); // Duplicate
    duplicate.SetClosed( true );

    bool success = triangulator->TesselatePolygon( duplicate, nullptr );

    BOOST_TEST( success );
    if( success )
    {
        BOOST_TEST( validateTriangulation( fixture.GetResult(), duplicate, false ) );
    }
}

BOOST_AUTO_TEST_CASE( ExtremeCoordinates )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Test with very large coordinates
    SHAPE_LINE_CHAIN extreme;
    int large = 1000000000; // 1 billion
    extreme.Append( 0, 0 );
    extreme.Append( large, 0 );
    extreme.Append( large, large );
    extreme.Append( 0, large );
    extreme.SetClosed( true );

    bool success = triangulator->TesselatePolygon( extreme, nullptr );

    BOOST_TEST( success );
    if( success )
    {
        BOOST_TEST( validateTriangulation( fixture.GetResult(), extreme, false ) );
    }
}

// Error recovery and cleanup tests
BOOST_AUTO_TEST_CASE( ErrorRecoveryAndCleanup )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Try a series of operations, some of which might fail
    std::vector<SHAPE_LINE_CHAIN> testPolygons;

    // Valid polygon
    testPolygons.push_back( createSquare() );

    // Degenerate polygon
    SHAPE_LINE_CHAIN degenerate;
    degenerate.Append( 0, 0 );
    degenerate.SetClosed( true );
    testPolygons.push_back( degenerate );

    // Another valid polygon
    testPolygons.push_back( createTriangle() );

    for( const auto& poly : testPolygons )
    {
        // Each triangulation should start with a clean state
        bool success = triangulator->TesselatePolygon( poly, nullptr );
        // Even if triangulation fails, it should not crash
        success |= fixture.GetResult().GetTriangleCount() > 0;
        BOOST_TEST( success );
    }
}

// Integration tests with TRIANGULATED_POLYGON interface
BOOST_AUTO_TEST_CASE( TriangulatedPolygonInterface )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    SHAPE_LINE_CHAIN square = createSquare();
    bool success = triangulator->TesselatePolygon( square, nullptr );

    BOOST_TEST( success );

    const auto& result = fixture.GetResult();

    // Test GetTriangle method
    if( result.GetTriangleCount() > 0 )
    {
        VECTOR2I a, b, c;
        result.GetTriangle( 0, a, b, c );

        // Vertices should be valid points from the square
        BOOST_TEST( (a.x >= 0 && a.x <= 100 && a.y >= 0 && a.y <= 100) );
        BOOST_TEST( (b.x >= 0 && b.x <= 100 && b.y >= 0 && b.y <= 100) );
        BOOST_TEST( (c.x >= 0 && c.x <= 100 && c.y >= 0 && c.y <= 100) );
    }

    // Test triangle iteration
    for( const auto& tri : result.Triangles() )
    {
        BOOST_TEST( tri.GetPointCount() == 3 );
        BOOST_TEST( tri.GetSegmentCount() == 3 );
        BOOST_TEST( tri.Area() > 0 );
        BOOST_TEST( tri.IsClosed() );
        BOOST_TEST( tri.IsSolid() );
    }
}

BOOST_AUTO_TEST_CASE( SourceOutlineIndexTracking )
{
    TRIANGULATION_TEST_FIXTURE fixture;
    auto triangulator = fixture.CreateTriangulator();

    // Test that source outline index is properly maintained
    const int expectedOutlineIndex = 5;

    // Create triangulated polygon with specific source index
    SHAPE_POLY_SET::TRIANGULATED_POLYGON result( expectedOutlineIndex );
    POLYGON_TRIANGULATION localTriangulator( result );

    SHAPE_LINE_CHAIN triangle = createTriangle();
    bool success = localTriangulator.TesselatePolygon( triangle, nullptr );

    BOOST_TEST( success );
    BOOST_TEST( result.GetSourceOutlineIndex() == expectedOutlineIndex );
}

// Performance regression tests
BOOST_AUTO_TEST_CASE( PerformanceRegression )
{
    // Test various polygon sizes to ensure performance scales reasonably
    std::vector<int> testSizes = { 10, 50, 100, 500, 1000 };
    std::vector<long long> durations;

    for( int size : testSizes )
    {
        TRIANGULATION_TEST_FIXTURE fixture;
        auto triangulator = fixture.CreateTriangulator();

        // Create regular polygon
        SHAPE_LINE_CHAIN poly;
        for( int i = 0; i < size; i++ )
        {
            double angle = 2.0 * M_PI * i / size;
            int x = static_cast<int>( 1000 * cos( angle ) );
            int y = static_cast<int>( 1000 * sin( angle ) );
            poly.Append( x, y );
        }
        poly.SetClosed( true );

        auto start = std::chrono::high_resolution_clock::now();
        bool success = triangulator->TesselatePolygon( poly, nullptr );
        auto end = std::chrono::high_resolution_clock::now();

        BOOST_TEST( success );

        auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end - start );
        durations.push_back( duration.count() );
    }

    // Check that performance scales reasonably (shouldn't be exponential)
    // This is a basic sanity check - actual performance will vary by hardware
    for( size_t i = 1; i < durations.size(); i++ )
    {
        double scaleFactor = static_cast<double>( durations[i] ) / durations[i-1];
        double sizeFactor = static_cast<double>( testSizes[i] ) / testSizes[i-1];

        // Performance shouldn't be worse than O(n^2) in most cases
        BOOST_TEST( scaleFactor < sizeFactor * sizeFactor * 2 );
    }
}

BOOST_AUTO_TEST_SUITE_END()