/*******************/
/* Locate element. */
/*******************/


#include "fctsys.h"

#include "common.h"
#include "pcbnew.h"
#include "class_board_design_settings.h"

#include "protos.h"



/* Locates a via point pX, pY
 * If layer < 0 will be located via whatever layer
 * If layer = 0 .. 15 Via will be located according to its type:
 * - Traverse: all layers
 * - = Blind between layers useful
 * - Blind idem
 * Entry: coord point of reference, layer
 * Output: NULL if not via
 * (* TRACK) address via
 */
TRACK* Locate_Via( BOARD* Pcb, const wxPoint& pos, int layer )
{
    TRACK* track;

    for( track = Pcb->m_Track;  track; track = track->Next() )
    {
        if( track->Type() != TYPE_VIA )
            continue;

        if( track->m_Start != pos )
            continue;

        if( track->GetState( BUSY | IS_DELETED ) )
            continue;

        if( layer < 0 )
            break;

        if( track->IsOnLayer( layer ) )
            break;
    }

    return track;
}


TRACK* Locate_Via_Area( TRACK* aStart, const wxPoint& pos, int layer )
{
    TRACK* track;

    for( track = aStart;   track;  track = track->Next() )
    {
        if( track->Type() != TYPE_VIA )
            continue;

        if( !track->HitTest(pos) )
            continue;

        if( track->GetState( BUSY | IS_DELETED ) )
            continue;

        if( layer < 0 )
            break;

        if( track->IsOnLayer( layer ) )
            break;
    }

    return track;
}


/* Locate the pad CONNECTED to a track
 * input: ptr_piste: pointer to the segment of track
 * Extr = flag = START -> beginning of the test segment
 * END -> end of the segment to be tested
 * Returns:
 * A pointer to the description of the pad if found.
 * NULL pointer if pad NOT FOUND
 */
D_PAD* Locate_Pad_Connecte( BOARD* Pcb, TRACK* ptr_piste, int extr )
{
    D_PAD*  ptr_pad = NULL;
    wxPoint ref_pos;

    int     masque_layer = g_TabOneLayerMask[ptr_piste->GetLayer()];

    if( extr == START )
    {
        ref_pos = ptr_piste->m_Start;
    }
    else
    {
        ref_pos = ptr_piste->m_End;
    }

    for( MODULE* module = Pcb->m_Modules;  module;  module = module->Next() )
    {
        ptr_pad = Locate_Pads( module, ref_pos, masque_layer );

        if( ptr_pad != NULL )
            break;
    }

    return ptr_pad;
}


/*
 * Locate a pad pointed to by the coordinates ref_pos.x, ref_pos.y
 * aLayerMask is allowed layers ( bitmap mask)
 * Returns:
 * Pointer to a pad if found or NULL
 */
D_PAD* Locate_Any_Pad( BOARD* Pcb, const wxPoint& ref_pos, int aLayerMask )
{
    D_PAD* pad = NULL;

    for( MODULE* module=Pcb->m_Modules; module && ( pad == NULL ); module = module->Next() )
    {
        if( aLayerMask )
            pad = Locate_Pads( module, ref_pos, aLayerMask );
        else
            pad = Locate_Pads( module, ref_pos, ALL_LAYERS );
    }

    return pad;
}


/* Locate the pad pointed to by the coordinate ref_pos.x,, ref_pos.y
 * Input:
 * - the footprint to test
 * - masque_layer layer(s) (bit_masque) which must be the pad
 * Returns:
 * A pointer to the pad if found or NULL
 */
D_PAD* Locate_Pads( MODULE* module, const wxPoint& ref_pos, int masque_layer )
{
    for( D_PAD* pt_pad = module->m_Pads;   pt_pad;   pt_pad = pt_pad->Next() )
    {
        /* ... and on the correct layer. */
        if( ( pt_pad->m_Masque_Layer & masque_layer ) == 0 )
            continue;

        if( pt_pad->HitTest( ref_pos ) )
            return pt_pad;
    }

    return NULL;
}


/**
 * Function Locate_Prefered_Module
 * locates a footprint by its bounding rectangle.  If several footprints
 * are possible, then the priority is: the closest on the active layer, then
 * closest.
 * The current mouse or cursor coordinates are grabbed from the active window
 * to perform hit-testing.
 * distance is calculated via manhattan distance from the center of the
 * bounding rectangle to the cursor position.
 *
 * @param aPcb The BOARD to search within.
 * @param aPosition Flag bits, tuning the search, see pcbnew.h
 * @param aActiveLayer Layer to test.
 * @param aVisibleOnly Search only the visible layers if true.
 * @param aIgnoreLocked Ignore locked modules when true.
 * @return MODULE* The best module or NULL if none.
 */
MODULE* Locate_Prefered_Module( BOARD* aPcb, const wxPoint& aPosition, int aActiveLayer,
                                bool aVisibleOnly, bool aIgnoreLocked )
{
    MODULE* pt_module;
    MODULE* module      = NULL;
    MODULE* Altmodule   = NULL;
    int     min_dim     = 0x7FFFFFFF;
    int     alt_min_dim = 0x7FFFFFFF;
    int     layer;

    pt_module = aPcb->m_Modules;

    for(  ;  pt_module;  pt_module = (MODULE*) pt_module->Next() )
    {
        // is the ref point within the module's bounds?
        if( !pt_module->HitTest( aPosition ) )
            continue;

        // if caller wants to ignore locked modules, and this one is locked, skip it.
        if( aIgnoreLocked && pt_module->IsLocked() )
            continue;

        /* Calculate priority: the priority is given to the layer of the
         * module and the copper layer if the module layer is indelible,
         * adhesive copper, a layer if cmp module layer is indelible,
         * adhesive component.
         */
        layer = pt_module->GetLayer();

        if( layer==ADHESIVE_N_BACK || layer==SILKSCREEN_N_BACK )
            layer = LAYER_N_BACK;
        else if( layer==ADHESIVE_N_FRONT || layer==SILKSCREEN_N_FRONT )
            layer = LAYER_N_FRONT;

        /* Test of minimum size to choosing the best candidate. */

        int offx = pt_module->m_BoundaryBox.m_Size.x / 2 +
            pt_module->m_BoundaryBox.m_Pos.x +
            pt_module->m_Pos.x;

        int offy = pt_module->m_BoundaryBox.m_Size.y / 2
            + pt_module->m_BoundaryBox.m_Pos.y
            + pt_module->m_Pos.y;

        //off x & offy point to the middle of the box.
        int dist = abs( aPosition.x - offx ) + abs( aPosition.y - offy );

        //int dist = MIN(lx, ly);  // to pick the smallest module (kinda
        // screwy with same-sized modules -- this is bad!)

        if( aActiveLayer == layer )
        {
            if( dist <= min_dim )
            {
                /* better footprint shown on the active layer */
                module  = pt_module;
                min_dim = dist;
            }
        }
        else if( aVisibleOnly && aPcb->IsModuleLayerVisible( layer ) )
        {
            if( dist <= alt_min_dim )
            {
                /* better footprint shown on other layers */
                Altmodule   = pt_module;
                alt_min_dim = dist;
            }
        }
    }

    if( module )
    {
        return module;
    }

    if( Altmodule )
    {
        return Altmodule;
    }

    return NULL;
}


/*
 * return true if the dist between p1 and p2 < max_dist
 * Currently in test (currently rasnest algos work only if p1 == p2)
*/
inline bool IsPointsAreNear(wxPoint & p1, wxPoint & p2, int max_dist)
{
#if 0   // Do not change it: does not work
{
int dist;
    dist = abs(p1.x - p2.x) + abs (p1.y - p2.y);
    dist *= 7;
    dist /= 10;
    if ( dist < max_dist ) return true;
}
#else
    if ( p1 == p2 ) return true;
#endif
    return false;
}


/** Search for the track (or via) segment which is connected to the track
 *  segment PtRefSegm
 *  if extr == START, the starting track segment PtRefSegm is used to locate
 *  a connected segment
 *  if extr == END, the ending track segment PtRefSegm is used
 *  The test connection consider only end track segments
 *
 *  Search is made from  pt_base to pt_lim (in the track linked list)
 *  if pt_lim == NULL,  the search  is made from  pt_base to the end of list
 *
 *  In order to have a fast computation time:
 *  a first search is made considering only the +/- 50 next door neighbor
 *  of PtRefSegm.
 *  if no track is found : the entire list is tested
 *
 *  @param PtRefSegm = reference segment
 *  @param pt_base = lower limit for search
 *  @param pt_lim = upper limit for search (can be NULL)
 *  @param extr = START or END = end of ref track segment to use in tests
 */
TRACK* Locate_Piste_Connectee( TRACK* PtRefSegm, TRACK* pt_base, TRACK* pt_lim, int extr )
{
    const int NEIGHTBOUR_COUNT_MAX = 50;

    TRACK*  PtSegmB, * PtSegmN;
    int     Reflayer;
    wxPoint pos_ref;
    int     ii;
    int     max_dist;

    if( extr == START )
        pos_ref = PtRefSegm->m_Start;
    else
        pos_ref = PtRefSegm->m_End;

    Reflayer = PtRefSegm->ReturnMaskLayer();

    PtSegmB = PtSegmN = PtRefSegm;

    for( ii = 0; ii < NEIGHTBOUR_COUNT_MAX; ii++ )
    {
        if( (PtSegmN == NULL) && (PtSegmB == NULL) )
            break;

        if( PtSegmN )
        {
            if( PtSegmN->GetState( BUSY | IS_DELETED ) )
                goto suite;

            if( PtSegmN == PtRefSegm )
                goto suite;

            /* max_dist is the max distance between 2 track ends which
             * ensure a copper continuity */
            max_dist = (PtSegmN->m_Width + PtRefSegm->m_Width)/2;

            if( IsPointsAreNear(pos_ref, PtSegmN->m_Start, max_dist) )
            {
                if( Reflayer & PtSegmN->ReturnMaskLayer() )
                    return PtSegmN;
            }

            if( IsPointsAreNear(pos_ref, PtSegmN->m_End, max_dist) )
            {
                if( Reflayer & PtSegmN->ReturnMaskLayer() )
                    return PtSegmN;
            }
suite:
            if( PtSegmN == pt_lim )
                PtSegmN = NULL;
            else
                PtSegmN =  PtSegmN->Next();
        }

        if( PtSegmB )
        {
            if( PtSegmB->GetState( BUSY | IS_DELETED ) )
                goto suite1;

            if( PtSegmB == PtRefSegm )
                goto suite1;

            max_dist = (PtSegmB->m_Width + PtRefSegm->m_Width)/2;

            if( IsPointsAreNear(pos_ref, PtSegmB->m_Start, max_dist) )
            {
                if( Reflayer & PtSegmB->ReturnMaskLayer() )
                    return PtSegmB;
            }

            if( IsPointsAreNear(pos_ref, PtSegmB->m_End, max_dist) )
            {
                if( Reflayer & PtSegmB->ReturnMaskLayer() )
                    return PtSegmB;
            }
suite1:
            if( PtSegmB == pt_base )
                PtSegmB = NULL;
            else if( PtSegmB->Type() != TYPE_PCB )
                PtSegmB =  PtSegmB->Back();
            else
                PtSegmB = NULL;
        }
    }

    /* General search. */
    for( PtSegmN = pt_base; PtSegmN != NULL; PtSegmN =  PtSegmN->Next() )
    {
        if( PtSegmN->GetState( IS_DELETED | BUSY ) )
        {
            if( PtSegmN == pt_lim )
                break;

            continue;
        }
        if( PtSegmN == PtRefSegm )
        {
            if( PtSegmN == pt_lim )
                break;

            continue;
        }

        max_dist = ( PtSegmN->m_Width + PtRefSegm->m_Width ) / 2;

        if( IsPointsAreNear( pos_ref, PtSegmN->m_Start, max_dist ) )
        {
            if( Reflayer & PtSegmN->ReturnMaskLayer() )
                return PtSegmN;
        }

        if( IsPointsAreNear( pos_ref, PtSegmN->m_End, max_dist ) )
        {
            if( Reflayer & PtSegmN->ReturnMaskLayer() )
                return PtSegmN;
        }
        if( PtSegmN == pt_lim )
            break;
    }

    return NULL;
}


/*
 * 1 - Locate segment of track leading from the mouse.
 * 2 - Locate segment of track point by point
 * ref_pos.x, ref_pos.y.r
 *
 * The search begins to address start_adresse
 */
TRACK* Locate_Pistes( BOARD* aPcb, TRACK* start_adresse, const wxPoint& ref_pos, int MasqueLayer )
{
    for( TRACK* track = start_adresse;   track;  track =  track->Next() )
    {
        int layer = track->GetLayer();

        if( track->GetState( BUSY | IS_DELETED ) )
        {
            // D( printf( "track %p is BUSY | IS_DELETED. BUSY=%d IS_DELETED=%d\n",
            //            track, track->GetState( BUSY ),
            //            track->GetState( IS_DELETED ) );)
            continue;
        }

        if( aPcb->GetBoardDesignSettings()->IsLayerVisible( layer ) == false )
            continue;

        if( track->Type() == TYPE_VIA ) /* VIA encountered. */
        {
            if( track->HitTest( ref_pos ) )
                return track;
        }
        else
        {
            if( (g_TabOneLayerMask[layer] & MasqueLayer) == 0 )
                continue;   /* Segments on different layers. */

            if( track->HitTest( ref_pos ) )
                return track;
        }
    }

    return NULL;
}


/*
 * 1 - Locate zone area by the mouse.
 * 2 - Locate zone area by point
 * def_pos.x, ref_pos.y.r
 *
 * If layer == -1, tst layer is not
 *
 * The search begins to address start_adresse
 */
TRACK* Locate_Zone( TRACK* start_adresse, const wxPoint& ref_pos, int layer )
{
    for( TRACK* Zone = start_adresse;  Zone;   Zone =  Zone->Next() )
    {
        if( (layer != -1) && (Zone->GetLayer() != layer) )
            continue;

        if( Zone->HitTest( ref_pos ) )
            return Zone;
    }

    return NULL;
}


/* Find the pad center px, py,
 * The layer INDICATED BY masque_layer (bitwise)
 * (Runway end)
 * The list of pads must already exist.
 *
 * Returns:
 *   NULL if no pad located.
 *   Pointer to the structure corresponding descr_pad if pad found
 * (Good position and good layer).
 */
D_PAD* Fast_Locate_Pad_Connecte( BOARD* Pcb, const wxPoint& ref_pos, int masque_layer )
{
    for( unsigned i=0; i<Pcb->GetPadsCount();  ++i )
    {
        D_PAD* pad = Pcb->m_NetInfo->GetPad(i);

        if( pad->m_Pos != ref_pos )
            continue;

        /* Pad found, it must be on the correct layer */
        if( pad->m_Masque_Layer & masque_layer )
            return pad;
    }

    return NULL;
}


/* Locate segment with one end that coincides with the point x, y
 * Data on layers by masklayer
 * Research is done to address start_adr has end_adr
 * If end_adr = NULL, end search list
 * The segments of track marks with the flag are not IS_DELETED or taken
 * into account
 */
TRACK* Fast_Locate_Piste( TRACK* start_adr, TRACK* end_adr, const wxPoint& ref_pos, int MaskLayer )
{
    TRACK* PtSegm;

    if( start_adr == NULL )
        return NULL;

    for( PtSegm = start_adr; PtSegm != NULL; PtSegm =  PtSegm->Next() )
    {
        if( PtSegm->GetState( IS_DELETED | BUSY ) == 0 )
        {
            if( ref_pos == PtSegm->m_Start )
            {
                if( MaskLayer & PtSegm->ReturnMaskLayer() )
                    return PtSegm;
            }

            if( ref_pos == PtSegm->m_End )
            {
                if( MaskLayer & PtSegm->ReturnMaskLayer() )
                    return PtSegm;
            }
        }

        if( PtSegm == end_adr )
            break;
    }

    return NULL;
}


/* Locates via through the point x, y, on layer data by masklayer.
 * Search is done to address start_adr has end_adr.
 * If end_adr = NULL, end search list
 * Vias whose parameter has the State or IS_DELETED bit BUSY = 1 are ignored
 */
TRACK* Fast_Locate_Via( TRACK* start_adr, TRACK* end_adr, const wxPoint& pos, int MaskLayer )
{
    TRACK* PtSegm;

    for( PtSegm = start_adr; PtSegm != NULL; PtSegm = PtSegm->Next() )
    {
        if( PtSegm->Type() == TYPE_VIA )
        {
            if( pos == PtSegm->m_Start )
            {
                if( PtSegm->GetState( BUSY | IS_DELETED ) == 0 )
                {
                    if( MaskLayer & PtSegm->ReturnMaskLayer() )
                        return PtSegm;
                }
            }
        }

        if( PtSegm == end_adr )
            break;
    }

    return NULL;
}
