/**
 * @file autoplac.cpp
 * @brief Routiness to automatically place MODULES on a board.
 */

/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
 * Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
 *
 * Copyright (C) 1992-2012 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 <class_drawpanel.h>
#include <confirm.h>
#include <pcbnew.h>
#include <wxPcbStruct.h>
#include <gr_basic.h>
#include <macros.h>
#include <pcbcommon.h>
#include <msgpanel.h>

#include <protos.h>
#include <autorout.h>
#include <cell.h>
#include <colors_selection.h>

#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
#include <class_drawsegment.h>
#include <convert_to_biu.h>


#define GAIN            16
#define KEEP_OUT_MARGIN 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 double 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;

double          MinCout;

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

static void CreateKeepOutRectangle( int ux0, int uy0, int ux1, int uy1,
                                    int marge, int aKeepOut, int aLayerMask );

static MODULE* PickModule( PCB_EDIT_FRAME* pcbframe, wxDC* DC );
static int propagate();

void PCB_EDIT_FRAME::AutoPlaceModule( MODULE* Module, int place_mode, wxDC* DC )
{
    MODULE*  currModule = NULL;
    wxPoint  PosOK;
    wxPoint  memopos;
    int      error;
    int      lay_tmp_TOP, lay_tmp_BOTTOM;

    // Undo: init list
    PICKED_ITEMS_LIST  newList;
    newList.m_Status = UR_CHANGED;
    ITEM_PICKER        picker( NULL, UR_CHANGED );

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

    m_canvas->SetAbortRequest( false );

    switch( place_mode )
    {
        case PLACE_1_MODULE:
            currModule = Module;

            if( currModule == NULL )
                return;

            currModule->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;

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

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

    /* Compute module parameters used in auto place */
    if( GenPlaceBoard() == 0 )
        return;

    int moduleCount = 0;
    Module = GetBoard()->m_Modules;

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

        switch( place_mode )
        {
        case PLACE_1_MODULE:
            if( currModule == Module )
            {
                // Module will be placed, add to undo.
                picker.SetItem( currModule );
                newList.PushItem( picker );

                Module->m_ModuleStatus |= MODULE_to_PLACE;
            }

            break;

        case PLACE_OUT_OF_BOARD:
            Module->SetIsPlaced( false );

            if( Module->IsLocked() )
                break;

            if( !RoutingMatrix.m_BrdBox.Contains( Module->m_Pos ) )
            {
                // Module will be placed, add to undo.
                picker.SetItem( Module );
                newList.PushItem( picker );

                Module->m_ModuleStatus |= MODULE_to_PLACE;
            }

            break;

        case PLACE_ALL:
            Module->SetIsPlaced( false );

            if( Module->IsLocked() )
                break;

            // Module will be placed, add to undo.
            picker.SetItem( Module );
            newList.PushItem( picker );

            Module->m_ModuleStatus |= MODULE_to_PLACE;
            break;

        case PLACE_INCREMENTAL:
            if( Module->IsLocked() )
            {
                Module->SetIsPlaced( false );
                break;
            }

            if( !(Module->m_ModuleStatus & MODULE_is_PLACED) )
            {
                // Module will be placed, add to undo.
                picker.SetItem( Module );
                newList.PushItem( picker );

                Module->m_ModuleStatus |= MODULE_to_PLACE;
            }

            break;
        }


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

    // Undo command: commit list
    if( newList.GetCount() )
        SaveCopyInUndoList( newList, UR_CHANGED );

    int cnt = 0;
    int ii;
    wxString msg;

    while( ( Module = PickModule( this, DC ) ) != NULL )
    {
        // Display some info about activity, module placement can take a while:
        msg.Printf( _("Place module %d of %d"), cnt, moduleCount );
        SetStatusText( msg );

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

        error     = GetOptimalModulePlacement( Module, DC );
        double 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->CalculateBoundingBox();
            error    = GetOptimalModulePlacement( 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    = GetOptimalModulePlacement( 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    = GetOptimalModulePlacement( 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 );
        PlaceModule( Module, DC );
        GetScreen()->SetCrossHairPosition( CurrPosition );

        Module->CalculateBoundingBox();

        GenModuleOnBoard( Module );
        Module->SetIsPlaced( true );
        Module->m_ModuleStatus &= ~MODULE_to_PLACE;
    }

    CurrPosition = memopos;

    RoutingMatrix.UnInitRoutingMatrix();

    Route_Layer_TOP    = lay_tmp_TOP;
    Route_Layer_BOTTOM = lay_tmp_BOTTOM;

    Module = GetBoard()->m_Modules;

    for( ; Module != NULL; Module = Module->Next() )
    {
        Module->CalculateBoundingBox();
    }

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


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

    GRSetDrawMode( DC, GR_COPY );

    for( ii = 0; ii < RoutingMatrix.m_Nrows; ii++ )
    {
        oy = RoutingMatrix.m_BrdBox.GetY() + ( ii * RoutingMatrix.m_GridRouting );

        for( jj = 0; jj < RoutingMatrix.m_Ncols; jj++ )
        {
            ox = RoutingMatrix.m_BrdBox.GetX() + (jj * RoutingMatrix.m_GridRouting);
            color = BLACK;

            top_state    = RoutingMatrix.GetCell( ii, jj, TOP );
            bottom_state = RoutingMatrix.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( RoutingMatrix.GetDist( ii, jj, TOP ) ||
                    RoutingMatrix.GetDist( ii, jj, BOTTOM ) )
                    color = DARKGRAY;
            }

            GRPutPixel( m_canvas->GetClipBox(), DC, ox, oy, color );
        }
    }
}


int PCB_EDIT_FRAME::GenPlaceBoard()
{
    wxString  msg;

    RoutingMatrix.UnInitRoutingMatrix();

    EDA_RECT bbox = GetBoard()->ComputeBoundingBox( true );

    if( bbox.GetWidth() == 0 || bbox.GetHeight() == 0 )
    {
        DisplayError( this, _( "No PCB edge found, unknown board size!" ) );
        return 0;
    }

    RoutingMatrix.ComputeMatrixSize( GetBoard(), true );
    int nbCells = RoutingMatrix.m_Ncols * RoutingMatrix.m_Nrows;

    m_messagePanel->EraseMsgBox();
    msg.Printf( wxT( "%d" ), RoutingMatrix.m_Ncols );
    m_messagePanel->SetMessage( 1, _( "Cols" ), msg, GREEN );
    msg.Printf( wxT( "%d" ), RoutingMatrix.m_Nrows );
    m_messagePanel->SetMessage( 7, _( "Lines" ), msg, GREEN );
    msg.Printf( wxT( "%d" ), nbCells );
    m_messagePanel->SetMessage( 14, _( "Cells." ), msg, YELLOW );

    /* Choose the number of board sides. */
    RoutingMatrix.m_RoutingLayersCount = 2;

    RoutingMatrix.InitRoutingMatrix();

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

    Route_Layer_BOTTOM = LAYER_N_FRONT;

    if( RoutingMatrix.m_RoutingLayersCount > 1 )
        Route_Layer_BOTTOM = LAYER_N_BACK;

    Route_Layer_TOP = LAYER_N_FRONT;

    /* Place the edge layer segments */
    TRACK TmpSegm( NULL );

    TmpSegm.SetLayer( -1 );
    TmpSegm.SetNet( -1 );
    TmpSegm.SetWidth( RoutingMatrix.m_GridRouting / 2 );

    EDA_ITEM* PtStruct = GetBoard()->m_Drawings;

    for( ; PtStruct != NULL; PtStruct = PtStruct->Next() )
    {
        DRAWSEGMENT* DrawSegm;

        switch( PtStruct->Type() )
        {
        case PCB_LINE_T:
            DrawSegm = (DRAWSEGMENT*) PtStruct;

            if( DrawSegm->GetLayer() != EDGE_N )
                break;

            TmpSegm.SetStart( DrawSegm->GetStart() );
            TmpSegm.SetEnd(   DrawSegm->GetEnd() );
            TmpSegm.SetShape( DrawSegm->GetShape() );
            TmpSegm.m_Param = DrawSegm->GetAngle();

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

        case PCB_TEXT_T:
        default:
            break;
        }
    }

    // Mark cells of the routing matrix to CELL_is_ZONE
    // (i.e. availlable cell to place a module )
    // Init a starting point of attachment to the area.
    RoutingMatrix.OrCell( RoutingMatrix.m_Nrows / 2, RoutingMatrix.m_Ncols / 2,
                          BOTTOM, CELL_is_ZONE );

    // find and mark all other availlable cells:
    for( int ii = 1; ii != 0; )
        ii = propagate();

    // Initialize top layer. to the same value as the bottom layer
    if( RoutingMatrix.m_BoardSide[TOP] )
        memcpy( RoutingMatrix.m_BoardSide[TOP], RoutingMatrix.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;
    int    marge = RoutingMatrix.m_GridRouting / 2;
    int    layerMask;
    D_PAD* Pad;

    ox = Module->m_BoundaryBox.GetX() - marge;
    fx = Module->m_BoundaryBox.GetRight() + marge;
    oy = Module->m_BoundaryBox.GetY() - marge;
    fy = Module->m_BoundaryBox.GetBottom() + marge;

    if( ox < RoutingMatrix.m_BrdBox.GetX() )
        ox = RoutingMatrix.m_BrdBox.GetX();

    if( ox > RoutingMatrix.m_BrdBox.GetRight() )
        ox = RoutingMatrix.m_BrdBox.GetRight();

    if( fx < RoutingMatrix.m_BrdBox.GetX() )
        fx = RoutingMatrix.m_BrdBox.GetX();

    if( fx > RoutingMatrix.m_BrdBox.GetRight() )
        fx = RoutingMatrix.m_BrdBox.GetRight();

    if( oy < RoutingMatrix.m_BrdBox.GetY() )
        oy = RoutingMatrix.m_BrdBox.GetY();

    if( oy > RoutingMatrix.m_BrdBox.GetBottom() )
        oy = RoutingMatrix.m_BrdBox.GetBottom();

    if( fy < RoutingMatrix.m_BrdBox.GetY() )
        fy = RoutingMatrix.m_BrdBox.GetY();

    if( fy > RoutingMatrix.m_BrdBox.GetBottom() )
        fy = RoutingMatrix.m_BrdBox.GetBottom();

    layerMask = 0;

    if( Module->GetLayer() == LAYER_N_FRONT )
        layerMask = LAYER_FRONT;

    if( Module->GetLayer() == LAYER_N_BACK )
        layerMask = LAYER_BACK;

    TraceFilledRectangle( ox, oy, fx, fy, layerMask,
                          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() )
    {
        ::PlacePad( Pad, CELL_is_MODULE, marge, WRITE_OR_CELL );
    }

    /* Trace clearance. */
    marge   = ( RoutingMatrix.m_GridRouting * Module->GetPadCount() ) / GAIN;
    CreateKeepOutRectangle( ox, oy, fx, fy, marge, KEEP_OUT_MARGIN, layerMask );
}


int PCB_EDIT_FRAME::GetOptimalModulePlacement( MODULE* aModule, wxDC* aDC )
{
    int     cx, cy;
    int     ox, oy, fx, fy; /* occupying part of the module focuses on the cursor */
    int     error = 1;
    int     showRat = 0;
    wxPoint LastPosOK;
    double  mincout, cout, Score;
    int     keepOut;
    bool    TstOtherSide;
    bool    showRats = g_Show_Module_Ratsnest;

    g_Show_Module_Ratsnest = false;

    SetMsgPanel( aModule );

    LastPosOK.x = RoutingMatrix.m_BrdBox.GetX();
    LastPosOK.y = RoutingMatrix.m_BrdBox.GetY();

    cx = aModule->m_Pos.x; cy = aModule->m_Pos.y;
    ox = aModule->m_BoundaryBox.GetX() - cx;
    fx = aModule->m_BoundaryBox.GetWidth() + ox;
    oy = aModule->m_BoundaryBox.GetY() - cy;
    fy = aModule->m_BoundaryBox.GetHeight() + oy;

    CurrPosition.x = RoutingMatrix.m_BrdBox.GetX() - ox;
    CurrPosition.y = RoutingMatrix.m_BrdBox.GetY() - oy;

    /* Module placement on grid. */
    CurrPosition.x -= CurrPosition.x % RoutingMatrix.m_GridRouting;
    CurrPosition.y -= CurrPosition.y % RoutingMatrix.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( RoutingMatrix.m_RoutingLayersCount > 1 )
    {
        D_PAD* Pad;
        int otherLayerMask = LAYER_BACK;

        if( aModule->GetLayer() == LAYER_N_BACK )
            otherLayerMask = LAYER_FRONT;

        for( Pad = aModule->m_Pads; Pad != NULL; Pad = Pad->Next() )
        {
            if( ( Pad->GetLayerMask() & otherLayerMask ) == 0 )
                continue;

            TstOtherSide = true;
            break;
        }
    }

    DrawModuleOutlines( m_canvas, aDC, aModule );

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

    for( ; CurrPosition.x < RoutingMatrix.m_BrdBox.GetRight() - fx;
         CurrPosition.x += RoutingMatrix.m_GridRouting )
    {
        wxYield();

        if( m_canvas->GetAbortRequest() )
        {
            if( IsOK( this, _( "Ok to abort?" ) ) )
                return ESC;
            else
                m_canvas->SetAbortRequest( false );
        }

        cx = aModule->m_Pos.x; cy = aModule->m_Pos.y;
        aModule->m_BoundaryBox.SetX( ox + CurrPosition.x );
        aModule->m_BoundaryBox.SetY( oy + CurrPosition.y );

        DrawModuleOutlines( m_canvas, aDC, aModule );

        g_Offset_Module.x = cx - CurrPosition.x;
        CurrPosition.y    = RoutingMatrix.m_BrdBox.GetY() - oy;

        /* Placement on grid. */
        CurrPosition.y -= CurrPosition.y % RoutingMatrix.m_GridRouting;

        DrawModuleOutlines( m_canvas, aDC, aModule );

        for( ; CurrPosition.y < RoutingMatrix.m_BrdBox.GetBottom() - fy;
             CurrPosition.y += RoutingMatrix.m_GridRouting )
        {
#ifndef USE_WX_OVERLAY
            /* Erase traces. */
            DrawModuleOutlines( m_canvas, aDC, aModule );

            if( showRat )
                Compute_Ratsnest_PlaceModule( aDC );
#endif
            showRat = 0;
            aModule->m_BoundaryBox.SetX( ox + CurrPosition.x );
            aModule->m_BoundaryBox.SetY( oy + CurrPosition.y );

            g_Offset_Module.y = cy - CurrPosition.y;
#ifndef USE_WX_OVERLAY
            DrawModuleOutlines( m_canvas, aDC, aModule );
#endif
            keepOut = TstModuleOnBoard( GetBoard(), aModule, TstOtherSide );

            if( keepOut >= 0 ) // i.e. if the module can be put here
            {
                error = 0;
                build_ratsnest_module( aModule );
                cout = Compute_Ratsnest_PlaceModule( aDC );
                showRat = 1;
                Score = cout + keepOut;

                if( (mincout >= Score ) || (mincout < 0 ) )
                {
                    LastPosOK = CurrPosition;
                    mincout   = Score;
                    wxString msg;
                    msg.Printf( wxT( "Score %g, pos %3.4g, %3.4g" ),
                                mincout,
                                (double) LastPosOK.x / 10000,
                                (double) LastPosOK.y / 10000 );
                    SetStatusText( msg );
                }
            }

            if( showRat )
                Compute_Ratsnest_PlaceModule( aDC );

            showRat = 0;
        }
    }

    DrawModuleOutlines( m_canvas, aDC, aModule );  /* erasing the last traces */

    g_Show_Module_Ratsnest = showRats;

    if( showRat )
        Compute_Ratsnest_PlaceModule( aDC );

    /* Regeneration of the modified variable. */
    aModule->m_BoundaryBox.SetX( ox + cx );
    aModule->m_BoundaryBox.SetY( 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->GetBoundingBox().GetX();
    uy0 -= Pcb->GetBoundingBox().GetY();
    ux1 -= Pcb->GetBoundingBox().GetX();
    uy1 -= Pcb->GetBoundingBox().GetY();

    row_max = uy1 / RoutingMatrix.m_GridRouting;
    col_max = ux1 / RoutingMatrix.m_GridRouting;
    row_min = uy0 / RoutingMatrix.m_GridRouting;

    if( uy0 > row_min * RoutingMatrix.m_GridRouting )
        row_min++;

    col_min = ux0 / RoutingMatrix.m_GridRouting;

    if( ux0 > col_min * RoutingMatrix.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;

    if( row_max >= ( RoutingMatrix.m_Nrows - 1 ) )
        row_max = RoutingMatrix.m_Nrows - 1;

    if( col_min < 0 )
        col_min = 0;

    if( col_max >= ( RoutingMatrix.m_Ncols - 1 ) )
        col_max = RoutingMatrix.m_Ncols - 1;

    for( row = row_min; row <= row_max; row++ )
    {
        for( col = col_min; col <= col_max; col++ )
        {
            data = RoutingMatrix.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 CalculateKeepOutArea( int ux0, int uy0, int ux1, int uy1, int side )
{
    int          row, col;
    int          row_min, row_max, col_min, col_max;
    unsigned int keepOut;

    ux0 -= RoutingMatrix.m_BrdBox.GetX();
    uy0 -= RoutingMatrix.m_BrdBox.GetY();
    ux1 -= RoutingMatrix.m_BrdBox.GetX();
    uy1 -= RoutingMatrix.m_BrdBox.GetY();

    row_max = uy1 / RoutingMatrix.m_GridRouting;
    col_max = ux1 / RoutingMatrix.m_GridRouting;
    row_min = uy0 / RoutingMatrix.m_GridRouting;

    if( uy0 > row_min * RoutingMatrix.m_GridRouting )
        row_min++;

    col_min = ux0 / RoutingMatrix.m_GridRouting;

    if( ux0 > col_min * RoutingMatrix.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;

    if( row_max >= ( RoutingMatrix.m_Nrows - 1 ) )
        row_max = RoutingMatrix.m_Nrows - 1;

    if( col_min < 0 )
        col_min = 0;

    if( col_max >= ( RoutingMatrix.m_Ncols - 1 ) )
        col_max = RoutingMatrix.m_Ncols - 1;

    keepOut = 0;

    for( row = row_min; row <= row_max; row++ )
    {
        for( col = col_min; col <= col_max; col++ )
        {
            keepOut += RoutingMatrix.GetDist( row, col, side );
        }
    }

    return keepOut;
}


/* 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, marge, side, otherside;

    side = TOP; otherside = BOTTOM;

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

    ox = Module->m_BoundaryBox.GetX();
    fx = Module->m_BoundaryBox.GetRight();
    oy = Module->m_BoundaryBox.GetY();
    fy = Module->m_BoundaryBox.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 = ( RoutingMatrix.m_GridRouting * Module->GetPadCount() ) / GAIN;

    return CalculateKeepOutArea( ox - marge, oy - marge, fx + marge, fy + marge, side );
}


double PCB_EDIT_FRAME::Compute_Ratsnest_PlaceModule( wxDC* DC )
{
    double cout, icout;
    wxPoint start;      // start point of a ratsnest
    wxPoint end;        // end point of a ratsnest
    int    dx, dy;

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

    cout = 0;

    EDA_COLOR_T 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_rats_nest = &GetBoard()->m_LocalRatsnest[ii];

        if( ( pt_local_rats_nest->m_Status & LOCAL_RATSNEST_ITEM ) )
            continue;   // Skip ratsnest between 2 pads of the current module

        // Skip modules not inside the board area
        MODULE * module = pt_local_rats_nest->m_PadEnd->GetParent();
        if( !RoutingMatrix.m_BrdBox.Contains( module->GetPosition() ) )
            continue;

        start = pt_local_rats_nest->m_PadStart->GetPosition() - g_Offset_Module;
        end = pt_local_rats_nest->m_PadEnd->GetPosition();

#ifndef USE_WX_OVERLAY
        if( AutoPlaceShowAll )
        {
            GRLine( m_canvas->GetClipBox(), DC, start, end, 0, color );
        }
#endif
        /* Cost of the ratsnest. */
        dx = end.x - start.x;
        dy = end.y - start.y;

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

        // ttry to have always dx >= dy to calculate the cost of the rastsnet
        if( dx < dy )
            EXCHG( dx, dy );

        // Cost of the connection = lenght + penalty due to the slope
        // dx is the biggest lenght relative to the X or Y axis
        // the penalty is max for 45 degrees ratsnests,
        // and 0 for horizontal or vertical ratsnests.
        // For Horizontal and Vertical ratsnests, dy = 0;
        icout  = hypot( (double) dx, (double) dy * 2.0 );
        cout  += icout; /* Total cost = sum of costs of each connection. */
    }

    return cout;
}


/**
 * Function CreateKeepOutRectangle
 * builds the cost map:
 * Cells ( in Dist map ) inside the rect x0,y0 a x1,y1 are
 *  incremented by value aKeepOut
 *  Cell outside this rectangle, but inside the rectangle
 *  x0,y0 -marge to x1,y1 + marge are incremented by a decreasing value
 *  (aKeepOut ... 0). The decreasing value de pends on the distance to the first rectangle
 *  Therefore the cost is high in rect x0,y0 a x1,y1, and decrease outside this rectangle
 */
void CreateKeepOutRectangle( int ux0, int uy0, int ux1, int uy1,
                             int marge, int aKeepOut, int aLayerMask )
{
    int      row, col;
    int      row_min, row_max, col_min, col_max, pmarge;
    int      trace = 0;
    DIST_CELL data, LocalKeepOut;
    int      lgain, cgain;

    if( aLayerMask & GetLayerMask( Route_Layer_BOTTOM ) )
        trace = 1;     /* Trace on bottom layer. */

    if( ( aLayerMask & GetLayerMask( Route_Layer_TOP ) ) && RoutingMatrix.m_RoutingLayersCount )
        trace |= 2;    /* Trace on top layer. */

    if( trace == 0 )
        return;

    ux0 -= RoutingMatrix.m_BrdBox.GetX();
    uy0 -= RoutingMatrix.m_BrdBox.GetY();
    ux1 -= RoutingMatrix.m_BrdBox.GetX();
    uy1 -= RoutingMatrix.m_BrdBox.GetY();

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

    pmarge = marge / RoutingMatrix.m_GridRouting;

    if( pmarge < 1 )
        pmarge = 1;

    /* Calculate the coordinate limits of the rectangle. */
    row_max = uy1 / RoutingMatrix.m_GridRouting;
    col_max = ux1 / RoutingMatrix.m_GridRouting;
    row_min = uy0 / RoutingMatrix.m_GridRouting;

    if( uy0 > row_min * RoutingMatrix.m_GridRouting )
        row_min++;

    col_min = ux0 / RoutingMatrix.m_GridRouting;

    if( ux0 > col_min * RoutingMatrix.m_GridRouting )
        col_min++;

    if( row_min < 0 )
        row_min = 0;

    if( row_max >= (RoutingMatrix.m_Nrows - 1) )
        row_max = RoutingMatrix.m_Nrows - 1;

    if( col_min < 0 )
        col_min = 0;

    if( col_max >= (RoutingMatrix.m_Ncols - 1) )
        col_max = RoutingMatrix.m_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;
            LocalKeepOut = aKeepOut;

            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 )
                LocalKeepOut = ( LocalKeepOut * cgain ) / 256;

            if( trace & 1 )
            {
                data = RoutingMatrix.GetDist( row, col, BOTTOM ) + LocalKeepOut;
                RoutingMatrix.SetDist( row, col, BOTTOM, data );
            }

            if( trace & 2 )
            {
                data = RoutingMatrix.GetDist( row, col, TOP );
                data = std::max( data, LocalKeepOut );
                RoutingMatrix.SetDist( row, col, TOP, data );
            }
        }
    }
}


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

    ff1 = ref->m_Surface * ref->GetPadCount();
    ff2 = compare->m_Surface * compare->GetPadCount();
    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;
}


/**
 * Function PickModule
 * find the "best" module place
 * The criteria are:
 * - Maximum ratsnest 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->CalculateBoundingBox();
        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;
        pcbframe->SetMsgPanel( Module );
        pcbframe->build_ratsnest_module( Module );

        /* Calculate external ratsnest. */
        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;
}


/**
 * Function propagate
 * Used 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 neighbor 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 neighbor cells
 *  the previous cell on the same line and the previous cell on the same column.
 *
 *  This function 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 propagate()
{
    int       row, col;
    long      current_cell, old_cell_H;
    std::vector< long > pt_cell_V;
    int       nbpoints = 0;

#define NO_CELL_ZONE (HOLE | CELL_is_EDGE | CELL_is_ZONE)

    pt_cell_V.reserve( std::max( RoutingMatrix.m_Nrows, RoutingMatrix.m_Ncols ) );
    fill( pt_cell_V.begin(), pt_cell_V.end(), 0 );

    // Search from left to right and top to bottom.
    for( row = 0; row < RoutingMatrix.m_Nrows; row++ )
    {
        old_cell_H = 0;

        for( col = 0; col < RoutingMatrix.m_Ncols; col++ )
        {
            current_cell = RoutingMatrix.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) )
                {
                    RoutingMatrix.OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }

            pt_cell_V[col] = old_cell_H = current_cell;
        }
    }

    // Search from right to left and top to bottom/
    fill( pt_cell_V.begin(), pt_cell_V.end(), 0 );

    for( row = 0; row < RoutingMatrix.m_Nrows; row++ )
    {
        old_cell_H = 0;

        for( col = RoutingMatrix.m_Ncols - 1; col >= 0; col-- )
        {
            current_cell = RoutingMatrix.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) )
                {
                    RoutingMatrix.OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }

            pt_cell_V[col] = old_cell_H = current_cell;
        }
    }

    // Search from bottom to top and right to left.
    fill( pt_cell_V.begin(), pt_cell_V.end(), 0 );

    for( col = RoutingMatrix.m_Ncols - 1; col >= 0; col-- )
    {
        old_cell_H = 0;

        for( row = RoutingMatrix.m_Nrows - 1; row >= 0; row-- )
        {
            current_cell = RoutingMatrix.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) )
                {
                    RoutingMatrix.OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }

            pt_cell_V[row] = old_cell_H = current_cell;
        }
    }

    // Search from bottom to top and left to right.
    fill( pt_cell_V.begin(), pt_cell_V.end(), 0 );

    for( col = 0; col < RoutingMatrix.m_Ncols; col++ )
    {
        old_cell_H = 0;

        for( row = RoutingMatrix.m_Nrows - 1; row >= 0; row-- )
        {
            current_cell = RoutingMatrix.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) )
                {
                    RoutingMatrix.OrCell( row, col, BOTTOM, CELL_is_ZONE );
                    current_cell = CELL_is_ZONE;
                    nbpoints++;
                }
            }

            pt_cell_V[row] = old_cell_H = current_cell;
        }
    }

    return nbpoints;
}
