/*************************************
*  file class_gerber_draw_item.cpp
*************************************/

/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 1992-2010 <Jean-Pierre Charras>
 * Copyright (C) 1992-2010 Kicad Developers, see change_log.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 "polygons_defs.h"
#include "gr_basic.h"
#include "common.h"
#include "trigo.h"
#include "class_drawpanel.h"
#include "drawtxt.h"

#include "gerbview.h"
#include "class_board_design_settings.h"
#include "colors_selection.h"
#include "class_gerber_draw_item.h"
#include "class_GERBER.h"


/**********************************************************/
GERBER_DRAW_ITEM::GERBER_DRAW_ITEM( BOARD_ITEM* aParent, GERBER* aGerberparams ) :
    BOARD_ITEM( aParent, TYPE_GERBER_DRAW_ITEM )
/**********************************************************/
{
    m_imageParams = aGerberparams;
    m_Layer         = 0;
    m_Shape         = GBR_SEGMENT;
    m_Flashed       = false;
    m_DCode         = 0;
    m_UnitsMetric   = false;
    m_ImageNegative = false;
    m_LayerNegative = false;
    m_swapAxis      = false;
    m_mirrorA       = false;
    m_mirrorB       = false;
    m_drawScale.x   = m_drawScale.y = 1.0;
    if( m_imageParams )
        SetLayerParameters();
}


// Copy constructor
GERBER_DRAW_ITEM::GERBER_DRAW_ITEM( const GERBER_DRAW_ITEM& aSource ) :
    BOARD_ITEM( aSource )
{
    m_imageParams = aSource.m_imageParams;
    m_Shape = aSource.m_Shape;

    m_Flags     = aSource.m_Flags;
    m_TimeStamp = aSource.m_TimeStamp;

    SetStatus( aSource.ReturnStatus() );
    m_Start         = aSource.m_Start;
    m_End           = aSource.m_End;
    m_Size          = aSource.m_Size;
    m_Layer         = aSource.m_Layer;
    m_Shape         = aSource.m_Shape;
    m_Flashed       = aSource.m_Flashed;
    m_DCode         = aSource.m_DCode;
    m_PolyCorners   = aSource.m_PolyCorners;
    m_UnitsMetric   = aSource.m_UnitsMetric;
    m_ImageNegative = aSource.m_ImageNegative;
    m_LayerNegative = aSource.m_LayerNegative;
    m_swapAxis      = aSource.m_swapAxis;
    m_mirrorA       = aSource.m_mirrorA;
    m_mirrorB       = aSource.m_mirrorB;
    m_layerOffset   = aSource.m_layerOffset;
    m_drawScale.x   = aSource.m_drawScale.x;
    m_drawScale.y   = aSource.m_drawScale.y;
}


GERBER_DRAW_ITEM::~GERBER_DRAW_ITEM()
{
}


GERBER_DRAW_ITEM* GERBER_DRAW_ITEM::Copy() const
{
    return new GERBER_DRAW_ITEM( *this );
}


/**
 * Function GetABPosition
 * returns the image position of aPosition for this object.
 * Image position is the value of aPosition, modified by image parameters:
 * offsets, axis selection, scale, rotation
 * @param aXYPosition = position in Y,X gerber axis
 * @return const wxPoint& - The position in A,B axis.
 * Because draw axis is top to bottom, the final y coordinates is negated
 */
wxPoint GERBER_DRAW_ITEM::GetABPosition( const wxPoint& aXYPosition )
{
    /* Note: RS274Xrevd_e is obscure about the order of transforms:
     * For instance: Rotation must be made after or before mirroring ?
     * Note: if something is changed here, GetYXPosition must reflect changes
     */
    wxPoint abPos = aXYPosition;

    if( m_swapAxis )
        EXCHG( abPos.x, abPos.y );
    abPos  += m_layerOffset + m_imageParams->m_ImageOffset;
    abPos.x = wxRound( abPos.x * m_drawScale.x );
    abPos.y = wxRound( abPos.y * m_drawScale.y );
    if( m_imageParams->m_Rotation )
        RotatePoint( &abPos, -m_imageParams->m_Rotation );
    if( m_mirrorA )
        NEGATE( abPos.x );
    // abPos.y must be negated, because draw axis is top to bottom
    if( !m_mirrorB )
        NEGATE( abPos.y );
    return abPos;
}

/**
 * Function GetXYPosition
 * returns the image position of aPosition for this object.
 * Image position is the value of aPosition, modified by image parameters:
 * offsets, axis selection, scale, rotation
 * @param aABPosition = position in A,B plotter axis
 * @return const wxPoint - The given position in X,Y axis.
 */
wxPoint GERBER_DRAW_ITEM::GetXYPosition(const wxPoint& aABPosition )
{
    // do the inverse tranform made by GetABPosition
   wxPoint xyPos = aABPosition;

    if( m_mirrorA )
        NEGATE( xyPos.x );
    if( !m_mirrorB )
        NEGATE( xyPos.y );
    if( m_imageParams->m_Rotation )
        RotatePoint( &xyPos, m_imageParams->m_Rotation );
    xyPos.x = wxRound( xyPos.x / m_drawScale.x );
    xyPos.y = wxRound( xyPos.y / m_drawScale.y );
    xyPos  -= m_layerOffset + m_imageParams->m_ImageOffset;
    if( m_swapAxis )
        EXCHG( xyPos.x, xyPos.y );
    return xyPos;
}

/** function SetLayerParameters
 * Initialize draw parameters from Image and Layer parameters
 * found in the gerber file:
 *   m_UnitsMetric,
 *   m_MirrorA, m_MirrorB,
 *   m_DrawScale, m_DrawOffset
 */
void GERBER_DRAW_ITEM::SetLayerParameters()
{
    m_UnitsMetric = m_imageParams->m_GerbMetric;
    m_swapAxis    = m_imageParams->m_SwapAxis;              // false if A = X, B = Y;
                                                            // true if A =Y, B = Y
    m_mirrorA     = m_imageParams->m_MirrorA;               // true: mirror / axe A
    m_mirrorB     = m_imageParams->m_MirrorB;               // true: mirror / axe B
    m_drawScale   = m_imageParams->m_LayerScale;            // A and B scaling factor
    m_layerOffset = m_imageParams->m_Offset;                // Offset from OF command
}


wxString GERBER_DRAW_ITEM::ShowGBRShape()
{
    switch( m_Shape )
    {
    case GBR_SEGMENT:
        return _( "Line" );

    case GBR_ARC:
        return _( "Arc" );

    case GBR_CIRCLE:
        return _( "Circle" );

    case GBR_SPOT_OVAL:
        return wxT( "spot_oval" );

    case GBR_SPOT_CIRCLE:
        return wxT( "spot_circle" );

    case GBR_SPOT_RECT:
        return wxT( "spot_rect" );

    case GBR_SPOT_POLY:
        return wxT( "spot_poly" );

    case GBR_POLYGON:
        return wxT( "polygon" );

    case GBR_SPOT_MACRO:
        return wxT( "apt_macro" );  // TODO: add aperture macro name

    default:
        return wxT( "??" );
    }
}


/**
 * Function GetDcodeDescr
 * returns the GetDcodeDescr of this object, or NULL.
 * @return D_CODE* - a pointer to the DCode description (for flashed items).
 */
D_CODE* GERBER_DRAW_ITEM::GetDcodeDescr()
{
    if( (m_DCode < FIRST_DCODE) || (m_DCode > LAST_DCODE) )
        return NULL;
    GERBER* gerber = g_GERBER_List[m_Layer];
    if( gerber == NULL )
        return NULL;

    D_CODE* d_code = gerber->GetDCODE( m_DCode, false );

    return d_code;
}


EDA_Rect GERBER_DRAW_ITEM::GetBoundingBox()
{
    // return a rectangle which is (pos,dim) in nature.  therefore the +1
    EDA_Rect bbox( m_Start, wxSize( 1, 1 ) );

    bbox.Inflate( m_Size.x / 2, m_Size.y / 2 );

    bbox.SetOrigin(GetXYPosition( bbox.GetOrigin() ) );
    bbox.SetEnd(GetXYPosition( bbox.GetEnd() ) );
    return bbox;
}


/**
 * Function Move
 * move this object.
 * @param const wxPoint& aMoveVector - the move vector for this object, in AB plotter axis.
 */
void GERBER_DRAW_ITEM::Move( const wxPoint& aMoveVector )
{
    wxPoint xymove = GetXYPosition( aMoveVector );
    m_Start     += xymove;
    m_End       += xymove;
    m_ArcCentre += xymove;
    for( unsigned ii = 0; ii < m_PolyCorners.size(); ii++ )
        m_PolyCorners[ii] += xymove;
}


/** function Save.
 * currently: no nothing, but must be defined to meet requirements
 * of the basic class
 */
bool GERBER_DRAW_ITEM::Save( FILE* aFile ) const
{
    return true;
}


/*********************************************************************/
void GERBER_DRAW_ITEM::Draw( WinEDA_DrawPanel* aPanel, wxDC* aDC, int aDrawMode,
                             const wxPoint& aOffset )
/*********************************************************************/
{
    static D_CODE dummyD_CODE( 0 );      // used when a D_CODE is not found. default D_CODE to draw a flashed item
    int           color, alt_color;
    bool          isFilled;
    int           radius;
    int           halfPenWidth;
    static bool   show_err;
    BOARD*        brd = GetBoard();
    D_CODE*       d_codeDescr = GetDcodeDescr();

    if( d_codeDescr == NULL )
        d_codeDescr = &dummyD_CODE;

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

    color = brd->GetLayerColor( GetLayer() );

    if( aDrawMode & GR_SURBRILL )
    {
        if( aDrawMode & GR_AND )
            color &= ~HIGHT_LIGHT_FLAG;
        else
            color |= HIGHT_LIGHT_FLAG;
    }
    if( color & HIGHT_LIGHT_FLAG )
        color = ColorRefs[color & MASKCOLOR].m_LightColor;

    alt_color = g_DrawBgColor;

    if( m_Flags & DRAW_ERASED )   // draw in background color ("negative" color)
    {
        EXCHG( color, alt_color );
    }

    GRSetDrawMode( aDC, aDrawMode );

    isFilled = DisplayOpt.DisplayPcbTrackFill ? true : false;

    switch( m_Shape )
    {
    case GBR_POLYGON:
        isFilled = (g_DisplayPolygonsModeSketch == false);
        if( m_Flags & DRAW_ERASED )
            isFilled = true;
        DrawGbrPoly( &aPanel->m_ClipBox, aDC, color, aOffset, isFilled );
        break;

    case GBR_CIRCLE:
        radius = (int) hypot( (double) ( m_End.x - m_Start.x ),
                             (double) ( m_End.y - m_Start.y ) );

        halfPenWidth = m_Size.x >> 1;

        if( !isFilled )
        {
            // draw the border of the pen's path using two circles, each as narrow as possible
            GRCircle( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                      radius - halfPenWidth, 0, color );
            GRCircle( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                      radius + halfPenWidth, 0, color );
        }
        else    // Filled mode
        {
            GRCircle( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                      radius, m_Size.x, color );
        }
        break;

    case GBR_ARC:
        if( !isFilled )
        {
            GRArc1( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                    GetABPosition( m_End ), GetABPosition( m_ArcCentre ),
                    0, color );
        }
        else
        {
            GRArc1( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                    GetABPosition( m_End ), GetABPosition( m_ArcCentre ),
                    m_Size.x, color );
        }
        break;

    case GBR_SPOT_CIRCLE:
    case GBR_SPOT_RECT:
    case GBR_SPOT_OVAL:
    case GBR_SPOT_POLY:
    case GBR_SPOT_MACRO:
        isFilled = DisplayOpt.DisplayPadFill ? true : false;
        d_codeDescr->DrawFlashedShape( this, &aPanel->m_ClipBox, aDC, color, alt_color,
                                       m_Start, isFilled );
        break;

    case GBR_SEGMENT:
        if( !isFilled )
            GRCSegm( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                     GetABPosition( m_End ), m_Size.x, color );
        else
            GRFilledSegment( &aPanel->m_ClipBox, aDC, GetABPosition( m_Start ),
                             GetABPosition( m_End ), m_Size.x, color );
        break;

    default:
        if( !show_err )
        {
            wxMessageBox( wxT( "Trace_Segment() type error" ) );
            show_err = TRUE;
        }
        break;
    }
}


/** function DrawGbrPoly
 * a helper function used id ::Draw to draw the polygon stored ion m_PolyCorners
 * Draw filled polygons
 */
void GERBER_DRAW_ITEM::DrawGbrPoly( EDA_Rect*      aClipBox,
                                    wxDC*          aDC,
                                    int            aColor,
                                    const wxPoint& aOffset,
                                    bool           aFilledShape )
{
    std::vector<wxPoint> points;

    points = m_PolyCorners;
    for( unsigned ii = 0; ii < points.size(); ii++ )
    {
        points[ii] += aOffset;
        points[ii]  = GetABPosition( points[ii] );
    }

    GRClosedPoly( aClipBox, aDC, points.size(), &points[0], aFilledShape, aColor, aColor );
}


/** Function DisplayInfo
 * has knowledge about the frame and how and where to put status information
 * about this object into the frame's message panel.
 * Display info about the track segment only, and does not calculate the full track length
 * @param frame A WinEDA_DrawFrame in which to print status information.
 */
void GERBER_DRAW_ITEM::DisplayInfo( WinEDA_DrawFrame* frame )
{
    wxString msg;

    frame->ClearMsgPanel();
    msg = ShowGBRShape();
    frame->AppendMsgPanel( _( "Type" ), msg, DARKCYAN );

    // Display D_Code value:
    msg.Printf( wxT("%d"), m_DCode);
    frame->AppendMsgPanel( _( "D Code" ), msg, RED );

    // Display Image name
    if(m_imageParams)
    {
        msg = m_imageParams->m_ImageName;
        frame->AppendMsgPanel( _( "Image name" ), msg, BROWN );
    }

    // Display graphic layer number
    msg.Printf( wxT("%d"), GetLayer()+1);
    frame->AppendMsgPanel( _( "Graphic layer" ), msg, BROWN );

    // This next info can be see as debug info, so it can be disabled
#if 1
    // Display offset
    wxPoint tmp = m_layerOffset + m_imageParams->m_ImageOffset;
    msg.Printf( wxT("X=%f Y=%f"), (double)tmp.x/10000, (double)tmp.y/10000);
    frame->AppendMsgPanel( _( "Offset" ), msg, DARKRED );

    // Display rotation
    msg.Printf( wxT("%d"), m_imageParams->m_Rotation/10);
    frame->AppendMsgPanel( _( "Image rotation" ), msg, DARKRED );

    // Display mirroring
    msg.Printf( wxT("X%d Y%d"), m_mirrorA, m_mirrorB);
    frame->AppendMsgPanel( _( "Mirror" ), msg, DARKRED );

    // Display AB axis swap
    msg = m_swapAxis ? wxT("A=Y B=X") : wxT("A=X B=Y");
    frame->AppendMsgPanel( _( "AB axis" ), msg, DARKRED );
#endif
}


/**
 * Function HitTest
 * tests if the given wxPoint is within the bounds of this object.
 * @param aRefPos A wxPoint to test in AB axis
 * @return bool - true if a hit, else false
 */
bool GERBER_DRAW_ITEM::HitTest( const wxPoint& aRefPos )
{
    wxPoint ref_pos = GetXYPosition( aRefPos );

    // TODO: a better analyse of the shape (perhaps create a D_CODE::HitTest for flashed items)
    int     radius = MIN( m_Size.x, m_Size.y ) >> 1;

    // delta is a vector from m_Start to m_End (an origin of m_Start)
    wxPoint delta = m_End - m_Start;

    // dist is a vector from m_Start to ref_pos (an origin of m_Start)
    wxPoint dist = ref_pos - m_Start;

    if( m_Flashed )
    {
        return (double) dist.x * dist.x + (double) dist.y * dist.y <=
               (double) radius * radius;
    }
    else
    {
        if( DistanceTest( radius, delta.x, delta.y, dist.x, dist.y ) )
            return true;
    }

    return false;
}


/**
 * Function HitTest (overlayed)
 * tests if the given EDA_Rect intersect this object.
 * For now, an ending point must be inside this rect.
 * @param refArea : the given EDA_Rect in AB plotter axis
 * @return bool - true if a hit, else false
 */
bool GERBER_DRAW_ITEM::HitTest( EDA_Rect& refArea )
{
    wxPoint pos = GetABPosition( m_Start );
    if( refArea.Inside( pos ) )
        return true;
    pos = GetABPosition( m_End );
    if( refArea.Inside( pos ) )
        return true;
    return false;
}


#if defined(DEBUG)

/**
 * Function Show
 * is used to output the object tree, currently for debugging only.
 * @param nestLevel An aid to prettier tree indenting, and is the level
 *          of nesting of this object within the overall tree.
 * @param os The ostream& to output to.
 */
void GERBER_DRAW_ITEM::Show( int nestLevel, std::ostream& os )
{
    NestedSpace( nestLevel, os ) << '<' << GetClass().Lower().mb_str() <<

    " shape=\"" << m_Shape << '"' <<
    " addr=\"" << std::hex << this << std::dec << '"' <<
    " layer=\"" << m_Layer << '"' <<
    " size=\"" << m_Size << '"' <<
    " flags=\"" << m_Flags << '"' <<
    " status=\"" << GetState( -1 ) << '"' <<
    "<start" << m_Start << "/>" <<
    "<end" << m_End << "/>";

    os << "</" << GetClass().Lower().mb_str() << ">\n";
}


#endif
