/**
 * @file move-drag_pads.cpp
 * @brief Edit footprint pads.
 */

#include "fctsys.h"
#include "gr_basic.h"
#include "common.h"
#include "class_drawpanel.h"
#include "confirm.h"
#include "trigo.h"
#include "block_commande.h"
#include "wxBasePcbFrame.h"
#include "macros.h"
#include "pcbcommon.h"

#include "class_board.h"
#include "class_module.h"

#include "pcbnew.h"
#include "drag.h"
#include "protos.h"


static D_PAD*  s_CurrentSelectedPad;
static wxPoint Pad_OldPos;


/* Cancel move pad command.
 */
static void Abort_Move_Pad( EDA_DRAW_PANEL* Panel, wxDC* DC )
{
    D_PAD* pad = s_CurrentSelectedPad;

    Panel->SetMouseCapture( NULL, NULL );

    if( pad == NULL )
        return;

    pad->Draw( Panel, DC, GR_XOR );
    pad->ClearFlags();
    pad->m_Pos   = Pad_OldPos;
    pad->Draw( Panel, DC, GR_XOR );

    /* Pad move in progress: the restore origin. */
    if( g_Drag_Pistes_On )
    {
        for( unsigned ii = 0; ii < g_DragSegmentList.size(); ii++ )
        {
            TRACK* Track = g_DragSegmentList[ii].m_Segm;
            Track->Draw( Panel, DC, GR_XOR );
            Track->SetState( IN_EDIT, OFF );
            g_DragSegmentList[ii].SetInitialValues();
            Track->Draw( Panel, DC, GR_OR );
        }
    }

    EraseDragList();
    s_CurrentSelectedPad = NULL;
    g_Drag_Pistes_On     = false;
}


/* Draw in drag mode when moving a pad.
 */
static void Show_Pad_Move( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosition,
                           bool aErase )
{
    TRACK*       Track;
    BASE_SCREEN* screen = aPanel->GetScreen();
    D_PAD*       pad    = s_CurrentSelectedPad;

    if( pad == NULL )       // Should not occur
        return;

    if( aErase )
        pad->Draw( aPanel, aDC, GR_XOR );

    pad->m_Pos = screen->GetCrossHairPosition();
    pad->Draw( aPanel, aDC, GR_XOR );

    if( !g_Drag_Pistes_On )
        return;

    for( unsigned ii = 0; ii < g_DragSegmentList.size(); ii++ )
    {
        Track = g_DragSegmentList[ii].m_Segm;

        if( aErase )
            Track->Draw( aPanel, aDC, GR_XOR );

        if( g_DragSegmentList[ii].m_Pad_Start )
        {
            Track->m_Start = pad->m_Pos;
        }

        if( g_DragSegmentList[ii].m_Pad_End )
        {
            Track->m_End = pad->m_Pos;
        }

        Track->Draw( aPanel, aDC, GR_XOR );
    }
}


/* Load list of features for default pad selection.
 */
void PCB_BASE_FRAME::Export_Pad_Settings( D_PAD* pt_pad )
{
    if( pt_pad == NULL )
        return;

    pt_pad->DisplayInfo( this );

    g_Pad_Master.m_PadShape     = pt_pad->m_PadShape;
    g_Pad_Master.m_Attribut     = pt_pad->m_Attribut;
    g_Pad_Master.m_layerMask = pt_pad->m_layerMask;
    g_Pad_Master.m_Orient = pt_pad->m_Orient -
                            ( (MODULE*) pt_pad->GetParent() )->m_Orient;
    g_Pad_Master.m_Size = pt_pad->m_Size;
    g_Pad_Master.m_DeltaSize = pt_pad->m_DeltaSize;
    pt_pad->ComputeShapeMaxRadius();

    g_Pad_Master.m_Offset     = pt_pad->m_Offset;
    g_Pad_Master.m_Drill      = pt_pad->m_Drill;
    g_Pad_Master.m_DrillShape = pt_pad->m_DrillShape;
}


/* Imports the new values of dimensions of the pad edge by pt_pad
 * - Source: selected values of general characteristics
 * - Measurements are modified
 * - The position, names, and keys are not.
 */
void PCB_BASE_FRAME::Import_Pad_Settings( D_PAD* aPad, bool aDraw )
{
    if( aDraw )
    {
        aPad->SetFlags( DO_NOT_DRAW );
        m_canvas->RefreshDrawingRect( aPad->GetBoundingBox() );
        aPad->ClearFlags( DO_NOT_DRAW );
    }

    aPad->m_PadShape = g_Pad_Master.m_PadShape;
    aPad->m_layerMask = g_Pad_Master.m_layerMask;
    aPad->m_Attribut = g_Pad_Master.m_Attribut;
    aPad->m_Orient = g_Pad_Master.m_Orient + ( (MODULE*) aPad->GetParent() )->m_Orient;
    aPad->m_Size = g_Pad_Master.m_Size;
    aPad->m_DeltaSize  = wxSize( 0, 0 );
    aPad->m_Offset     = g_Pad_Master.m_Offset;
    aPad->m_Drill      = g_Pad_Master.m_Drill;
    aPad->m_DrillShape = g_Pad_Master.m_DrillShape;

    switch( g_Pad_Master.m_PadShape )
    {
    case PAD_TRAPEZOID:
        aPad->m_DeltaSize = g_Pad_Master.m_DeltaSize;
        break;

    case PAD_CIRCLE:
        aPad->m_Size.y = aPad->m_Size.x;
        break;
    }

    switch( g_Pad_Master.m_Attribut & 0x7F )
    {
    case PAD_SMD:
    case PAD_CONN:
        aPad->m_Drill    = wxSize( 0, 0 );
        aPad->m_Offset.x = 0;
        aPad->m_Offset.y = 0;
    }

    aPad->ComputeShapeMaxRadius();

    if( aDraw )
        m_canvas->RefreshDrawingRect( aPad->GetBoundingBox() );

    ( (MODULE*) aPad->GetParent() )->m_LastEdit_Time = time( NULL );
}


/* Add a pad on the selected module.
 */
void PCB_BASE_FRAME::AddPad( MODULE* Module, bool draw )
{
    wxString lastPadName;   // Last used pad name (pad num)
    lastPadName = g_Pad_Master.GetPadName();

    m_Pcb->m_Status_Pcb     = 0;
    Module->m_LastEdit_Time = time( NULL );

    D_PAD* Pad = new D_PAD( Module );

    /* Add the new pad to end of the module pad list. */
    Module->m_Pads.PushBack( Pad );

    /* Update the pad properties. */
    Import_Pad_Settings( Pad, false );
    Pad->SetNetname( wxEmptyString );

    Pad->m_Pos = GetScreen()->GetCrossHairPosition();

    // Set the relative pad position
    // ( pad position for module orient, 0, and relative to the module position)
    Pad->m_Pos0 = Pad->m_Pos - Module->m_Pos;
    RotatePoint( &Pad->m_Pos0, -Module->m_Orient );

    /* Automatically increment the current pad number. */
    long num    = 0;
    int  ponder = 1;

    while( lastPadName.Len() && lastPadName.Last() >= '0' && lastPadName.Last() <= '9' )
    {
        num += ( lastPadName.Last() - '0' ) * ponder;
        lastPadName.RemoveLast();
        ponder *= 10;
    }

    num++;  // Use next number for the new pad
    lastPadName << num;
    Pad->SetPadName( lastPadName );
    g_Pad_Master.SetPadName(lastPadName);

    Module->CalculateBoundingBox();
    Pad->DisplayInfo( this );

    if( draw )
        m_canvas->RefreshDrawingRect( Module->GetBoundingBox() );
}


/**
 * Function DeletePad
 * Delete the pad aPad.
 * Refresh the modified screen area
 * Refresh modified parameters of the parent module (bounding box, last date)
 * @param aPad = the pad to delete
 * @param aQuery = true to promt for confirmation, false to delete silently
 */
void PCB_BASE_FRAME::DeletePad( D_PAD* aPad, bool aQuery )
{
    MODULE*  Module;

    if( aPad == NULL )
        return;

    Module = (MODULE*) aPad->GetParent();
    Module->m_LastEdit_Time = time( NULL );

    if( aQuery )
    {
        wxString msg;
        msg.Printf( _( "Delete Pad (module %s %s) " ),
                    GetChars( Module->m_Reference->m_Text ),
                    GetChars( Module->m_Value->m_Text ) );

        if( !IsOK( this, msg ) )
            return;
    }

    m_Pcb->m_Status_Pcb = 0;
    aPad->DeleteStructure();
    m_canvas->RefreshDrawingRect( Module->GetBoundingBox() );
    Module->CalculateBoundingBox();

    OnModify();
}


/* Function to initialize the "move pad" command */
void PCB_BASE_FRAME::StartMovePad( D_PAD* Pad, wxDC* DC )
{
    if( Pad == NULL )
        return;

    s_CurrentSelectedPad = Pad;
    Pad_OldPos = Pad->m_Pos;
    Pad->DisplayInfo( this );
    m_canvas->SetMouseCapture( Show_Pad_Move, Abort_Move_Pad );

    /* Draw the pad  (SKETCH mode) */
    Pad->Draw( m_canvas, DC, GR_XOR );
    Pad->SetFlags( IS_MOVED );
    Pad->Draw( m_canvas, DC, GR_XOR );

    /* Build the list of track segments to drag if the command is a drag pad*/
    if( g_Drag_Pistes_On )
        Build_1_Pad_SegmentsToDrag( m_canvas, DC, Pad );
    else
        EraseDragList();
}


/* Routine to place a moved pad. */
void PCB_BASE_FRAME::PlacePad( D_PAD* Pad, wxDC* DC )
{
    int     dX, dY;
    TRACK*  Track;
    MODULE* Module;

    if( Pad == NULL )
        return;

    Module = (MODULE*) Pad->GetParent();

    ITEM_PICKER       picker( NULL, UR_CHANGED );
    PICKED_ITEMS_LIST pickList;

    /* Save dragged track segments in undo list */
    for( unsigned ii = 0; ii < g_DragSegmentList.size(); ii++ )
    {
        Track = g_DragSegmentList[ii].m_Segm;

        // Set the old state
        if( g_DragSegmentList[ii].m_Pad_Start )
            Track->m_Start = Pad_OldPos;

        if( g_DragSegmentList[ii].m_Pad_End )
            Track->m_End = Pad_OldPos;

        picker.m_PickedItem = Track;
        pickList.PushItem( picker );
    }

    /* Save old module and old items values */
    wxPoint pad_curr_position = Pad->m_Pos;

    Pad->m_Pos = Pad_OldPos;

    if( g_DragSegmentList.size() == 0 )
        SaveCopyInUndoList( Module, UR_CHANGED );
    else
    {
        picker.m_PickedItem = Module;
        pickList.PushItem( picker );
        SaveCopyInUndoList( pickList, UR_CHANGED );
    }

    Pad->m_Pos = pad_curr_position;
    Pad->Draw( m_canvas, DC, GR_XOR );

    /* Redraw dragged track segments */
    for( unsigned ii = 0; ii < g_DragSegmentList.size(); ii++ )
    {
        Track = g_DragSegmentList[ii].m_Segm;

        // Set the new state
        if( g_DragSegmentList[ii].m_Pad_Start )
            Track->m_Start = Pad->m_Pos;

        if( g_DragSegmentList[ii].m_Pad_End )
            Track->m_End = Pad->m_Pos;

        Track->SetState( IN_EDIT, OFF );

        if( DC )
            Track->Draw( m_canvas, DC, GR_OR );
    }

    /* Compute local coordinates (i.e refer to Module position and for Module orient = 0) */
    dX = Pad->m_Pos.x - Pad_OldPos.x;
    dY = Pad->m_Pos.y - Pad_OldPos.y;
    RotatePoint( &dX, &dY, -Module->m_Orient );

    Pad->m_Pos0.x += dX;
    s_CurrentSelectedPad->m_Pos0.y += dY;

    Pad->ClearFlags();

    if( DC )
        Pad->Draw( m_canvas, DC, GR_OR );

    Module->CalculateBoundingBox();
    Module->m_LastEdit_Time = time( NULL );

    EraseDragList();

    OnModify();
    m_canvas->SetMouseCapture( NULL, NULL );
    m_Pcb->m_Status_Pcb &= ~( LISTE_RATSNEST_ITEM_OK | CONNEXION_OK );
}


/* Rotate selected pad 90 degrees.
 */
void PCB_BASE_FRAME::RotatePad( D_PAD* Pad, wxDC* DC )
{
    MODULE* Module;

    if( Pad == NULL )
        return;

    Module = (MODULE*) Pad->GetParent();
    Module->m_LastEdit_Time = time( NULL );

    OnModify();

    if( DC )
        Module->Draw( m_canvas, DC, GR_XOR );

    EXCHG( Pad->m_Size.x, Pad->m_Size.y );
    EXCHG( Pad->m_Drill.x, Pad->m_Drill.y );
    EXCHG( Pad->m_Offset.x, Pad->m_Offset.y );
    Pad->m_Offset.y = -Pad->m_Offset.y;

    EXCHG( Pad->m_DeltaSize.x, Pad->m_DeltaSize.y );
    Pad->m_DeltaSize.x = -Pad->m_DeltaSize.x;
    Module->CalculateBoundingBox();
    Pad->DisplayInfo( this );

    if( DC )
        Module->Draw( m_canvas, DC, GR_OR );
}
