/*******************************************/
/* Routines to automatically place MODULES */
/*******************************************/

#include "fctsys.h"
#include "common.h"
#include "class_drawpanel.h"
#include "confirm.h"
#include "pcbnew.h"
#include "wxPcbStruct.h"
#include "autorout.h"
#include "cell.h"
#include "class_board_design_settings.h"
#include "colors_selection.h"

#include "protos.h"


#define GAIN     16
#define PENALITE 500

/* Penalty for guidance given by CntRot90 and CntRot180:
 * graduated from 0 (rotation allowed) to 10 (rotation count null)
 * the count is increased.
 */
static const float OrientPenality[11] =
{
    2.0f,       /* CntRot = 0 rotation prohibited */
    1.9f,       /* CntRot = 1 */
    1.8f,       /* CntRot = 2 */
    1.7f,       /* CntRot = 3 */
    1.6f,       /* CntRot = 4 */
    1.5f,       /* CntRot = 5 */
    1.4f,       /* CntRot = 5 */
    1.3f,       /* CntRot = 7 */
    1.2f,       /* CntRot = 8 */
    1.1f,       /* CntRot = 9 */
    1.0f        /* CntRot = 10 rotation authorized, no penalty */
};

/* Cell states. */
#define OUT_OF_BOARD      -2
#define OCCUPED_By_MODULE -1

static wxPoint CurrPosition; // Current position of the current module
                             // placement
static bool    AutoPlaceShowAll = true;

float          MinCout;

static int  TstModuleOnBoard( BOARD* Pcb, MODULE* Module, bool TstOtherSide );

static void TracePenaliteRectangle( BOARD* Pcb,
                                    int    ux0,
                                    int    uy0,
                                    int    ux1,
                                    int    uy1,
                                    int    marge,
                                    int    Penalite,
                                    int    masque_layer );
static MODULE* PickModule( PCB_EDIT_FRAME* pcbframe, wxDC* DC );


/* Routine to automatically place components in the contour of the PCB
 * The components with the FIXED status are not moved.  If the menu is
 * calling the placement of 1 module, it will be replaced.
 */
void PCB_EDIT_FRAME::AutoPlaceModule( MODULE* Module, int place_mode, wxDC* DC )
{
    int      ii, activ;
    MODULE*  ThisModule = NULL;
    wxPoint  PosOK;
    wxPoint  memopos;
    int      error;
    int      NbModules = 0;
    int      NbTotalModules = 0;
    float    Pas;
    int      lay_tmp_TOP, lay_tmp_BOTTOM;

    if( GetBoard()->m_Modules == NULL )
        return;

    DrawPanel->m_AbortRequest = false;
    DrawPanel->m_AbortEnable  = true;

    switch( place_mode )
    {
    case PLACE_1_MODULE:
        ThisModule = Module;
        if( ThisModule == NULL )
            return;
        ThisModule->m_ModuleStatus &= ~(MODULE_is_PLACED | MODULE_to_PLACE);
        break;

    case PLACE_OUT_OF_BOARD:
        break;

    case PLACE_ALL:
        if( !IsOK( this, _( "Footprints NOT LOCKED will be moved" ) ) )
            return;
        break;

    case PLACE_INCREMENTAL:
        if( !IsOK( this, _( "Footprints NOT PLACED will be moved" ) ) )
            return;
        break;
    }

    memopos = CurrPosition;
    lay_tmp_BOTTOM = Route_Layer_BOTTOM;
    lay_tmp_TOP    = Route_Layer_TOP;

    Board.m_GridRouting = (int) GetScreen()->GetGridSize().x;

    // Ensure Board.m_GridRouting has a reasonnable value:
    if( Board.m_GridRouting < 10 )
        Board.m_GridRouting = 10;                      // Min value = 1/1000 inch

    /* Compute module parmeters used in auto place */
    Module = GetBoard()->m_Modules;
    NbTotalModules = 0;
    for( ; Module != NULL; Module = Module->Next() )
    {
        Module->Set_Rectangle_Encadrement();
        Module->SetRectangleExinscrit();
        NbTotalModules ++;
    }

    if( GenPlaceBoard() == 0 )
        return;

    Module = GetBoard()->m_Modules;
    for( ; Module != NULL; Module = Module->Next() )
    {
        Module->m_ModuleStatus &= ~MODULE_to_PLACE;

        switch( place_mode )
        {
        case PLACE_1_MODULE:
            if( ThisModule == Module )
                Module->m_ModuleStatus |= MODULE_to_PLACE;
            break;

        case PLACE_OUT_OF_BOARD:
            Module->m_ModuleStatus &= ~MODULE_is_PLACED;
            if( Module->m_ModuleStatus & MODULE_is_LOCKED )
                break;
            if( !GetBoard()->m_BoundaryBox.Contains( Module->m_Pos ) )
                Module->m_ModuleStatus |= MODULE_to_PLACE;
            break;

        case PLACE_ALL:
            Module->m_ModuleStatus &= ~MODULE_is_PLACED;
            if( Module->m_ModuleStatus & MODULE_is_LOCKED )
                break;
            Module->m_ModuleStatus |= MODULE_to_PLACE;
            break;

        case PLACE_INCREMENTAL:
            if( Module->m_ModuleStatus & MODULE_is_LOCKED )
            {
                Module->m_ModuleStatus &= ~MODULE_is_PLACED; break;
            }
            if( !(Module->m_ModuleStatus & MODULE_is_PLACED) )
                Module->m_ModuleStatus |= MODULE_to_PLACE;
            break;
        }


        if( Module->m_ModuleStatus & MODULE_to_PLACE )  // Erase from screen
        {
            NbModules++;
            Module->Draw( DrawPanel, DC, GR_XOR );
        }
        else
        {
            GenModuleOnBoard( Module );
        }
    }

    activ = 0;
    Pas   = 100.0;

    if( NbModules )
        Pas = 100.0 / (float) NbModules;
    while( ( Module = PickModule( this, DC ) ) != NULL )
    {
        float BestScore;
        DisplayActivity( (int) (activ * Pas), wxEmptyString ); activ++;

        /* Display fill area of interest, barriers, penalties. */
        DrawInfoPlace( DC );

        error     = RecherchePlacementModule( Module, DC );
        BestScore = MinCout;
        PosOK     = CurrPosition;
        if( error == ESC )
            goto end_of_tst;

        /* Determine if the best orientation of a module is 180. */
        ii = Module->m_CntRot180 & 0x0F;

        if( ii != 0 )
        {
            int Angle_Rot_Module = 1800;
            Rotate_Module( DC, Module, Angle_Rot_Module, false );
            Module->SetRectangleExinscrit();
            error    = RecherchePlacementModule( Module, DC );
            MinCout *= OrientPenality[ii];
            if( BestScore > MinCout )   /* This orientation is best. */
            {
                PosOK     = CurrPosition;
                BestScore = MinCout;
            }
            else
            {
                Angle_Rot_Module = -1800;
                Rotate_Module( DC, Module, Angle_Rot_Module, false );
            }
            if( error == ESC )
                goto end_of_tst;
        }

        /* Determine if the best orientation of a module is 90. */
        ii = Module->m_CntRot90 & 0x0F;
        if( ii != 0 )
        {
            int Angle_Rot_Module = 900;
            Rotate_Module( DC, Module, Angle_Rot_Module, false );
            error    = RecherchePlacementModule( Module, DC );
            MinCout *= OrientPenality[ii];
            if( BestScore > MinCout )   /* This orientation is best. */
            {
                PosOK     = CurrPosition;
                BestScore = MinCout;
            }
            else
            {
                Angle_Rot_Module = -900;
                Rotate_Module( DC, Module, Angle_Rot_Module, false );
            }
            if( error == ESC )
                goto end_of_tst;
        }

        /*  Determine if the best orientation of a module is 270. */
        ii = (Module->m_CntRot90 >> 4 ) & 0x0F;
        if( ii != 0 )
        {
            int Angle_Rot_Module = 2700;
            Rotate_Module( DC, Module, Angle_Rot_Module, false );
            error    = RecherchePlacementModule( Module, DC );
            MinCout *= OrientPenality[ii];
            if( BestScore > MinCout )   /* This orientation is best. */
            {
                PosOK     = CurrPosition;
                BestScore = MinCout;
            }
            else
            {
                Angle_Rot_Module = -2700;
                Rotate_Module( DC, Module, Angle_Rot_Module, false );
            }
            if( error == ESC )
                goto end_of_tst;
        }

end_of_tst:

        if( error == ESC )
            break;

        /* Place module. */
        CurrPosition = GetScreen()->GetCrossHairPosition();
        GetScreen()->SetCrossHairPosition( PosOK );
        Place_Module( Module, DC );
        GetScreen()->SetCrossHairPosition( CurrPosition );

        Module->Set_Rectangle_Encadrement();
        Module->SetRectangleExinscrit();

        GenModuleOnBoard( Module );
        Module->m_ModuleStatus |= MODULE_is_PLACED;
        Module->m_ModuleStatus &= ~MODULE_to_PLACE;
    }

    CurrPosition = memopos;

    Board.UnInitBoard();

    Route_Layer_TOP    = lay_tmp_TOP;
    Route_Layer_BOTTOM = lay_tmp_BOTTOM;

    Module = GetBoard()->m_Modules;
    for( ; Module != NULL; Module = Module->Next() )
    {
        Module->Set_Rectangle_Encadrement();
    }

    GetBoard()->m_Status_Pcb = 0;
    Compile_Ratsnest( DC, true );
    DrawPanel->ReDraw( DC, true );

    DrawPanel->m_AbortEnable = false;
}


void PCB_EDIT_FRAME::DrawInfoPlace( wxDC* DC )
{
    int       color, ii, jj;
    int       ox, oy;
    MATRIX_CELL top_state, bottom_state;

    GRSetDrawMode( DC, GR_COPY );
    for( ii = 0; ii < Board.m_Nrows; ii++ )
    {
        oy = GetBoard()->m_BoundaryBox.m_Pos.y + ( ii * Board.m_GridRouting );

        for( jj = 0; jj < Board.m_Ncols; jj++ )
        {
            ox = GetBoard()->m_BoundaryBox.m_Pos.x +
                 (jj * Board.m_GridRouting);
            color = BLACK;

            top_state    = GetCell( ii, jj, TOP );
            bottom_state = GetCell( ii, jj, BOTTOM );

            if( top_state & CELL_is_ZONE )
                color = BLUE;

            /* obstacles */
            if( ( top_state & CELL_is_EDGE ) || ( bottom_state & CELL_is_EDGE ) )
                color = WHITE;

            else if( top_state & ( HOLE | CELL_is_MODULE ) )
                color = LIGHTRED;
            else if( bottom_state & (HOLE | CELL_is_MODULE) )
                color = LIGHTGREEN;

            else /* Display the filling and keep out regions. */
            {
                if( GetDist( ii, jj, TOP ) || GetDist( ii, jj, BOTTOM ) )
                    color = DARKGRAY;
            }

            GRPutPixel( &DrawPanel->m_ClipBox, DC, ox, oy, color );
        }
    }
}


/* Generate board (component side copper + rating):
 * Allocate the memory needed to represent in "bitmap" on the grid
 * Current:
 * - The size of clearance area of component (the board)
 * - The bitmap PENALTIES
 * And initialize the cells of the board has
 * - Hole in the cells occupied by a segment EDGE
 * - CELL_is_ZONE for cell internal contour EDGE (if closed)
 *
 * Placement surface (board) gives the cells internal to the contour
 * PCB, and among the latter the free cells and cells already occupied
 *
 * The bitmap PENALTIES give cells occupied by the modules,
 * Plus a surface penalty related to the number of pads of the module
 *
 * Bitmap of the penalty is set to 0
 * Occupation cell is a 0 leaves
 */
int PCB_EDIT_FRAME::GenPlaceBoard()
{
    int       jj, ii;
    int       NbCells;
    EDA_ITEM* PtStruct;
    wxString  msg;

    Board.UnInitBoard();

    if( !GetBoard()->ComputeBoundingBox( true ) )
    {
        DisplayError( this, _( "No PCB edge found, unknown board size!" ) );
        return 0;
    }

    /* The boundary box must have its start point on placing grid: */
    GetBoard()->m_BoundaryBox.m_Pos.x -= GetBoard()->m_BoundaryBox.m_Pos.x %
                                         Board.m_GridRouting;
    GetBoard()->m_BoundaryBox.m_Pos.y -= GetBoard()->m_BoundaryBox.m_Pos.y %
                                         Board.m_GridRouting;
    /* The boundary box must have its end point on placing grid: */
    wxPoint end = GetBoard()->m_BoundaryBox.GetEnd();
    end.x -= end.x % Board.m_GridRouting;
    end.x += Board.m_GridRouting;
    end.y -= end.y % Board.m_GridRouting;
    end.y += Board.m_GridRouting;
    GetBoard()->m_BoundaryBox.SetEnd( end );

    Nrows = GetBoard()->m_BoundaryBox.GetHeight() / Board.m_GridRouting;
    Ncols = GetBoard()->m_BoundaryBox.GetWidth() / Board.m_GridRouting;
    /* get a small margin for memory allocation: */
    Ncols  += 2; Nrows += 2;
    NbCells = Ncols * Nrows;

    MsgPanel->EraseMsgBox();
    msg.Printf( wxT( "%d" ), Ncols );
    MsgPanel->SetMessage( 1, _( "Cols" ), msg, GREEN );
    msg.Printf( wxT( "%d" ), Nrows );
    MsgPanel->SetMessage( 7, _( "Lines" ), msg, GREEN );
    msg.Printf( wxT( "%d" ), NbCells );
    MsgPanel->SetMessage( 14, _( "Cells." ), msg, YELLOW );

    /* Choose the number of board sides. */
    Nb_Sides = TWO_SIDES;

    MsgPanel->SetMessage( 22, wxT( "S" ), ( Nb_Sides == TWO_SIDES ) ? wxT( "2" ) : wxT( "1" ),
                          WHITE );

    Board.InitBoard();

    /* Display memory usage. */
    msg.Printf( wxT( "%d" ), Board.m_MemSize / 1024 );
    MsgPanel->SetMessage( 24, wxT( "Mem(Kb)" ), msg, CYAN );

    Route_Layer_BOTTOM = LAYER_N_FRONT;
    if( Nb_Sides == TWO_SIDES )
        Route_Layer_BOTTOM = LAYER_N_BACK;
    Route_Layer_TOP = LAYER_N_FRONT;

    /* Place the edge layer segments */
    PtStruct = GetBoard()->m_Drawings;
    TRACK TmpSegm( NULL );

    TmpSegm.SetLayer( -1 );
    TmpSegm.SetNet( -1 );
    TmpSegm.m_Width = Board.m_GridRouting / 2;
    for( ; PtStruct != NULL; PtStruct = PtStruct->Next() )
    {
        DRAWSEGMENT* DrawSegm;

        switch( PtStruct->Type() )
        {
        case TYPE_DRAWSEGMENT:
            DrawSegm = (DRAWSEGMENT*) PtStruct;
            if( DrawSegm->GetLayer() != EDGE_N )
                break;

            TmpSegm.m_Start = DrawSegm->m_Start;
            TmpSegm.m_End   = DrawSegm->m_End;
            TmpSegm.m_Shape = DrawSegm->m_Shape;
            TmpSegm.m_Param = DrawSegm->m_Angle;

            TraceSegmentPcb( GetBoard(), &TmpSegm, HOLE | CELL_is_EDGE,
                             Board.m_GridRouting, WRITE_CELL );
            break;

        case TYPE_TEXTE:
        default:
            break;
        }
    }

    /* Init the point of attachment to the area. */
    OrCell( Nrows / 2, Ncols / 2, BOTTOM, CELL_is_ZONE );

    /* Fill bottom layer zones. */
    ii = 1;
    jj = 1;

    while( ii )
    {
        msg.Printf( wxT( "%d" ), jj++ );
        MsgPanel->SetMessage(  50, _( "Loop" ), msg, CYAN );
        ii = Propagation( this );
    }

    /* Initialize top layer. */
    if( Board.m_BoardSide[TOP] )
        memcpy( Board.m_BoardSide[TOP], Board.m_BoardSide[BOTTOM],
                NbCells * sizeof(MATRIX_CELL) );

    return 1;
}


/* Place module on board.
 */
void PCB_EDIT_FRAME::GenModuleOnBoard( MODULE* Module )
{
    int    ox, oy, fx, fy, Penalite;
    int    marge = Board.m_GridRouting / 2;
    int    masque_layer;
    D_PAD* Pad;

    ox = Module->m_RealBoundaryBox.m_Pos.x - marge;
    fx = Module->m_RealBoundaryBox.GetRight() + marge;
    oy = Module->m_RealBoundaryBox.m_Pos.y - marge;
    fy = Module->m_RealBoundaryBox.GetBottom() + marge;

    if( ox < GetBoard()->m_BoundaryBox.m_Pos.x )
        ox = GetBoard()->m_BoundaryBox.m_Pos.x;
    if( ox > GetBoard()->m_BoundaryBox.GetRight() )
        ox = GetBoard()->m_BoundaryBox.GetRight();

    if( fx < GetBoard()->m_BoundaryBox.m_Pos.x )
        fx = GetBoard()->m_BoundaryBox.m_Pos.x;
    if( fx > GetBoard()->m_BoundaryBox.GetRight() )
        fx = GetBoard()->m_BoundaryBox.GetRight();

    if( oy < GetBoard()->m_BoundaryBox.m_Pos.y )
        oy = GetBoard()->m_BoundaryBox.m_Pos.y;
    if( oy > GetBoard()->m_BoundaryBox.GetBottom() )
        oy = GetBoard()->m_BoundaryBox.GetBottom();

    if( fy < GetBoard()->m_BoundaryBox.m_Pos.y )
        fy = GetBoard()->m_BoundaryBox.m_Pos.y;
    if( fy > GetBoard()->m_BoundaryBox.GetBottom() )
        fy = GetBoard()->m_BoundaryBox.GetBottom();

    masque_layer = 0;
    if( Module->GetLayer() == LAYER_N_FRONT )
        masque_layer = LAYER_FRONT;
    if( Module->GetLayer() == LAYER_N_BACK )
        masque_layer = LAYER_BACK;

    TraceFilledRectangle( GetBoard(), ox, oy, fx, fy, masque_layer,
                          CELL_is_MODULE, WRITE_OR_CELL );

    int trackWidth = GetBoard()->m_NetClasses.GetDefault()->GetTrackWidth();
    int clearance  = GetBoard()->m_NetClasses.GetDefault()->GetClearance();

    /* Trace pads and surface safely. */
    marge = trackWidth + clearance;

    for( Pad = Module->m_Pads; Pad != NULL; Pad = Pad->Next() )
    {
        Place_1_Pad_Board( GetBoard(), Pad, CELL_is_MODULE, marge,
                           WRITE_OR_CELL );
    }

    /* Trace clearance. */
    marge    = (Board.m_GridRouting * Module->m_PadNum ) / GAIN;
    Penalite = PENALITE;
    TracePenaliteRectangle( GetBoard(), ox, oy, fx, fy, marge, Penalite,
                            masque_layer );
}


/*
 * Search for the optimal position of the module.
 * Entree:
 * Module tip MODULE struct module's place.
 * Returns:
 * 1 if placement impossible, 0 if OK
 * = MinCout and external variable = cost of best placement
 */
int PCB_EDIT_FRAME::RecherchePlacementModule( MODULE* Module, wxDC* DC )
{
    int     cx, cy;
    int     ox, oy, fx, fy; /* occupying part of the module focuses on the
                             * cursor */
    int     error = 1;
    int     DisplayChevelu = 0;
    wxPoint LastPosOK;
    float   mincout, cout, Score;
    int     Penalite;
    bool    TstOtherSide;

    Module->DisplayInfo( this );

    LastPosOK.x = GetBoard()->m_BoundaryBox.m_Pos.x;
    LastPosOK.y = GetBoard()->m_BoundaryBox.m_Pos.y;

    cx = Module->m_Pos.x; cy = Module->m_Pos.y;
    ox = Module->m_RealBoundaryBox.m_Pos.x - cx;
    fx = Module->m_RealBoundaryBox.m_Size.x + ox;
    oy = Module->m_RealBoundaryBox.m_Pos.y - cy;
    fy = Module->m_RealBoundaryBox.m_Size.y + oy;

    CurrPosition.x = GetBoard()->m_BoundaryBox.m_Pos.x - ox;
    CurrPosition.y = GetBoard()->m_BoundaryBox.m_Pos.y - oy;
    /* Module placement on grid. */
    CurrPosition.x -= CurrPosition.x % Board.m_GridRouting;
    CurrPosition.y -= CurrPosition.y % Board.m_GridRouting;

    g_Offset_Module.x = cx - CurrPosition.x;
    g_Offset_Module.y = cy - CurrPosition.y;
    GetBoard()->m_Status_Pcb &= ~RATSNEST_ITEM_LOCAL_OK;

    /* Test pads, a printed circuit with components of the 2 dimensions
     * can become a component on opposite side if there is at least 1 patch
     * appearing on the other side.
     */
    TstOtherSide = false;
    if( Nb_Sides == TWO_SIDES )
    {
        D_PAD* Pad; int masque_otherlayer;
        masque_otherlayer = LAYER_BACK;
        if( Module->GetLayer() == LAYER_N_BACK )
            masque_otherlayer = LAYER_FRONT;

        for( Pad = Module->m_Pads; Pad != NULL; Pad = Pad->Next() )
        {
            if( ( Pad->m_Masque_Layer & masque_otherlayer ) == 0 )
                continue;
            TstOtherSide = true;
            break;
        }
    }


    DrawModuleOutlines( DrawPanel, DC, Module );

    mincout = -1.0;
    SetStatusText( wxT( "Score ??, pos ??" ) );

    for( ; CurrPosition.x < GetBoard()->m_BoundaryBox.GetRight() - fx;
         CurrPosition.x += Board.m_GridRouting )
    {
        wxYield();
        if( DrawPanel->m_AbortRequest )
        {
            if( IsOK( this, _( "Ok to abort?" ) ) )
                return ESC;
            else
                DrawPanel->m_AbortRequest = false;
        }

        cx = Module->m_Pos.x; cy = Module->m_Pos.y;
        Module->m_RealBoundaryBox.m_Pos.x = ox + CurrPosition.x;
        Module->m_RealBoundaryBox.m_Pos.y = oy + CurrPosition.y;

        DrawModuleOutlines( DrawPanel, DC, Module );

        g_Offset_Module.x = cx - CurrPosition.x;
        CurrPosition.y    = GetBoard()->m_BoundaryBox.m_Pos.y - oy;
        /* Placement on grid. */
        CurrPosition.y -= CurrPosition.y % Board.m_GridRouting;

        DrawModuleOutlines( DrawPanel, DC, Module );

        for( ; CurrPosition.y < GetBoard()->m_BoundaryBox.GetBottom() - fy;
             CurrPosition.y += Board.m_GridRouting )
        {
            /* Erase traces. */
            DrawModuleOutlines( DrawPanel, DC, Module );
            if( DisplayChevelu )
                Compute_Ratsnest_PlaceModule( DC );
            DisplayChevelu = 0;
            Module->m_RealBoundaryBox.m_Pos.x = ox + CurrPosition.x;
            Module->m_RealBoundaryBox.m_Pos.y = oy + CurrPosition.y;

            g_Offset_Module.y = cy - CurrPosition.y;
            DrawModuleOutlines( DrawPanel, DC, Module );
            Penalite = TstModuleOnBoard( GetBoard(), Module, TstOtherSide );
            if( Penalite >= 0 ) /* c a d if the module can be placed. */
            {
                error = 0;
                build_ratsnest_module( Module );
                cout = Compute_Ratsnest_PlaceModule( DC );
                DisplayChevelu = 1;
                Score = cout + (float) Penalite;

                if( (mincout >= Score ) || (mincout < 0 ) )
                {
                    LastPosOK = CurrPosition;
                    mincout   = Score;
                    wxString msg;
                    msg.Printf( wxT( "Score %d, pos %3.4f, %3.4f" ),
                                (int) mincout,
                                (float) LastPosOK.x / 10000,
                                (float) LastPosOK.y / 10000 );
                    SetStatusText( msg );
                }
            }
            if( DisplayChevelu )
                Compute_Ratsnest_PlaceModule( DC );
            DisplayChevelu = 0;
        }
    }

    DrawModuleOutlines( DrawPanel, DC, Module );  /* erasing the last traces */
    if( DisplayChevelu )
        Compute_Ratsnest_PlaceModule( DC );

    /* Regeneration of the modified variable. */
    Module->m_RealBoundaryBox.m_Pos.x = ox + cx;
    Module->m_RealBoundaryBox.m_Pos.y = oy + cy;
    CurrPosition = LastPosOK;

    GetBoard()->m_Status_Pcb &= ~( RATSNEST_ITEM_LOCAL_OK | LISTE_PAD_OK );

    MinCout = mincout;
    return error;
}


/* Test if the rectangular area (ux, ux .. y0, y1):
 * - is a free zone (except OCCUPED_By_MODULE returns)
 * - is on the working surface of the board (otherwise returns OUT_OF_BOARD)
 *
 * Returns 0 if OK
 */
int TstRectangle( BOARD* Pcb, int ux0, int uy0, int ux1, int uy1, int side )
{
    int          row, col;
    int          row_min, row_max, col_min, col_max;
    unsigned int data;

    ux0 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy0 -= Pcb->m_BoundaryBox.m_Pos.y;
    ux1 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy1 -= Pcb->m_BoundaryBox.m_Pos.y;

    row_max = uy1 / Board.m_GridRouting;
    col_max = ux1 / Board.m_GridRouting;
    row_min = uy0 / Board.m_GridRouting;
    if( uy0 > row_min * Board.m_GridRouting )
        row_min++;
    col_min = ux0 / Board.m_GridRouting;
    if( ux0 > col_min * Board.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;
    if( row_max >= ( Nrows - 1 ) )
        row_max = Nrows - 1;
    if( col_min < 0 )
        col_min = 0;
    if( col_max >= ( Ncols - 1 ) )
        col_max = Ncols - 1;

    for( row = row_min; row <= row_max; row++ )
    {
        for( col = col_min; col <= col_max; col++ )
        {
            data = GetCell( row, col, side );
            if( ( data & CELL_is_ZONE ) == 0 )
                return OUT_OF_BOARD;
            if( data & CELL_is_MODULE )
                return OCCUPED_By_MODULE;
        }
    }

    return 0;
}


/* Calculates and returns the clearance area of the rectangular surface
 * (ux, ux .. y0, y1):
 * (Sum of cells in terms of distance)
 */
unsigned int CalculePenaliteRectangle( BOARD* Pcb, int ux0, int uy0,
                                       int ux1, int uy1, int side )
{
    int          row, col;
    int          row_min, row_max, col_min, col_max;
    unsigned int Penalite;

    ux0 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy0 -= Pcb->m_BoundaryBox.m_Pos.y;
    ux1 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy1 -= Pcb->m_BoundaryBox.m_Pos.y;

    row_max = uy1 / Board.m_GridRouting;
    col_max = ux1 / Board.m_GridRouting;
    row_min = uy0 / Board.m_GridRouting;
    if( uy0 > row_min * Board.m_GridRouting )
        row_min++;
    col_min = ux0 / Board.m_GridRouting;
    if( ux0 > col_min * Board.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;
    if( row_max >= ( Nrows - 1 ) )
        row_max = Nrows - 1;
    if( col_min < 0 )
        col_min = 0;
    if( col_max >= ( Ncols - 1 ) )
        col_max = Ncols - 1;

    Penalite = 0;
    for( row = row_min; row <= row_max; row++ )
    {
        for( col = col_min; col <= col_max; col++ )
        {
            Penalite += (int) GetDist( row, col, side );
        }
    }

    return Penalite;
}


/* Test if the module can be placed on the board.
 * Returns the value TstRectangle().
 * Module is known by its rectangle
 */
int TstModuleOnBoard( BOARD* Pcb, MODULE* Module, bool TstOtherSide )
{
    int ox, oy, fx, fy;
    int error, Penalite, marge, side, otherside;

    side = TOP; otherside = BOTTOM;
    if( Module->GetLayer() == LAYER_N_BACK )
    {
        side = BOTTOM; otherside = TOP;
    }

    ox = Module->m_RealBoundaryBox.m_Pos.x;
    fx = Module->m_RealBoundaryBox.GetRight();
    oy = Module->m_RealBoundaryBox.m_Pos.y;
    fy = Module->m_RealBoundaryBox.GetBottom();

    error = TstRectangle( Pcb, ox, oy, fx, fy, side );
    if( error < 0 )
        return error;

    if( TstOtherSide )
    {
        error = TstRectangle( Pcb, ox, oy, fx, fy, otherside );
        if( error < 0 )
            return error;
    }

    marge = ( Board.m_GridRouting * Module->m_PadNum ) / GAIN;

    Penalite = CalculePenaliteRectangle( Pcb, ox - marge, oy - marge,
                                         fx + marge, fy + marge, side );
    return Penalite;
}


/*
 * Display the module's ratsnet during displacement, and
 * assess the "cost" of the position.
 * The cost is the longest ratsnest distance with penalty for connections
 * approaching 45 degrees.
 */
float PCB_EDIT_FRAME::Compute_Ratsnest_PlaceModule( wxDC* DC )
{
    double cout, icout;
    int    ox, oy;
    int    fx, fy;
    int    dx, dy;

    if( ( GetBoard()->m_Status_Pcb & RATSNEST_ITEM_LOCAL_OK ) == 0 )
        return -1;
    cout = 0;

    int color = g_ColorsSettings.GetItemColor(RATSNEST_VISIBLE);

    if( AutoPlaceShowAll )
        GRSetDrawMode( DC, GR_XOR );
    for( unsigned ii = 0; ii < GetBoard()->m_LocalRatsnest.size(); ii++ )
    {
        RATSNEST_ITEM* pt_local_chevelu = &GetBoard()->m_LocalRatsnest[ii];
        if( !( pt_local_chevelu->m_Status & LOCAL_RATSNEST_ITEM ) )
        {
            ox = pt_local_chevelu->m_PadStart->GetPosition().x -
                 g_Offset_Module.x;
            oy = pt_local_chevelu->m_PadStart->GetPosition().y -
                 g_Offset_Module.y;
            fx = pt_local_chevelu->m_PadEnd->GetPosition().x;
            fy = pt_local_chevelu->m_PadEnd->GetPosition().y;

            if( AutoPlaceShowAll )
            {
                GRLine( &DrawPanel->m_ClipBox, DC, ox, oy, fx, fy,
                        0, color );
            }

            /* Cost of the ratsnest. */
            dx = fx - ox;
            dy = fy - oy;

            dx = abs( dx );
            dy = abs( dy );

            if( dx < dy )
                EXCHG( dx, dy );  /* dx >= dy */

            /* Cost of the longest connection. */
            icout = (float) dx * dx;

            /* Cost of inclination. */
            icout += 3 * (float) dy * dy;
            icout  = sqrt( icout );
            cout  += icout; /* Total cost = sum of costs of each connection. */
        }
    }

    return (float) cout;
}


/***********************************/
/* Draw keep out area of a module. */
/***********************************/

/* Buid the cost map.
 * Cells ( in Dist mao ) inside the rect x0,y0 a x1,y1 are
 *  incremented by value Penalite
 *  Cell outside this rectangle, but inside the rectangle
 *  x0,y0 -marge to x1,y1 + marge sont incrementede by a decreasing value
 *  (Penalite ... 0). The decreasing value de pends on the distance to the first rectangle
 *  Therefore the cost is hight in  rect x0,y0 a x1,y1, and decrease outside this rectangle
 */
static void TracePenaliteRectangle( BOARD* Pcb,
                                    int    ux0,
                                    int    uy0,
                                    int    ux1,
                                    int    uy1,
                                    int    marge,
                                    int    Penalite,
                                    int    masque_layer )
{
    int      row, col;
    int      row_min, row_max, col_min, col_max, pmarge;
    int      trace = 0;
    DIST_CELL data, LocalPenalite;
    int      lgain, cgain;

    if( masque_layer & g_TabOneLayerMask[Route_Layer_BOTTOM] )
        trace = 1;     /* Trace on bottom layer. */

    if( ( masque_layer & g_TabOneLayerMask[Route_Layer_TOP] ) && Nb_Sides )
        trace |= 2;    /* Trace on top layer. */

    if( trace == 0 )
        return;

    ux0 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy0 -= Pcb->m_BoundaryBox.m_Pos.y;
    ux1 -= Pcb->m_BoundaryBox.m_Pos.x;
    uy1 -= Pcb->m_BoundaryBox.m_Pos.y;

    ux0 -= marge; ux1 += marge;
    uy0 -= marge; uy1 += marge;

    pmarge = marge / Board.m_GridRouting; if( pmarge < 1 )
        pmarge = 1;

    /* Calculate the coordinate limits of the rectangle. */
    row_max = uy1 / Board.m_GridRouting;
    col_max = ux1 / Board.m_GridRouting;
    row_min = uy0 / Board.m_GridRouting;
    if( uy0 > row_min * Board.m_GridRouting )
        row_min++;
    col_min = ux0 / Board.m_GridRouting;
    if( ux0 > col_min * Board.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;
    if( row_max >= (Nrows - 1) )
        row_max = Nrows - 1;
    if( col_min < 0 )
        col_min = 0;
    if( col_max >= (Ncols - 1) )
        col_max = Ncols - 1;

    for( row = row_min; row <= row_max; row++ )
    {
        lgain = 256;
        if( row < pmarge )
            lgain = ( 256 * row ) / pmarge;
        else if( row > row_max - pmarge )
            lgain = ( 256 * ( row_max - row ) ) / pmarge;

        for( col = col_min; col <= col_max; col++ )
        {
            cgain = 256;
            LocalPenalite = Penalite;
            if( col < pmarge )
                cgain = ( 256 * col ) / pmarge;
            else if( col > col_max - pmarge )
                cgain = ( 256 * ( col_max - col ) ) / pmarge;

            cgain = ( cgain * lgain ) / 256;
            if( cgain != 256 )
                LocalPenalite = ( LocalPenalite * cgain ) / 256;
            if( trace & 1 )
            {
                data = GetDist( row, col, BOTTOM ) + LocalPenalite;
                SetDist( row, col, BOTTOM, data );
            }
            if( trace & 2 )
            {
                data = GetDist( row, col, TOP );
                data = MAX( data, LocalPenalite );
                SetDist( row, col, TOP, data );
            }
        }
    }
}


/* Sort routines */
static bool Tri_PlaceModules( MODULE* ref, MODULE* compare )
{
    double ff1, ff2;

    ff1 = ref->m_Surface * ref->m_PadNum;
    ff2 = compare->m_Surface * compare->m_PadNum;
    return ff2 < ff1;
}

static bool Tri_RatsModules( MODULE* ref, MODULE* compare )
{
    double ff1, ff2;

    ff1 = ref->m_Surface * ref->flag;
    ff2 = compare->m_Surface * compare->flag;
    return ff2 < ff1;
}


/* Find the "best" module place
 * The criteria of choice are:
 * - Maximum ratsnet with modules already placed
 * - Max size, and number of pads max
 */
static MODULE* PickModule( PCB_EDIT_FRAME* pcbframe, wxDC* DC )
{
    MODULE*  Module;
    std::vector <MODULE*> moduleList;

    // Build sorted footprints list (sort by decreasing size )
    Module = pcbframe->GetBoard()->m_Modules;
    for( ; Module != NULL; Module = Module->Next() )
    {
        Module->Set_Rectangle_Encadrement();
        Module->SetRectangleExinscrit();
        moduleList.push_back(Module);
    }
    sort( moduleList.begin(), moduleList.end(), Tri_PlaceModules );

    for( unsigned ii = 0; ii < moduleList.size(); ii++ )
    {
        Module = moduleList[ii];
        Module->flag = 0;
        if( !( Module->m_ModuleStatus & MODULE_to_PLACE ) )
            continue;
        pcbframe->GetBoard()->m_Status_Pcb &= ~RATSNEST_ITEM_LOCAL_OK;
        Module->DisplayInfo( pcbframe );
        pcbframe->build_ratsnest_module( Module );

        /* Calculate external ratsnet. */
        for( unsigned ii = 0;
             ii < pcbframe->GetBoard()->m_LocalRatsnest.size();
             ii++ )
        {
            if( ( pcbframe->GetBoard()->m_LocalRatsnest[ii].m_Status &
                  LOCAL_RATSNEST_ITEM ) == 0 )
                Module->flag++;
        }
    }

    pcbframe->GetBoard()->m_Status_Pcb &= ~RATSNEST_ITEM_LOCAL_OK;

    sort( moduleList.begin(), moduleList.end(), Tri_RatsModules );

    /* Search for "best" module. */
    MODULE* bestModule = NULL;
    MODULE* altModule = NULL;
    for( unsigned ii = 0; ii < moduleList.size(); ii++ )
    {
        Module = moduleList[ii];
        if( !( Module->m_ModuleStatus & MODULE_to_PLACE ) )
            continue;
        altModule = Module;
        if( Module->flag == 0 )
            continue;
        bestModule = Module;
        break;
    }

    if( bestModule )
        return bestModule;
    else
        return altModule;
}


/********************************************/
int Propagation( PCB_EDIT_FRAME* frame )
/********************************************/

/**
 * Function Propagation
 * Used now only in autoplace calculations
 * Uses the routing matrix to fill the cells within the zone
 * Search and mark cells within the zone, and agree with DRC options.
 * Requirements:
 * Start from an initial point, to fill zone
 * The zone must have no "copper island"
 *  Algorithm:
 *  If the current cell has a neightbour flagged as "cell in the zone", it
 *  become a cell in the zone
 *  The first point in the zone is the starting point
 *  4 searches within the matrix are made:
 *          1 - Left to right and top to bottom
 *          2 - Right to left and top to bottom
 *          3 - bottom to top and Right to left
 *          4 - bottom to top and Left to right
 *  Given the current cell, for each search, we consider the 2 neightbour cells
 *  the previous cell on the same line and the previous cell on the same column.
 *
 *  This funtion can request some iterations
 *  Iterations are made until no cell is added to the zone.
 *  @return: added cells count (i.e. which the attribute CELL_is_ZONE is set)
 */
{
    int       row, col, nn;
    long      current_cell, old_cell_H;
    int long* pt_cell_V;
    int       nbpoints = 0;

#define NO_CELL_ZONE (HOLE | CELL_is_EDGE | CELL_is_ZONE)
    wxString  msg;

    frame->MsgPanel->SetMessage( 57, wxT( "Detect" ), msg, CYAN );
    frame->MsgPanel->SetMessage( -1, wxEmptyString, wxT( "1" ), CYAN );

    // Alloc memory to handle 1 line or 1 colunmn on the routing matrix
    nn = MAX( Nrows, Ncols ) * sizeof(*pt_cell_V);
    pt_cell_V = (long*) MyMalloc( nn );

    /* search 1 : from left to right and top to bottom */
    memset( pt_cell_V, 0, nn );
    for( row = 0; row < Nrows; row++ )
    {
        old_cell_H = 0;
        for( col = 0; col < Ncols; col++ )
        {
            current_cell = GetCell( row, col, BOTTOM ) & NO_CELL_ZONE;
            if( current_cell == 0 )  /* a free cell is found */
            {
                if( (old_cell_H & CELL_is_ZONE)
                   || (pt_cell_V[col] & CELL_is_ZONE) )
                {
                    OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }
            pt_cell_V[col] = old_cell_H = current_cell;
        }
    }

    /* search 2 : from right to left and top to bottom */
    frame->MsgPanel->SetMessage( -1, wxEmptyString, wxT( "2" ), CYAN );
    memset( pt_cell_V, 0, nn );
    for( row = 0; row < Nrows; row++ )
    {
        old_cell_H = 0;
        for( col = Ncols - 1; col >= 0; col-- )
        {
            current_cell = GetCell( row, col, BOTTOM ) & NO_CELL_ZONE;
            if( current_cell == 0 )  /* a free cell is found */
            {
                if( (old_cell_H & CELL_is_ZONE)
                   || (pt_cell_V[col] & CELL_is_ZONE) )
                {
                    OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }
            pt_cell_V[col] = old_cell_H = current_cell;
        }
    }

    /* search 3 : from bottom to top and right to left balayage */
    frame->MsgPanel->SetMessage( -1, wxEmptyString, wxT( "3" ), CYAN );
    memset( pt_cell_V, 0, nn );
    for( col = Ncols - 1; col >= 0; col-- )
    {
        old_cell_H = 0;
        for( row = Nrows - 1; row >= 0; row-- )
        {
            current_cell = GetCell( row, col, BOTTOM ) & NO_CELL_ZONE;
            if( current_cell == 0 )  /* a free cell is found */
            {
                if( (old_cell_H & CELL_is_ZONE)
                   || (pt_cell_V[row] & CELL_is_ZONE) )
                {
                    OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }
            pt_cell_V[row] = old_cell_H = current_cell;
        }
    }

    /* search 4 : from bottom to top and left to right */
    frame->MsgPanel->SetMessage( -1, wxEmptyString, wxT( "4" ), CYAN );
    memset( pt_cell_V, 0, nn );

    for( col = 0; col < Ncols; col++ )
    {
        old_cell_H = 0;
        for( row = Nrows - 1; row >= 0; row-- )
        {
            current_cell = GetCell( row, col, BOTTOM ) & NO_CELL_ZONE;
            if( current_cell == 0 )  /* a free cell is found */
            {
                if( (old_cell_H & CELL_is_ZONE)
                   || (pt_cell_V[row] & CELL_is_ZONE) )
                {
                    OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }
            pt_cell_V[row] = old_cell_H = current_cell;
        }
    }

    MyFree( pt_cell_V );

    return nbpoints;
}

