/**
 * @file am_primitive.cpp
 */

/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 1992-2017 Jean-Pierre Charras <jp.charras at wanadoo.fr>
 * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 1992-2023 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 <trigo.h>
#include <convert_basic_shapes_to_polygon.h>
#include <math/util.h>      // for KiROUND

#include <gerbview.h>
#include <gerber_file_image.h>


/**
 * Convert a distance given in floating point to our internal units.
 */
extern int scaletoIU( double aCoord, bool isMetric );


/**
 * Translate a point from the aperture macro coordinate system to our
 * deci-mils coordinate system.
 *
 * @return The GerbView coordinate system vector.
 */
static VECTOR2I mapPt( double x, double y, bool isMetric )
{
    VECTOR2I ret( scaletoIU( x, isMetric ), scaletoIU( y, isMetric ) );

    return ret;
}


bool AM_PRIMITIVE::IsAMPrimitiveExposureOn( APERTURE_MACRO* aApertMacro ) const
{
    /*
     * Some but not all primitives use the first parameter as an exposure control.
     * Others are always ON.
     * In a aperture macro shape, a basic primitive with exposure off is a hole in the shape
     * it is NOT a negative shape
    */
    wxASSERT( m_Params.size() );

    switch( m_Primitive_id )
    {
    case AMP_CIRCLE:
    case AMP_LINE2:
    case AMP_LINE20:
    case AMP_LINE_CENTER:
    case AMP_LINE_LOWER_LEFT:
    case AMP_OUTLINE:
    case AMP_POLYGON:
        // All have an exposure parameter and can return a value (0 or 1)
        return m_Params[0].GetValueFromMacro( aApertMacro ) != 0;
        break;

    case AMP_THERMAL:   // Exposure is always on
    case AMP_MOIRE:     // Exposure is always on
    case AMP_UNKNOWN:
    default:
        return 1;       // All have no exposure parameter and are always 0N return true
        break;
    }
}


void AM_PRIMITIVE::ConvertBasicShapeToPolygon( APERTURE_MACRO* aApertMacro,
                                               SHAPE_POLY_SET& aShapeBuffer )
{
    // Draw the primitive shape for flashed items.
    // Create a static buffer to avoid a lot of memory reallocation.
    static std::vector<VECTOR2I> polybuffer;
    polybuffer.clear();

    aApertMacro->EvalLocalParams( *this );

    switch( m_Primitive_id )
    {
    case AMP_CIRCLE:        // Circle, given diameter and position
    {
        /* Generated by an aperture macro declaration like:
         * "1,1,0.3,0.5, 1.0*"
         * type (1), exposure, diameter, pos.x, pos.y, <rotation>
         * <rotation> is a optional parameter: rotation from origin.
         * type is not stored in parameters list, so the first parameter is exposure
         */
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        // shape rotation (if any):
        if( m_Params.size() >= 5 )
        {
            EDA_ANGLE rotation( m_Params[4].GetValueFromMacro( aApertMacro ), DEGREES_T );

            if( !rotation.IsZero() )
            {
                for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
                    RotatePoint( polybuffer[ii], -rotation );
            }
        }

        break;
    }

    case AMP_LINE2:
    case AMP_LINE20:        // Line with rectangle ends. (Width, start and end pos + rotation)
    {
        /* Vector Line, Primitive Code 20.
         * A vector line is a rectangle defined by its line width, start and end points.
         * The line ends are rectangular.
        */
        /* Generated by an aperture macro declaration like:
         * "2,1,0.3,0,0, 0.5, 1.0,-135*"
         * type (2), exposure, width, start.x, start.y, end.x, end.y, rotation
         * type is not stored in parameters list, so the first parameter is exposure
         */
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        // shape rotation:
        EDA_ANGLE rotation( m_Params[6].GetValueFromMacro( aApertMacro ), DEGREES_T );

        if( !rotation.IsZero() )
        {
            for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
                RotatePoint( polybuffer[ii], -rotation );
        }

        break;
    }

    case AMP_LINE_CENTER:
    {
        /* Center Line, Primitive Code 21
         * A center line primitive is a rectangle defined by its width, height, and center point
         */
        /* Generated by an aperture macro declaration like:
         * "21,1,0.3,0.03,0,0,-135*"
         * type (21), exposure, ,width, height, center pos.x, center pos.y, rotation
         * type is not stored in parameters list, so the first parameter is exposure
         */
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        // shape rotation:
        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );

        if( !rotation.IsZero() )
        {
            for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
                RotatePoint( polybuffer[ii], -rotation );
        }

        break;
    }

    case AMP_LINE_LOWER_LEFT:
    {
        /* Generated by an aperture macro declaration like:
         * "22,1,0.3,0.03,0,0,-135*"
         * type (22), exposure, ,width, height, corner pos.x, corner pos.y, rotation
         * type is not stored in parameters list, so the first parameter is exposure
         */
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        // shape rotation:
        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );

        if( !rotation.IsZero() )
        {
            for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
                RotatePoint( polybuffer[ii], -rotation );
        }
        break;
    }

    case AMP_THERMAL:
    {
        /* Generated by an aperture macro declaration like:
         * "7, 0,0,1.0,0.3,0.01,-13*"
         * type (7), center.x , center.y, outside diam, inside diam, crosshair thickness, rotation
         * type is not stored in parameters list, so the first parameter is center.x
         *
         * The thermal primitive is a ring (annulus) interrupted by four gaps. Exposure is always
         * on.
         */
        std::vector<VECTOR2I> subshape_poly;
        VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ),
                         m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );
        ConvertShapeToPolygon( aApertMacro, subshape_poly );

        // shape rotation:
        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );

        // Because a thermal shape has 4 identical sub-shapes, only one is created in subshape_poly.
        // We must draw 4 sub-shapes rotated by 90 deg
        for( int ii = 0; ii < 4; ii++ )
        {
            polybuffer = subshape_poly;
            EDA_ANGLE sub_rotation = rotation + ANGLE_90 * ii;

            for( unsigned jj = 0; jj < polybuffer.size(); jj++ )
                RotatePoint( polybuffer[jj], -sub_rotation );

            // Move to center position given by the tool:
            for( unsigned jj = 0; jj < polybuffer.size(); jj++ )
            {
                polybuffer[jj] += center;
            }

            aShapeBuffer.NewOutline();

            for( unsigned jj = 0; jj < polybuffer.size(); jj++ )
                aShapeBuffer.Append( polybuffer[jj] );

            aShapeBuffer.Append( polybuffer[0] );
        }
    }
    break;

    case AMP_MOIRE:
    {
        /* Moire, Primitive Code 6
         * The moire primitive is a cross hair centered on concentric rings (annuli).
         * Exposure is always on.
         */

        /* Generated by an aperture macro declaration like:
         * "6,0,0,0.125,.01,0.01,3,0.003,0.150,0"
         * type(6), pos.x, pos.y, diam, penwidth, gap, circlecount, crosshair thickness,
         * crosshair len, rotation.  The type is not stored in parameters list, so the first
         * parameter is pos.x.
         */
        int outerDiam    = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        int penThickness = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        int gap = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        int numCircles = KiROUND( m_Params[5].GetValueFromMacro( aApertMacro ) );

        // Adjust the allowed approx error to convert arcs to segments:
        int arc_to_seg_error = gerbIUScale.mmToIU( 0.005 );    // Allow 5 microns

        // Draw circles @ position pos.x, pos.y given by the tool:
        VECTOR2I center( mapPt( m_Params[0].GetValueFromMacro( aApertMacro ),
                         m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );

        // adjust outerDiam by this on each nested circle
        int diamAdjust = ( gap + penThickness ) * 2;

        for( int i = 0; i < numCircles; ++i, outerDiam -= diamAdjust )
        {
            if( outerDiam <= 0 )
                break;

            // Note: outerDiam is the outer diameter of the ring.
            // the ring graphic diameter is (outerDiam - penThickness)
            if( outerDiam <= penThickness )
            {   // No room to draw a ring (no room for the hole):
                // draw a circle instead (with no hole), with the right diameter
                TransformCircleToPolygon( aShapeBuffer, center, outerDiam / 2, arc_to_seg_error,
                                          ERROR_INSIDE );
            }
            else
            {
                TransformRingToPolygon( aShapeBuffer, center, ( outerDiam - penThickness ) / 2,
                                        penThickness, arc_to_seg_error, ERROR_INSIDE );
            }
        }

        // Draw the cross:
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        EDA_ANGLE rotation( m_Params[8].GetValueFromMacro( aApertMacro ), DEGREES_T );

        for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
        {
            // move crossair shape to center and rotate shape:
            RotatePoint( polybuffer[ii], -rotation );
            polybuffer[ii] += center;
        }

        break;
    }

    case AMP_OUTLINE:
    {
        /* Outline, Primitive Code 4
         * An outline primitive is an area enclosed by an n-point polygon defined by its start
         * point and n
         * subsequent points. The outline must be closed, i.e. the last point must be equal to
         * the start point. There must be at least one subsequent point (to close the outline).
         * The outline of the primitive is actually the contour (see 2.6) that consists of linear
         * segments only, so it must conform to all the requirements described for contours.
         * Warning: Make no mistake: n is the number of subsequent points, being the number of
         * vertices of the outline or one less than the number of coordinate pairs.
        */
        /* Generated by an aperture macro declaration like:
         * "4,1,3,0.0,0.0,0.0,0.5,0.5,0.5,0.5,0.0,-25"
         * type(4), exposure, corners count, corner1.x, corner.1y, ..., corner1.x, corner.1y,
         * rotation
         * type is not stored in parameters list, so the first parameter is exposure
         */
        // m_Params[0] is the exposure and m_Params[1] is the corners count after the first corner
        int numCorners = (int) m_Params[1].GetValueFromMacro( aApertMacro );

        // the shape rotation is the last param of list, after corners
        int       last_prm = m_Params.size() - 1;
        EDA_ANGLE rotation( m_Params[last_prm].GetValueFromMacro( aApertMacro ), DEGREES_T );
        VECTOR2I  pos;

        // Read points.
        // Note: numCorners is the polygon corner count, following the first corner
        // * the polygon is always closed,
        // * therefore the last XY coordinate is the same as the first
        int prm_idx = 2;    //  m_Params[2] is the first X coordinate

        for( int i = 0; i <= numCorners; ++i )
        {
            pos.x = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), m_GerbMetric );
            prm_idx++;
            pos.y = scaletoIU( m_Params[prm_idx].GetValueFromMacro( aApertMacro ), m_GerbMetric );
            prm_idx++;
            polybuffer.push_back(pos);

            // Guard: ensure prm_idx < last_prm
            // I saw malformed gerber files with numCorners = number
            // of coordinates instead of number of coordinates following the first point
            if( prm_idx >= last_prm )
                break;
        }

        // rotate polygon and move it to the actual position shape rotation:
        for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
        {
            RotatePoint( polybuffer[ii], -rotation );
        }

        break;
    }

    case AMP_POLYGON:
    {
        /* Polygon, Primitive Code 5
         * A polygon primitive is a regular polygon defined by the number of vertices n, the
         * center point and the diameter of the circumscribed circle
        */
        /* Generated by an aperture macro declaration like:
         * "5,1,0.6,0,0,0.5,25"
         * type(5), exposure, vertices count, pox.x, pos.y, diameter, rotation
         * type is not stored in parameters list, so the first parameter is exposure
         */
        VECTOR2I curPos( mapPt( m_Params[2].GetValueFromMacro( aApertMacro ),
                                m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) );

        // Creates the shape:
        ConvertShapeToPolygon( aApertMacro, polybuffer );

        // rotate polygon
        EDA_ANGLE rotation( m_Params[5].GetValueFromMacro( aApertMacro ), DEGREES_T );

        for( unsigned ii = 0; ii < polybuffer.size(); ii++ )
        {
            RotatePoint( polybuffer[ii], -rotation );
            polybuffer[ii] += curPos;
        }

        break;
    }

    case AMP_COMMENT:
    case AMP_UNKNOWN:
        break;
    }

    if( polybuffer.size() > 1 )     // a valid polygon has more than 1 corner
    {
        aShapeBuffer.NewOutline();

        for( unsigned jj = 0; jj < polybuffer.size(); jj++ )
            aShapeBuffer.Append( polybuffer[jj] );

        // Close the shape:
        aShapeBuffer.Append( polybuffer[0] );
    }
}


void AM_PRIMITIVE::ConvertShapeToPolygon( APERTURE_MACRO* aApertMacro,
                                          std::vector<VECTOR2I>&  aBuffer )
{
    switch( m_Primitive_id )
    {
    case AMP_CIRCLE:
    {
        /* Generated by an aperture macro declaration like:
         * "1,1,0.3,0.5, 1.0*"
         * type (1), exposure, diameter, pos.x, pos.y, <rotation>
         * <rotation> is a optional parameter: rotation from origin.
         * type is not stored in parameters list, so the first parameter is exposure
         */
        int radius = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;

        // A circle primitive can have a 0 size (for instance when used in roundrect macro),
        // so skip it
        if( radius <= 0 )
            break;

        VECTOR2I  center = mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ),
                                  m_GerbMetric );
        VECTOR2I  corner;

        const int seg_per_circle = 64;   // Number of segments to approximate a circle
        EDA_ANGLE delta = ANGLE_360 / seg_per_circle;

        for( EDA_ANGLE angle = ANGLE_0; angle < ANGLE_360; angle += delta )
        {
            corner.x   = radius;
            corner.y   = 0;
            RotatePoint( corner, angle );
            corner += center;
            aBuffer.push_back( corner );
        }

        break;
    }

    case AMP_LINE2:
    case AMP_LINE20:        // Line with rectangle ends. (Width, start and end pos + rotation)
    {
        int      width = scaletoIU( m_Params[1].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        VECTOR2I start =
                mapPt( m_Params[2].GetValueFromMacro( aApertMacro ), m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        VECTOR2I end =
                mapPt( m_Params[4].GetValueFromMacro( aApertMacro ), m_Params[5].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        VECTOR2I delta = end - start;
        int     len   = KiROUND( EuclideanNorm( delta ) );

        // To build the polygon, we must create a horizontal polygon starting to "start"
        // and rotate it to have the end point to "end"
        VECTOR2I currpt;
        currpt.y += width / 2;          // Upper left
        aBuffer.push_back( currpt );
        currpt.x = len;                 // Upper right
        aBuffer.push_back( currpt );
        currpt.y -= width;              // lower right
        aBuffer.push_back( currpt );
        currpt.x = 0;                   // lower left
        aBuffer.push_back( currpt );

        // Rotate rectangle and move it to the actual start point
        EDA_ANGLE angle( delta );

        for( unsigned ii = 0; ii < 4; ii++ )
        {
            RotatePoint( aBuffer[ii], -angle );
            aBuffer[ii] += start;
        }

        break;
    }

    case AMP_LINE_CENTER:
    {
        VECTOR2I size =
                mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        VECTOR2I pos =
                mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );

        // Build poly:
        pos.x -= size.x / 2;
        pos.y -= size.y / 2;        // Lower left
        aBuffer.push_back( pos );
        pos.y += size.y;            // Upper left
        aBuffer.push_back( pos );
        pos.x += size.x;            // Upper right
        aBuffer.push_back( pos );
        pos.y -= size.y;            // lower right
        aBuffer.push_back( pos );
        break;
    }

    case AMP_LINE_LOWER_LEFT:
    {
        VECTOR2I size =
                mapPt( m_Params[1].GetValueFromMacro( aApertMacro ), m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        VECTOR2I lowerLeft =
                mapPt( m_Params[3].GetValueFromMacro( aApertMacro ), m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric );

        // Build poly:
        aBuffer.push_back( lowerLeft );
        lowerLeft.y += size.y;          // Upper left
        aBuffer.push_back( lowerLeft );
        lowerLeft.x += size.x;          // Upper right
        aBuffer.push_back( lowerLeft );
        lowerLeft.y -= size.y;          // lower right
        aBuffer.push_back( lowerLeft );
        break;
    }

    case AMP_THERMAL:
    {
        // Only 1/4 of the full shape is built, because the other 3 shapes will be draw from
        // this first rotated by 90, 180 and 270 deg.
        // m_Params = center.x (unused here), center.y (unused here), outside diam, inside diam,
        // crosshair thickness.
        int outerRadius   = scaletoIU( m_Params[2].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
        int innerRadius   = scaletoIU( m_Params[3].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;

        // Safety checks to guarantee no divide-by-zero
        outerRadius = std::max( 1, outerRadius );
        innerRadius = std::max( 1, innerRadius );

        int       halfthickness  = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;
        EDA_ANGLE angle_start( asin( (double) halfthickness / innerRadius ), RADIANS_T );

        // Draw shape in the first quadrant (X and Y > 0)
        VECTOR2I  pos, startpos;

        // Inner arc
        startpos.x = innerRadius;
        EDA_ANGLE angle_end = ANGLE_90 - angle_start;

        for( EDA_ANGLE angle = angle_start; angle < angle_end; angle += EDA_ANGLE( 10, DEGREES_T ) )
        {
            pos = startpos;
            RotatePoint( pos, angle );
            aBuffer.push_back( pos );
        }

        // Last point
        pos = startpos;
        RotatePoint( pos, angle_end );
        aBuffer.push_back( pos );

        // outer arc
        startpos.x  = outerRadius;
        startpos.y  = 0;
        angle_start = EDA_ANGLE( asin( (double) halfthickness / outerRadius ), RADIANS_T );
        angle_end   = ANGLE_90 - angle_start;

        // First point, near Y axis, outer arc
        for( EDA_ANGLE angle = angle_end; angle > angle_start; angle -= EDA_ANGLE( 10, DEGREES_T ) )
        {
            pos = startpos;
            RotatePoint( pos, angle );
            aBuffer.push_back( pos );
        }

        // last point
        pos = startpos;
        RotatePoint( pos, angle_start );
        aBuffer.push_back( pos );

        aBuffer.push_back( aBuffer[0] );  // Close poly
    }
    break;

    case AMP_MOIRE:
    {
        // A cross hair with n concentric circles. Only the cross is built as
        // polygon because circles can be drawn easily
        int crossHairThickness = scaletoIU( m_Params[6].GetValueFromMacro( aApertMacro ), m_GerbMetric );
        int crossHairLength    = scaletoIU( m_Params[7].GetValueFromMacro( aApertMacro ), m_GerbMetric );

        // Create cross. First create 1/4 of the shape.
        // Others point are the same, rotated by 90, 180 and 270 deg
        VECTOR2I pos( crossHairThickness / 2, crossHairLength / 2 );
        aBuffer.push_back( pos );
        pos.y = crossHairThickness / 2;
        aBuffer.push_back( pos );
        pos.x = -crossHairLength / 2;
        aBuffer.push_back( pos );
        pos.y = -crossHairThickness / 2;
        aBuffer.push_back( pos );

        // Copy the 4 shape, rotated by 90, 180 and 270 deg
        for( int jj = 1; jj <= 3; jj ++ )
        {
            for( int ii = 0; ii < 4; ii++ )
            {
                pos = aBuffer[ii];
                RotatePoint( pos, ANGLE_90 * jj );
                aBuffer.push_back( pos );
            }
        }

        break;
    }

    case AMP_OUTLINE:
        // already is a polygon. Do nothing
        break;

    case AMP_POLYGON:   // Creates a regular polygon
    {
        int vertexcount = KiROUND( m_Params[1].GetValueFromMacro( aApertMacro ) );
        int radius    = scaletoIU( m_Params[4].GetValueFromMacro( aApertMacro ), m_GerbMetric ) / 2;

        // rs274x said: vertex count = 3 ... 10, and the first corner is on the X axis
        if( vertexcount < 3 )
            vertexcount = 3;

        if( vertexcount > 10 )
            vertexcount = 10;

        for( int ii = 0; ii <= vertexcount; ii++ )
        {
            VECTOR2I pos( radius, 0 );
            RotatePoint( pos, ANGLE_360 * ii / vertexcount );
            aBuffer.push_back( pos );
        }

        break;
    }

    case AMP_COMMENT:
    case AMP_UNKNOWN:
        break;
    }
}
