/************************************************/
/* Locate items at the current cursor position. */
/************************************************/

#include "fctsys.h"
#include "common.h"
#include "class_drawpanel.h"

#include "pcbnew.h"
#include "gerbview.h"
#include "trigo.h"
#include "protos.h"


int ux0, uy0, dx, dy, spot_cX, spot_cY;


static TRACK*       Locate_Zone( TRACK* start_adresse, int layer, int typeloc );
static TRACK*       Locate_Zone( TRACK* start_adresse, wxPoint ref, int layer );
static TRACK*       Locate_Pistes( TRACK* start_adresse,
                                   int    Layer,
                                   int    typeloc );
static TRACK*       Locate_Pistes( TRACK*  start_adresse,
                                   wxPoint ref,
                                   int     Layer );
static DRAWSEGMENT* Locate_Segment_Pcb( BOARD* Pcb, int typeloc );
static TEXTE_PCB*   Locate_Texte_Pcb( TEXTE_PCB* pt_txt_pcb, int typeloc );
static int          distance( int seuil );


/* Macro for calculating the coordinates of the cursor position.
 */
#define SET_REF_POS( ref ) if( typeloc == CURSEUR_ON_GRILLE ) \
    { ref = ActiveScreen->m_Curseur; } \
    else { ref = ActiveScreen->m_MousePosition; }


/* Display the character of the localized STRUCTURE and return a pointer
 * to it.
 */
BOARD_ITEM* WinEDA_GerberFrame::Locate( int typeloc )
{
    TEXTE_PCB*   pt_texte_pcb;
    TRACK*       Track, * TrackLocate;
    DRAWSEGMENT* DrawSegm;
    int          layer;

    /* Locate tracks and vias, with priority to vias */
    layer = GetScreen()->m_Active_Layer;
    Track = Locate_Pistes( GetBoard()->m_Track, -1, typeloc );
    if( Track != NULL )
    {
        TrackLocate = Track;

        while( ( TrackLocate = Locate_Pistes( TrackLocate,
                                              layer, typeloc ) ) != NULL )
        {
            Track = TrackLocate;
            if( TrackLocate->Type() == TYPE_VIA )
                break;
            TrackLocate = TrackLocate->Next();
        }

        Track->DisplayInfo( this );
        return Track;
    }


    pt_texte_pcb = Locate_Texte_Pcb(
         (TEXTE_PCB*) GetBoard()->m_Drawings.GetFirst(), typeloc );

    if( pt_texte_pcb )
    {
        pt_texte_pcb->DisplayInfo( this );
        return pt_texte_pcb;
    }

    if( ( DrawSegm = Locate_Segment_Pcb( GetBoard(), typeloc ) ) != NULL )
    {
        return DrawSegm;
    }

    if( ( TrackLocate = Locate_Zone( GetBoard()->m_Zone,
                                     GetScreen()->m_Active_Layer,
                                     typeloc ) ) != NULL )
    {
        TrackLocate->DisplayInfo( this );
        return TrackLocate;
    }

    MsgPanel->EraseMsgBox();
    return NULL;
}


/* Locate of segments of pcb edge or draw as active layer.
 * Returns:
 *   Pointer to START segment if found
 *   NULL if nothing found
 */
DRAWSEGMENT* Locate_Segment_Pcb( BOARD* Pcb, int typeloc )
{
    BOARD_ITEM*  PtStruct;
    DRAWSEGMENT* pts;
    wxPoint      ref;
    PCB_SCREEN*  screen = (PCB_SCREEN*) ActiveScreen;

    SET_REF_POS( ref );

    PtStruct = Pcb->m_Drawings;
    for( ; PtStruct != NULL; PtStruct = PtStruct->Next() )
    {
        if( PtStruct->Type() != TYPE_DRAWSEGMENT )
            continue;
        pts = (DRAWSEGMENT*) PtStruct;
        ux0 = pts->m_Start.x;
        uy0 = pts->m_Start.y;

        dx = pts->m_End.x - ux0;
        dy = pts->m_End.y - uy0;
        spot_cX = ref.x - ux0;
        spot_cY = ref.y - uy0;

        if( pts->GetLayer() != screen->m_Active_Layer )
            continue;

        if( ( pts->m_Shape == S_CIRCLE ) || ( pts->m_Shape == S_ARC ) )
        {
            int rayon, dist, StAngle, EndAngle, MouseAngle;
            rayon = (int) hypot( (double) (dx), (double) (dy) );
            dist  = (int) hypot( (double) (spot_cX), (double) (spot_cY) );
            if( abs( rayon - dist ) <= ( pts->m_Width / 2 ) )
            {
                if( pts->m_Shape == S_CIRCLE )
                    return pts;

                MouseAngle = (int) ArcTangente( spot_cY, spot_cX );
                StAngle    = (int) ArcTangente( dy, dx );
                EndAngle   = StAngle + pts->m_Angle;

                if( EndAngle > 3600 )
                {
                    StAngle -= 3600; EndAngle -= 3600;
                }
                if( ( MouseAngle >= StAngle ) && ( MouseAngle <= EndAngle ) )
                    return pts;
            }
        }
        else
        {
            if( distance( pts->m_Width / 2 ) )
                return pts;
        }
    }

    return NULL;
}


/*
 * 1 - Locate segment of track at current cursor position.
 * 2 - Locate  segment of track point by point.
 * Ref_pX, ref_pY.r
 *
 * The search begins to address start_adresse
 */
TRACK* Locate_Pistes( TRACK* start_adresse, int Layer, int typeloc )
{
    wxPoint ref;

    SET_REF_POS( ref );

    return Locate_Pistes( start_adresse, ref, Layer );
}


TRACK* Locate_Pistes( TRACK* start_adresse, wxPoint ref, int Layer )
{
    TRACK* Track;
    int    l_piste;             /* half-width of the track */

    for( Track = start_adresse; Track != NULL; Track = Track->Next() )
    {
        if( Track->GetState( BUSY | DELETED ) )
            continue;
        /* Calculate coordinates of the test segment. */
        l_piste = Track->m_Width >> 1;
        ux0     = Track->m_Start.x;
        uy0     = Track->m_Start.y;
        dx = Track->m_End.x;
        dy = Track->m_End.y;

        dx -= ux0;
        dy -= uy0;
        spot_cX = ref.x - ux0;
        spot_cY = ref.y - uy0;

        if( Track->Type() == TYPE_VIA )
        {
            if( ( abs( spot_cX ) <= l_piste ) && ( abs( spot_cY ) <=l_piste ) )
            {
                return Track;
            }
            continue;
        }

        if( Layer >= 0 )
            if( Track->GetLayer() != Layer )
                continue;
        if( distance( l_piste ) )
            return Track;
    }

    return NULL;
}


/*
 * Locate zone area at the cursor position.
 *
 * The search begins to address start_adresse
 */
TRACK* Locate_Zone( TRACK* start_adresse, int layer, int typeloc )
{
    wxPoint ref;

    SET_REF_POS( ref );

    return Locate_Zone( start_adresse, ref, layer );
}


/*
 * Locate zone area at point.
 *
 * The search begins to address start_adresse
 */
TRACK* Locate_Zone( TRACK* start_adresse, wxPoint ref, int layer )
{
    TRACK* Zone;
    int    l_segm;

    for( Zone = start_adresse; Zone != NULL; Zone = Zone->Next() )
    {
        l_segm = Zone->m_Width >> 1;
        ux0    = Zone->m_Start.x;
        uy0    = Zone->m_Start.y;
        dx     = Zone->m_End.x;
        dy     = Zone->m_End.y;

        dx -= ux0;
        dy -= uy0;
        spot_cX = ref.x - ux0;
        spot_cY = ref.y - uy0;

        if( ( layer != -1 ) && ( Zone->GetLayer() != layer ) )
            continue;
        if( distance( l_segm ) )
            return Zone;
    }

    return NULL;
}


/* Location of text on the PCB:
 * INPUT: char pointer to the beginning of the search area
 * Return: pointer to the text description located.
 */
TEXTE_PCB* Locate_Texte_Pcb( TEXTE_PCB* pt_txt_pcb, int typeloc )
{
    int             angle;
    EDA_BaseStruct* PtStruct;
    wxPoint         ref;

    SET_REF_POS( ref );
    PtStruct = (EDA_BaseStruct*) pt_txt_pcb;
    for( ; PtStruct != NULL; PtStruct = PtStruct->Next() )
    {
        if( PtStruct->Type() != TYPE_TEXTE )
            continue;
        pt_txt_pcb = (TEXTE_PCB*) PtStruct;

        angle = pt_txt_pcb->m_Orient;
        ux0   = pt_txt_pcb->m_Pos.x;
        uy0   = pt_txt_pcb->m_Pos.y;
        dx    = ( pt_txt_pcb->m_Size.x * pt_txt_pcb->GetLength() ) / 2;
        dy    = pt_txt_pcb->m_Size.y / 2;

        dx *= 13;
        dx /= 9;    /* Character for factor 13/9. */

        /* Cursor in the rectangle around the center.  */
        spot_cX = ref.x - ux0;
        spot_cY = ref.y - uy0;
        RotatePoint( &spot_cX, &spot_cY, -angle );
        if( ( abs( spot_cX ) <= abs( dx ) ) && ( abs( spot_cY ) <= abs( dy ) ) )
            return pt_txt_pcb;
    }

    return NULL;
}


/*
 * Calculate the distance from the cursor to a line segment:
 * (Track, edge, contour module ..
 * Returns:
 * 0 if distance > threshold
 * 1 if distance <= threshold
 * Variables used (must be initialized before use, and
 * are brought to the mark center on the origin of the segment)
 * dx, dy = coord of extremity segment.
 * spot_cX, spot_cY = coord of mouse cursor
 * Search 4 cases:
 *   Horizontal segment
 *   Vertical segment
 *   Segment 45
 *   Any segment
 */
int distance( int seuil )
{
    int cXrot, cYrot, segX, segY;
    int pointX, pointY;

    segX = dx;
    segY = dy;
    pointX = spot_cX;
    pointY = spot_cY;

    /* Reroute coordinate for the segment in 1st quadrant (coord> = 0). */
    if( segX < 0 )   /* Set > 0 if symmetrical about the axis Y. */
    {
        segX   = -segX;
        pointX = -pointX;
    }
    if( segY < 0 )   /* Set > 0 if symmetrical about the axis X. */
    {
        segY   = -segY;
        pointY = -pointY;
    }


    if( segY == 0 ) /* Horizontal track. */
    {
        if( abs( pointY ) <= seuil )
        {
            if( ( pointX >= 0 ) && ( pointX <= segX ) )
                return 1;

            if( ( pointX < 0 ) && ( pointX >= -seuil ) )
            {
                if( ( ( pointX * pointX ) + ( pointY * pointY ) ) <=
                    ( seuil * seuil ) )
                    return 1;
            }
            if( ( pointX > segX ) && ( pointX <= ( segX + seuil ) ) )
            {
                if( ( ( ( pointX - segX ) * ( pointX - segX ) ) +
                      ( pointY * pointY ) ) <= ( seuil * seuil ) )
                    return 1;
            }
        }
    }
    else if( segX == 0 ) /* Vertical track. */
    {
        if( abs( pointX ) <= seuil )
        {
            if( ( pointY >= 0 ) && ( pointY <= segY ) )
                return 1;
            if( ( pointY < 0 ) && ( pointY >= -seuil ) )
            {
                if( ( (pointY * pointY ) + ( pointX * pointX ) ) <=
                   ( seuil * seuil ) )
                    return 1;
            }
            if( ( pointY > segY ) && ( pointY <= ( segY + seuil ) ) )
            {
                if( ( ( ( pointY - segY ) * ( pointY - segY ) ) +
                      ( pointX * pointX ) ) <= ( seuil * seuil ) )
                    return 1;
            }
        }
    }
    else if( segX == segY )    /* 45 degree track. */
    {
        /* You spin axes of 45 degrees. mouse was then
         * coord: x1 = x * y * cos45 + sin45
         * y1 = y * cos45 - sin45 x *
         * And the segment of track is horizontal.
         * coord recalculation of the mouse (sin45 = cos45 = .707 = 7 / 10
         * Note: sin or cos45 = .707, and when recalculating coord
         * dX45 and dy45, lect coeff .707 is neglected, dx and dy are both
         * actually .707
         * too big. (security hole too small)
         * spot_cX *, Y * must be by .707 * .707 = 0.5
         */

        cXrot = (pointX + pointY) >> 1;
        cYrot = (pointY - pointX) >> 1;

        /* Recalculate coordinates of extremity segment, which will be vertical
         * following the orientation of axes on the screen: DX45 = pointx
         * (or pointy) and 1.414 is actually greater, and dy45 = 0
         *
         * Threshold should be .707 to reflect the difference in coeff dx, dy
         */
        seuil *= 7; seuil /= 10;
        if( abs( cYrot ) <= seuil )
        {
            if( ( cXrot >= 0 ) && ( cXrot <= segX ) )
                return 1;

            if( ( cXrot < 0 ) && ( cXrot >= -seuil ) )
            {
                if( ( ( cXrot * cXrot ) + ( cYrot * cYrot ) )
                    <= ( seuil * seuil ) )
                    return 1;
            }
            if( ( cXrot > segX ) && ( cXrot <= ( segX + seuil ) ) )
            {
                if( ( ( ( cXrot - segX ) * ( cXrot - segX ) ) +
                      ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return 1;
            }
        }
    }
    else    /* Any orientation. */
    {
        /* There is a change of axis (rotation), so that the segment
         * track is horizontal in the new reference, */
        int angle;

        angle = (int) ( atan2( (double) segY, (double) segX ) * 1800 / M_PI);
        cXrot = pointX;
        cYrot = pointY;
        RotatePoint( &cXrot, &cYrot, angle );   /* Rotate test point. */
        RotatePoint( &segX, &segY, angle );     /* Rotate segment. */

        /* The track is horizontal, following the changes to coordinate
         * axis and, therefore segX = length of segment
         */

        if( abs( cYrot ) <= seuil )
        {
            if( ( cXrot >= 0 ) && ( cXrot <= segX ) )
                return 1;

            if( ( cXrot < 0 ) && ( cXrot >= -seuil ) )
            {
                if( ( ( cXrot * cXrot ) + ( cYrot * cYrot ) )
                    <= ( seuil * seuil ) )
                    return 1;
            }
            if( ( cXrot > segX ) && ( cXrot <= ( segX + seuil ) ) )
            {
                if( ( ( ( cXrot - segX ) * ( cXrot - segX ) ) +
                      ( cYrot * cYrot ) ) <= ( seuil * seuil ) )
                    return 1;
            }
        }
    }

    return 0;
}