/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
 * Copyright (C) 1992-2019 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 <fctsys.h>
#include <macros.h>
#include <gr_basic.h>
#include <bezier_curves.h>
#include <pcb_screen.h>
#include <trigo.h>
#include <msgpanel.h>
#include <bitmaps.h>
#include <pcb_edit_frame.h>
#include <pcbnew.h>
#include <class_board.h>
#include <class_module.h>
#include <class_drawsegment.h>
#include <base_units.h>
#include <math/util.h>      // for KiROUND
#include <pgm_base.h>
#include <settings/color_settings.h>
#include <settings/settings_manager.h>


DRAWSEGMENT::DRAWSEGMENT( BOARD_ITEM* aParent, KICAD_T idtype ) :
    BOARD_ITEM( aParent, idtype )
{
    m_Type  = 0;
    m_Angle = 0;
    m_Flags = 0;
    m_Shape = S_SEGMENT;
    m_Width = Millimeter2iu( DEFAULT_LINE_WIDTH );
}


DRAWSEGMENT::~DRAWSEGMENT()
{
}


void DRAWSEGMENT::SetPosition( const wxPoint& aPos )
{
    m_Start = aPos;
}


const wxPoint DRAWSEGMENT::GetPosition() const
{
    if( m_Shape == S_POLYGON )
        return (wxPoint) m_Poly.CVertex( 0 );
    else
        return m_Start;
}


double DRAWSEGMENT::GetLength() const
{
    double length = 0.0;

    switch( m_Shape )
    {
    case S_CURVE:
        for( size_t ii = 1; ii < m_BezierPoints.size(); ++ii )
            length += GetLineLength( m_BezierPoints[ii - 1], m_BezierPoints[ii] );

        break;

    default:
        length = GetLineLength( GetStart(), GetEnd() );
        break;
    }

    return length;
}


void DRAWSEGMENT::Move( const wxPoint& aMoveVector )
{
    // Move vector should not affect start/end for polygon since it will
    // be applied directly to polygon outline.
    if( m_Shape != S_POLYGON )
    {
        m_Start += aMoveVector;
        m_End += aMoveVector;
    }

    switch ( m_Shape )
    {
    case S_POLYGON:
        m_Poly.Move( VECTOR2I( aMoveVector ) );
        break;

    case S_CURVE:
        m_BezierC1 += aMoveVector;
        m_BezierC2 += aMoveVector;

        for( unsigned int ii = 0; ii < m_BezierPoints.size(); ii++ )
        {
            m_BezierPoints[ii] += aMoveVector;
        }

        break;

    default:
        break;
    }
}


void DRAWSEGMENT::Rotate( const wxPoint& aRotCentre, double aAngle )
{
    switch( m_Shape )
    {
    case S_ARC:
    case S_SEGMENT:
    case S_CIRCLE:
        // these can all be done by just rotating the start and end points
        RotatePoint( &m_Start, aRotCentre, aAngle);
        RotatePoint( &m_End, aRotCentre, aAngle);
        break;

    case S_POLYGON:
        m_Poly.Rotate( -DECIDEG2RAD( aAngle ), VECTOR2I( aRotCentre ) );
        break;

    case S_CURVE:
        RotatePoint( &m_Start, aRotCentre, aAngle);
        RotatePoint( &m_End, aRotCentre, aAngle);
        RotatePoint( &m_BezierC1, aRotCentre, aAngle);
        RotatePoint( &m_BezierC2, aRotCentre, aAngle);

        for( unsigned int ii = 0; ii < m_BezierPoints.size(); ii++ )
        {
            RotatePoint( &m_BezierPoints[ii], aRotCentre, aAngle);
        }
        break;

    case S_RECT:
    default:
        // un-handled edge transform
        wxASSERT_MSG( false, wxT( "DRAWSEGMENT::Rotate not implemented for "
                + ShowShape( m_Shape ) ) );
        break;
    }
}


void DRAWSEGMENT::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
{
    if( aFlipLeftRight )
    {
        m_Start.x = aCentre.x - ( m_Start.x - aCentre.x );
        m_End.x   = aCentre.x - ( m_End.x - aCentre.x );
    }
    else
    {
        m_Start.y = aCentre.y - ( m_Start.y - aCentre.y );
        m_End.y   = aCentre.y - ( m_End.y - aCentre.y );
    }

    switch ( m_Shape )
    {
    case S_ARC:
        m_Angle = -m_Angle;
        break;

    case S_POLYGON:
        m_Poly.Mirror( aFlipLeftRight, !aFlipLeftRight, VECTOR2I( aCentre ) );
        break;

    case S_CURVE:
        {
            if( aFlipLeftRight )
            {
                m_BezierC1.x = aCentre.x - ( m_BezierC1.x - aCentre.x );
                m_BezierC2.x = aCentre.x - ( m_BezierC2.x - aCentre.x );
            }
            else
            {
                m_BezierC1.y = aCentre.y - ( m_BezierC1.y - aCentre.y );
                m_BezierC2.y = aCentre.y - ( m_BezierC2.y - aCentre.y );
            }

            // Rebuild the poly points shape
            std::vector<wxPoint> ctrlPoints = { m_Start, m_BezierC1, m_BezierC2, m_End };
            BEZIER_POLY converter( ctrlPoints );
            converter.GetPoly( m_BezierPoints, m_Width );
        }
        break;

    default:
        break;
    }

    // DRAWSEGMENT items are not allowed on copper layers, so
    // copper layers count is not taken in account in Flip transform
    SetLayer( FlipLayer( GetLayer() ) );
}


void DRAWSEGMENT::RebuildBezierToSegmentsPointsList( int aMinSegLen )
{
    // Has meaning only for S_CURVE DRAW_SEGMENT shape
    if( m_Shape != S_CURVE )
    {
        m_BezierPoints.clear();
        return;
    }
    // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
    std::vector<wxPoint> ctrlPoints = { m_Start, m_BezierC1, m_BezierC2, m_End };
    BEZIER_POLY converter( ctrlPoints );
    converter.GetPoly( m_BezierPoints, aMinSegLen );
}


const wxPoint DRAWSEGMENT::GetCenter() const
{
    wxPoint c;

    switch( m_Shape )
    {
    case S_ARC:
    case S_CIRCLE:
        c = m_Start;
        break;

    case S_SEGMENT:
        // Midpoint of the line
        c = ( GetStart() + GetEnd() ) / 2;
        break;

    case S_POLYGON:
    case S_RECT:
    case S_CURVE:
        c = GetBoundingBox().Centre();
        break;

    default:
        wxASSERT_MSG( false, "DRAWSEGMENT::GetCentre not implemented for shape"
                + ShowShape( GetShape() ) );
        break;
    }

    return c;
}


const wxPoint DRAWSEGMENT::GetArcEnd() const
{
    wxPoint endPoint( m_End );         // start of arc

    switch( m_Shape )
    {
    case S_ARC:
        // rotate the starting point of the arc, given by m_End, through the
        // angle m_Angle to get the ending point of the arc.
        // m_Start is the arc centre
        endPoint  = m_End;         // m_End = start point of arc
        RotatePoint( &endPoint, m_Start, -m_Angle );
        break;

    default:
        break;
    }

    return endPoint;   // after rotation, the end of the arc.
}


const wxPoint DRAWSEGMENT::GetArcMid() const
{
    wxPoint endPoint( m_End );

    switch( m_Shape )
    {
    case S_ARC:
        // rotate the starting point of the arc, given by m_End, through half
        // the angle m_Angle to get the middle of the arc.
        // m_Start is the arc centre
        endPoint  = m_End;         // m_End = start point of arc
        RotatePoint( &endPoint, m_Start, -m_Angle / 2.0 );
        break;

    default:
        break;
    }

    return endPoint;   // after rotation, the end of the arc.
}


double DRAWSEGMENT::GetArcAngleStart() const
{
    // due to the Y axis orient atan2 needs - y value
    double angleStart = ArcTangente( GetArcStart().y - GetCenter().y,
                                     GetArcStart().x - GetCenter().x );

    // Normalize it to 0 ... 360 deg, to avoid discontinuity for angles near 180 deg
    // because 180 deg and -180 are very near angles when ampping betewwen -180 ... 180 deg.
    // and this is not easy to handle in calculations
    NORMALIZE_ANGLE_POS( angleStart );

    return angleStart;
}


void DRAWSEGMENT::SetAngle( double aAngle )
{
    // m_Angle must be >= -360 and <= +360 degrees
    m_Angle = NormalizeAngle360Max( aAngle );
}


MODULE* DRAWSEGMENT::GetParentModule() const
{
    if( !m_Parent || m_Parent->Type() != PCB_MODULE_T )
        return NULL;

    return (MODULE*) m_Parent;
}


void DRAWSEGMENT::Print( PCB_BASE_FRAME* aFrame, wxDC* DC, const wxPoint& aOffset )
{
    int ux0, uy0, dx, dy;
    int l_trace;
    int radius;

    BOARD* brd =  GetBoard( );

    if( brd->IsLayerVisible( GetLayer() ) == false )
        return;

    COLOR4D color = Pgm().GetSettingsManager().GetColorSettings()->GetColor( GetLayer() );
    auto displ_opts = aFrame->GetDisplayOptions();

    l_trace = m_Width >> 1;         // half trace width

    // Line start point or Circle and Arc center
    ux0 = m_Start.x + aOffset.x;
    uy0 = m_Start.y + aOffset.y;

    // Line end point or circle and arc start point
    dx = m_End.x + aOffset.x;
    dy = m_End.y + aOffset.y;

    bool filled = displ_opts.m_DisplayDrawItemsFill;

    if( m_Flags & FORCE_SKETCH )
        filled = SKETCH;

    switch( m_Shape )
    {
    case S_CIRCLE:
        radius = KiROUND( Distance( ux0, uy0, dx, dy ) );

        if( filled )
        {
            GRCircle( nullptr, DC, ux0, uy0, radius, m_Width, color );
        }
        else
        {
            GRCircle( nullptr, DC, ux0, uy0, radius - l_trace, color );
            GRCircle( nullptr, DC, ux0, uy0, radius + l_trace, color );
        }

        break;

    case S_ARC:
        double StAngle, EndAngle;
        radius   = KiROUND( Distance( ux0, uy0, dx, dy ) );
        StAngle  = ArcTangente( dy - uy0, dx - ux0 );
        EndAngle = StAngle + m_Angle;

        if( StAngle > EndAngle )
            std::swap( StAngle, EndAngle );

        if( filled )
        {
            GRArc( nullptr, DC, ux0, uy0, StAngle, EndAngle, radius, m_Width, color );
        }
        else
        {
            GRArc( nullptr, DC, ux0, uy0, StAngle, EndAngle, radius - l_trace, color );
            GRArc( nullptr, DC, ux0, uy0, StAngle, EndAngle, radius + l_trace, color );
        }

        break;

    case S_CURVE:
        {
            RebuildBezierToSegmentsPointsList( m_Width );

            wxPoint& startp = m_BezierPoints[0];

            for( unsigned int i = 1; i < m_BezierPoints.size(); i++ )
            {
                wxPoint& endp = m_BezierPoints[i];

                if( filled )
                    GRFilledSegment( nullptr, DC, startp+aOffset, endp+aOffset, m_Width, color );
                else
                    GRCSegm( nullptr, DC, startp+aOffset, endp+aOffset, m_Width, color );

                startp = m_BezierPoints[i];
            }
        }
        break;

    case S_POLYGON:
        {
            SHAPE_POLY_SET& outline = GetPolyShape();
            // Draw the polygon: only one polygon is expected
            // However we provide a multi polygon shape drawing
            // ( for the future or to show a non expected shape )
            for( int jj = 0; jj < outline.OutlineCount(); ++jj )
            {
                SHAPE_LINE_CHAIN& poly = outline.Outline( jj );
                GRClosedPoly( nullptr, DC, poly.PointCount(), (const wxPoint*) &poly.CPoint( 0 ),
                        IsPolygonFilled(), GetWidth(), color, color );
            }
        }
        break;

    default:
        if( filled )
            GRFillCSegm( nullptr, DC, ux0, uy0, dx, dy, m_Width, color );
        else
            GRCSegm( nullptr, DC, ux0, uy0, dx, dy, m_Width, color );

        break;
    }
}


void DRAWSEGMENT::GetMsgPanelInfo( EDA_UNITS aUnits, std::vector<MSG_PANEL_ITEM>& aList )
{
    wxString msg;

    msg = _( "Drawing" );

    aList.emplace_back( _( "Type" ), msg, DARKCYAN );

    wxString    shape = _( "Shape" );

    switch( m_Shape )
    {
    case S_CIRCLE:
        aList.emplace_back( shape, _( "Circle" ), RED );

        msg = MessageTextFromValue( aUnits, GetLineLength( m_Start, m_End ) );
        aList.emplace_back( _( "Radius" ), msg, RED );
        break;

    case S_ARC:
        aList.emplace_back( shape, _( "Arc" ), RED );
        msg.Printf( wxT( "%.1f" ), m_Angle / 10.0 );
        aList.emplace_back( _( "Angle" ), msg, RED );

        msg = MessageTextFromValue( aUnits, GetLineLength( m_Start, m_End ) );
        aList.emplace_back( _( "Radius" ), msg, RED );
        break;

    case S_CURVE:
        aList.emplace_back( shape, _( "Curve" ), RED );

        msg = MessageTextFromValue( aUnits, GetLength() );
        aList.emplace_back( _( "Length" ), msg, DARKGREEN );
        break;

    case S_POLYGON:
        aList.emplace_back( shape, _( "Polygon" ), RED );

        msg.Printf( "%d", GetPolyShape().Outline(0).PointCount() );
        aList.emplace_back( _( "Points" ), msg, DARKGREEN );
        break;

    default:
    {
        aList.emplace_back( shape, _( "Segment" ), RED );

        msg = MessageTextFromValue( aUnits, GetLineLength( m_Start, m_End ) );
        aList.emplace_back( _( "Length" ), msg, DARKGREEN );

        // angle counter-clockwise from 3'o-clock
        const double deg = RAD2DEG( atan2( (double)( m_Start.y - m_End.y ),
                                           (double)( m_End.x - m_Start.x ) ) );
        msg.Printf( wxT( "%.1f" ), deg );
        aList.emplace_back( _( "Angle" ), msg, DARKGREEN );
    }
    }

    if( m_Shape == S_POLYGON )
    {
        VECTOR2I point0 = GetPolyShape().Outline(0).CPoint(0);
        wxString origin = wxString::Format( "@(%s, %s)",
                                           MessageTextFromValue( aUnits, point0.x ),
                                           MessageTextFromValue( aUnits, point0.y ) );

        aList.emplace_back( _( "Origin" ), origin, DARKGREEN );
    }
    else
    {
        wxString start = wxString::Format( "@(%s, %s)",
                                           MessageTextFromValue( aUnits, GetStart().x ),
                                           MessageTextFromValue( aUnits, GetStart().y ) );
        wxString end   = wxString::Format( "@(%s, %s)",
                                           MessageTextFromValue( aUnits, GetEnd().x ),
                                           MessageTextFromValue( aUnits, GetEnd().y ) );

        aList.emplace_back( start, end, DARKGREEN );
    }

    aList.emplace_back( _( "Layer" ), GetLayerName(), DARKBROWN );

    msg = MessageTextFromValue( aUnits, m_Width, true );
    aList.emplace_back( _( "Width" ), msg, DARKCYAN );
}


const EDA_RECT DRAWSEGMENT::GetBoundingBox() const
{
    EDA_RECT bbox;

    bbox.SetOrigin( m_Start );

    switch( m_Shape )
    {
    case S_SEGMENT:
        bbox.SetEnd( m_End );
        break;

    case S_CIRCLE:
        bbox.Inflate( GetRadius() );
        break;

    case S_ARC:
        computeArcBBox( bbox );
        break;

    case S_POLYGON:
        if( m_Poly.IsEmpty() )
            break;
    {
        wxPoint p_end;
        MODULE* module = GetParentModule();
        bool first = true;

        for( auto iter = m_Poly.CIterate(); iter; iter++ )
        {
            wxPoint pt ( iter->x, iter->y );

            if( module ) // Transform, if we belong to a module
            {
                RotatePoint( &pt, module->GetOrientation() );
                pt += module->GetPosition();
            }


            if( first )
            {
                p_end = pt;
                bbox.SetX( pt.x );
                bbox.SetY( pt.y );
                first = false;
            }
            else
            {

                bbox.SetX( std::min( bbox.GetX(), pt.x ) );
                bbox.SetY( std::min( bbox.GetY(), pt.y ) );

                p_end.x   = std::max( p_end.x, pt.x );
                p_end.y   = std::max( p_end.y, pt.y );
            }
        }

        bbox.SetEnd( p_end );
        break;
	}

    case S_CURVE:

        bbox.Merge( m_BezierC1 );
        bbox.Merge( m_BezierC2 );
        bbox.Merge( m_End );
        break;

    default:
        break;
    }

    bbox.Inflate( ((m_Width+1) / 2) + 1 );
    bbox.Normalize();

    return bbox;
}


bool DRAWSEGMENT::HitTest( const wxPoint& aPosition, int aAccuracy ) const
{
    int maxdist = aAccuracy + ( m_Width / 2 );

    switch( m_Shape )
    {
    case S_CIRCLE:
    case S_ARC:
    {
        wxPoint relPos = aPosition - GetCenter();
        int radius = GetRadius();
        int dist   = KiROUND( EuclideanNorm( relPos ) );

        if( abs( radius - dist ) <= maxdist )
        {
            if( m_Shape == S_CIRCLE )
                return true;

            // For arcs, the test point angle must be >= arc angle start
            // and <= arc angle end
            // However angle values > 360 deg are not easy to handle
            // so we calculate the relative angle between arc start point and teast point
            // this relative arc should be < arc angle if arc angle > 0 (CW arc)
            // and > arc angle if arc angle < 0 (CCW arc)
            double arc_angle_start = GetArcAngleStart();    // Always 0.0 ... 360 deg, in 0.1 deg

            double arc_hittest = ArcTangente( relPos.y, relPos.x );

            // Calculate relative angle between the starting point of the arc, and the test point
            arc_hittest -= arc_angle_start;

            // Normalise arc_hittest between 0 ... 360 deg
            NORMALIZE_ANGLE_POS( arc_hittest );

            // Check angle: inside the arc angle when it is > 0
            // and outside the not drawn arc when it is < 0
            if( GetAngle() >= 0.0 )
            {
                if( arc_hittest <= GetAngle() )
                    return true;
            }
            else
            {
                if( arc_hittest >= (3600.0 + GetAngle()) )
                    return true;
            }
        }
    }
        break;

    case S_CURVE:
        ((DRAWSEGMENT*)this)->RebuildBezierToSegmentsPointsList( m_Width );

        for( unsigned int i= 1; i < m_BezierPoints.size(); i++)
        {
            if( TestSegmentHit( aPosition, m_BezierPoints[i-1], m_BezierPoints[i], maxdist ) )
                return true;
        }
        break;

    case S_SEGMENT:
        if( TestSegmentHit( aPosition, m_Start, m_End, maxdist ) )
            return true;
        break;

    case S_POLYGON:
        {
            if( !IsPolygonFilled() )
            {
                SHAPE_POLY_SET::VERTEX_INDEX i;
                auto poly = m_Poly;  //todo: Fix CollideEdge to be const
                return poly.CollideEdge( VECTOR2I( aPosition ), i,
                                         std::max( maxdist, Millimeter2iu( 0.25 ) ) );
            }
            else
                return m_Poly.Collide( VECTOR2I( aPosition ), maxdist );
        }
        break;

    default:
        wxASSERT_MSG( 0, wxString::Format( "unknown DRAWSEGMENT shape: %d", m_Shape ) );
        break;
    }

    return false;
}


bool DRAWSEGMENT::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
{
    EDA_RECT arect = aRect;
    arect.Normalize();
    arect.Inflate( aAccuracy );

    EDA_RECT arcRect;
    EDA_RECT bb = GetBoundingBox();

    switch( m_Shape )
    {
    case S_CIRCLE:
        // Test if area intersects or contains the circle:
        if( aContained )
            return arect.Contains( bb );
        else
        {
            // If the rectangle does not intersect the bounding box, this is a much quicker test
            if( !aRect.Intersects( bb ) )
            {
                return false;
            }
            else
            {
                return arect.IntersectsCircleEdge( GetCenter(), GetRadius(), GetWidth() );
            }
        }
        break;

    case S_ARC:
        // Test for full containment of this arc in the rect
        if( aContained )
        {
            return arect.Contains( bb );
        }
        // Test if the rect crosses the arc
        else
        {
            arcRect = bb.Common( arect );

            /* All following tests must pass:
             * 1. Rectangle must intersect arc BoundingBox
             * 2. Rectangle must cross the outside of the arc
             */
            return arcRect.Intersects( arect ) &&
                   arcRect.IntersectsCircleEdge( GetCenter(), GetRadius(), GetWidth() );
        }
        break;

    case S_SEGMENT:
        if( aContained )
        {
            return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
        }
        else
        {
            // Account for the width of the line
            arect.Inflate( GetWidth() / 2 );
            return arect.Intersects( GetStart(), GetEnd() );
        }

        break;

    case S_POLYGON:
        if( aContained )
        {
            return arect.Contains( bb );
        }
        else
        {
            // Fast test: if aRect is outside the polygon bounding box,
            // rectangles cannot intersect
            if( !arect.Intersects( bb ) )
                return false;

            // Account for the width of the line
            arect.Inflate( GetWidth() / 2 );
            int count = m_Poly.TotalVertices();

            for( int ii = 0; ii < count; ii++ )
            {
                auto vertex = m_Poly.CVertex( ii );
                auto vertexNext = m_Poly.CVertex( ( ii + 1 ) % count );

                // Test if the point is within aRect
                if( arect.Contains( ( wxPoint ) vertex ) )
                    return true;

                // Test if this edge intersects aRect
                if( arect.Intersects( ( wxPoint ) vertex, ( wxPoint ) vertexNext ) )
                    return true;
            }
        }
        break;

    case S_CURVE:     // not yet handled
        if( aContained )
        {
            return arect.Contains( bb );
        }
        else
        {
            // Fast test: if aRect is outside the polygon bounding box,
            // rectangles cannot intersect
            if( !arect.Intersects( bb ) )
                return false;

            // Account for the width of the line
            arect.Inflate( GetWidth() / 2 );
            unsigned count = m_BezierPoints.size();

            for( unsigned ii = 1; ii < count; ii++ )
            {
                wxPoint vertex = m_BezierPoints[ii-1];
                wxPoint vertexNext = m_BezierPoints[ii];

                // Test if the point is within aRect
                if( arect.Contains( ( wxPoint ) vertex ) )
                    return true;

                // Test if this edge intersects aRect
                if( arect.Intersects( vertex, vertexNext ) )
                    return true;
            }
        }
        break;


    default:
        wxASSERT_MSG( 0, wxString::Format( "unknown DRAWSEGMENT shape: %d", m_Shape ) );
        break;
    }

    return false;
}


wxString DRAWSEGMENT::GetSelectMenuText( EDA_UNITS aUnits ) const
{
    return wxString::Format( _( "Pcb Graphic %s, length %s on %s" ),
                             ShowShape( m_Shape ),
                             MessageTextFromValue( aUnits, GetLength() ),
                             GetLayerName() );
}


BITMAP_DEF DRAWSEGMENT::GetMenuImage() const
{
    return add_dashed_line_xpm;
}


EDA_ITEM* DRAWSEGMENT::Clone() const
{
    return new DRAWSEGMENT( *this );
}


const BOX2I DRAWSEGMENT::ViewBBox() const
{
    // For arcs - do not include the center point in the bounding box,
    // it is redundant for displaying an arc
    if( m_Shape == S_ARC )
    {
        EDA_RECT bbox;
        bbox.SetOrigin( m_End );
        computeArcBBox( bbox );
        return BOX2I( bbox.GetOrigin(), bbox.GetSize() );
    }

    return EDA_ITEM::ViewBBox();
}


void DRAWSEGMENT::computeArcBBox( EDA_RECT& aBBox ) const
{
    // Do not include the center, which is not necessarily
    // inside the BB of a arc with a small angle
    aBBox.SetOrigin( m_End );

    wxPoint end = m_End;
    RotatePoint( &end, m_Start, -m_Angle );
    aBBox.Merge( end );

    // Determine the starting quarter
    // 0 right-bottom
    // 1 left-bottom
    // 2 left-top
    // 3 right-top
    unsigned int quarter = 0;       // assume right-bottom

    if( m_End.x < m_Start.x )
    {
        if( m_End.y <= m_Start.y )
            quarter = 2;
        else // ( m_End.y > m_Start.y )
            quarter = 1;
    }
    else if( m_End.x >= m_Start.x )
    {
        if( m_End.y < m_Start.y )
            quarter = 3;
        else if( m_End.x == m_Start.x )
            quarter = 1;
    }

    int radius = GetRadius();
    int angle = (int) GetArcAngleStart() % 900 + m_Angle;
    bool directionCW = ( m_Angle > 0 );      // Is the direction of arc clockwise?

    // Make the angle positive, so we go clockwise and merge points belonging to the arc
    if( !directionCW )
    {
        angle = 900 - angle;
        quarter = ( quarter + 3 ) % 4;       // -1 modulo arithmetic
    }

    while( angle > 900 )
    {
        switch( quarter )
        {
        case 0:
            aBBox.Merge( wxPoint( m_Start.x, m_Start.y + radius ) );     // down
            break;

        case 1:
            aBBox.Merge( wxPoint( m_Start.x - radius, m_Start.y ) );     // left
            break;

        case 2:
            aBBox.Merge( wxPoint( m_Start.x, m_Start.y - radius ) );     // up
            break;

        case 3:
            aBBox.Merge( wxPoint( m_Start.x + radius, m_Start.y ) );     // right
            break;
        }

        if( directionCW )
            ++quarter;
        else
            quarter += 3;       // -1 modulo arithmetic

        quarter %= 4;
        angle -= 900;
    }
}


void DRAWSEGMENT::SetPolyPoints( const std::vector<wxPoint>& aPoints )
{
    m_Poly.RemoveAllContours();
    m_Poly.NewOutline();

    for ( auto p : aPoints )
    {
        m_Poly.Append( p.x, p.y );
    }
}


const std::vector<wxPoint> DRAWSEGMENT::BuildPolyPointsList() const
{
    std::vector<wxPoint> rv;

    if( m_Poly.OutlineCount() )
    {
        if( m_Poly.COutline( 0 ).PointCount() )
        {
            for ( auto iter = m_Poly.CIterate(); iter; iter++ )
            {
                rv.emplace_back( iter->x, iter->y );
            }
        }
    }

    return rv;
}


bool DRAWSEGMENT::IsPolyShapeValid() const
{
    // return true if the polygonal shape is valid (has more than 2 points)
    if( GetPolyShape().OutlineCount() == 0 )
        return false;

    const SHAPE_LINE_CHAIN& outline = ((SHAPE_POLY_SET&)GetPolyShape()).Outline( 0 );

    return outline.PointCount() > 2;
}


int DRAWSEGMENT::GetPointCount() const
{
    // return the number of corners of the polygonal shape
    // this shape is expected to be only one polygon without hole
    if( GetPolyShape().OutlineCount() )
        return GetPolyShape().VertexCount( 0 );

    return 0;
}


void DRAWSEGMENT::SwapData( BOARD_ITEM* aImage )
{
    DRAWSEGMENT* image = dynamic_cast<DRAWSEGMENT*>( aImage );
    assert( image );

    std::swap( m_Width, image->m_Width );
    std::swap( m_Start, image->m_Start );
    std::swap( m_End, image->m_End );
    std::swap( m_Shape, image->m_Shape );
    std::swap( m_Type, image->m_Type );
    std::swap( m_Angle, image->m_Angle );
    std::swap( m_BezierC1, image->m_BezierC1 );
    std::swap( m_BezierC2, image->m_BezierC2 );
    std::swap( m_BezierPoints, image->m_BezierPoints );
    std::swap( m_Poly, image->m_Poly );
}
