/******************************************************/
/* Routines for locating an element of a schematic.   */
/******************************************************/

#include "fctsys.h"
#include "common.h"
#include "program.h"
#include "trigo.h"
#include "macros.h"

#include "general.h"
#include "class_marker_sch.h"
#include "protos.h"
#include "class_library.h"


static bool DrawStructInBox( int x1, int y1, int x2, int y2,
                             SCH_ITEM* DrawStruct );

static SCH_ITEM* LastSnappedStruct = NULL;
static bool IsBox1InBox2( int StartX1, int StartY1, int EndX1, int EndY1,
                          int StartX2, int StartY2, int EndX2, int EndY2 );
static bool SnapPoint2( const wxPoint& aPosRef, int SearchMask,
                        SCH_ITEM* DrawList, double aScaleFactor );


/**
 * Search the smaller (considering its area) component under the mouse
 * cursor or the pcb cursor
 *
 * If more than 1 component is found, a pointer to the smaller component is
 * returned
 */
SCH_COMPONENT* LocateSmallestComponent( SCH_SCREEN* Screen )
{
    SCH_COMPONENT* component = NULL, * lastcomponent = NULL;
    SCH_ITEM*      DrawList;
    EDA_Rect       BoundaryBox;
    float          sizeref = 0, sizecurr;

    DrawList = Screen->EEDrawList;

    while( DrawList )
    {
        if( ( SnapPoint2( Screen->m_MousePosition, LIBITEM,
                          DrawList, Screen->GetZoom() ) ) == FALSE )
        {
            if( ( SnapPoint2( Screen->m_Curseur, LIBITEM,
                              DrawList, Screen->GetScalingFactor() ) ) == FALSE )
                break;
        }
        component = (SCH_COMPONENT*) LastSnappedStruct;
        DrawList  = component->Next();
        if( lastcomponent == NULL )  // First time a component is located
        {
            lastcomponent = component;
            BoundaryBox   = lastcomponent->GetBoundaryBox();
            sizeref = ABS( (float) BoundaryBox.GetWidth() *
                           BoundaryBox.GetHeight() );
        }
        else
        {
            BoundaryBox = component->GetBoundaryBox();
            sizecurr    = ABS( (float) BoundaryBox.GetWidth() *
                               BoundaryBox.GetHeight() );
            if( sizeref > sizecurr )   // a smallest component is found
            {
                sizeref = sizecurr;
                lastcomponent = component;
            }
        }
    }

    return lastcomponent;
}


/******************************************************************************/
SCH_ITEM* PickStruct( const wxPoint& refpos,
                      BASE_SCREEN*   screen,
                      int            SearchMask )
{
/******************************************************************************/
/* Search an item at pos refpos
 *  SearchMask = (bitwise OR):
 *  LIBITEM
 *  WIREITEM
 *  BUSITEM
 *  RACCORDITEM
 *  JUNCTIONITEM
 *  DRAWITEM
 *  TEXTITEM
 *  LABELITEM
 *  SHEETITEM
 *  MARKERITEM
 *  NOCONNECTITEM
 *  SEARCH_PINITEM
 *  SHEETLABELITEM
 *  FIELDCMPITEM
 *
 *  if EXCLUDE_WIRE_BUS_ENDPOINTS is set, in wire or bus search and locate,
 *  start and end points are not included in search
 *  if WIRE_BUS_ENDPOINTS_ONLY is set, in wire or bus search and locate,
 *  only start and end points are included in search
 *
 *
 *  Return:
 *          pointer on item found or NULL
 *
 */
    bool Snapped;

    if( screen == NULL || screen->EEDrawList == NULL )
        return NULL;

    if( ( Snapped = SnapPoint2( refpos, SearchMask,
                                screen->EEDrawList,
                                screen->GetScalingFactor() ) ) != FALSE )
    {
        return LastSnappedStruct;
    }
    return NULL;
}


/** Function PickStruct
 * Search items in a block
 * @return items count
 * @param aBlock a BLOCK_SELECTOR that gives the search area boundary
 * list of items is stored in aBlock
 */
int PickItemsInBlock( BLOCK_SELECTOR& aBlock, BASE_SCREEN* aScreen )
{
    int x, y, OrigX, OrigY;
    int itemcount = 0;

    if( aScreen == NULL )
        return itemcount;

    OrigX = aBlock.GetX();
    OrigY = aBlock.GetY();
    x     = aBlock.GetRight();
    y     = aBlock.GetBottom();

    if( x < OrigX )
        EXCHG( x, OrigX );
    if( y < OrigY )
        EXCHG( y, OrigY );

    ITEM_PICKER picker;
    SCH_ITEM*   DrawStruct = aScreen->EEDrawList;
    for( ; DrawStruct != NULL; DrawStruct = DrawStruct->Next() )
    {
        if( DrawStructInBox( OrigX, OrigY, x, y, DrawStruct ) )
        {
            /* Put this structure in the picked list: */
            picker.m_PickedItem     = DrawStruct;
            picker.m_PickedItemType = DrawStruct->Type();
            aBlock.PushItem( picker );
            itemcount++;
        }
    }

    return itemcount;
}


/*****************************************************************************
* Routine to search all objects for the closest point to a given point, in   *
* drawing space, and snap it to that points if closer than SnapDistance.     *
* Note we use L1 norm as distance measure, as it is the fastest.             *
* This routine updates LastSnappedStruct to the last object used in to snap  *
* a point. This variable is global to this module only (see above).          *
* The routine returns TRUE if point was snapped.                             *
*****************************************************************************/
bool SnapPoint2( const wxPoint& aPosRef, int SearchMask,
                 SCH_ITEM* DrawList, double aScaleFactor )
{
    for( ; DrawList != NULL; DrawList = DrawList->Next() )
    {
        int hitminDist = MAX( g_DrawDefaultLineThickness, 3 );
        switch( DrawList->Type() )
        {
        case DRAW_POLYLINE_STRUCT_TYPE:
            #undef  STRUCT
            #define STRUCT ( (DrawPolylineStruct*) DrawList )
            if( !( SearchMask & (DRAWITEM | WIREITEM | BUSITEM) ) )
                break;

            for( unsigned i = 0; i < STRUCT->GetCornerCount() - 1; i++ )
            {
                if( TestSegmentHit( aPosRef, STRUCT->m_PolyPoints[i],
                                    STRUCT->m_PolyPoints[i + 1], hitminDist ) )
                {
                    LastSnappedStruct = DrawList;
                    return TRUE;
                }
            }

            break;

        case DRAW_SEGMENT_STRUCT_TYPE:
            #undef  STRUCT
            #define STRUCT ( (EDA_DrawLineStruct*) DrawList )
            if( !( SearchMask & (DRAWITEM | WIREITEM | BUSITEM) ) )
                break;

            if( TestSegmentHit( aPosRef, STRUCT->m_Start, STRUCT->m_End, 0 ) )
            {
                if( ( (SearchMask & DRAWITEM)
                     && (STRUCT->GetLayer() == LAYER_NOTES) )
                   || ( (SearchMask & WIREITEM)
                       && (STRUCT->GetLayer() == LAYER_WIRE) )
                   || ( (SearchMask & BUSITEM)
                       && (STRUCT->GetLayer() == LAYER_BUS) )
                    )
                {
                    if( SearchMask & EXCLUDE_WIRE_BUS_ENDPOINTS )
                    {
                        if( aPosRef == STRUCT->m_Start || aPosRef ==
                            STRUCT->m_End )
                            break;
                    }

                    if( SearchMask & WIRE_BUS_ENDPOINTS_ONLY )
                    {
                        if( !STRUCT->IsOneEndPointAt( aPosRef ) )
                            break;
                    }

                    LastSnappedStruct = DrawList;
                    return TRUE;
                }
            }
            break;


        case DRAW_BUSENTRY_STRUCT_TYPE:
            #undef  STRUCT
            #define STRUCT ( (DrawBusEntryStruct*) DrawList )
            if( !( SearchMask & (RACCORDITEM) ) )
                break;

            if( TestSegmentHit( aPosRef, STRUCT->m_Pos, STRUCT->m_End(),
                                hitminDist ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;

        case DRAW_JUNCTION_STRUCT_TYPE:
            #undef  STRUCT
            #define STRUCT ( (DrawJunctionStruct*) DrawList )
            if( !(SearchMask & JUNCTIONITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;

        case DRAW_NOCONNECT_STRUCT_TYPE:
            #undef  STRUCT
            #define STRUCT ( (DrawNoConnectStruct*) DrawList )
            if( !(SearchMask & NOCONNECTITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;

        case TYPE_MARKER_SCH:
        {
            #undef  STRUCT
            #define STRUCT ( (MARKER_SCH*) DrawList )
            if( !(SearchMask & MARKERITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;
        }

        case TYPE_SCH_TEXT:
            #undef  STRUCT
            #define STRUCT ( (SCH_TEXT*) DrawList )
            if( !( SearchMask & TEXTITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;


        case TYPE_SCH_LABEL:
        case TYPE_SCH_GLOBALLABEL:
        case TYPE_SCH_HIERLABEL:
            #undef  STRUCT
            #define STRUCT ( (SCH_TEXT*) DrawList ) // SCH_TEXT is the base
                                                    // class of these labels
            if( !(SearchMask & LABELITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;

        case TYPE_SCH_COMPONENT:
            if( !( SearchMask & (LIBITEM | FIELDCMPITEM) ) )
                break;

            if( SearchMask & FIELDCMPITEM )
            {
                SCH_COMPONENT* DrawLibItem = (SCH_COMPONENT*) DrawList;
                for( int i = REFERENCE; i < DrawLibItem->GetFieldCount(); i++ )
                {
                    SCH_CMP_FIELD* field = DrawLibItem->GetField( i );

                    if( field->m_Attributs & TEXT_NO_VISIBLE )
                        continue;

                    if( field->IsVoid() )
                        continue;

                    EDA_Rect BoundaryBox = field->GetBoundaryBox();
                    if( BoundaryBox.Inside( aPosRef ) )
                    {
                        LastSnappedStruct = field;
                        return TRUE;
                    }
                }
            }
            else
            {
                #undef  STRUCT
                #define STRUCT ( (SCH_COMPONENT*) DrawList )
                EDA_Rect BoundaryBox = STRUCT->GetBoundaryBox();
                if( BoundaryBox.Inside( aPosRef ) )
                {
                    LastSnappedStruct = DrawList;
                    return TRUE;
                }
            }
            break;

        case DRAW_SHEET_STRUCT_TYPE:
            #undef STRUCT
            #define STRUCT ( (SCH_SHEET*) DrawList )
            if( !(SearchMask & SHEETITEM) )
                break;
            if( STRUCT->HitTest( aPosRef ) )
            {
                LastSnappedStruct = DrawList;
                return TRUE;
            }
            break;

        default:
        {
            wxString msg;
            msg.Printf( wxT( "SnapPoint2() error: unexpected struct type %d (" ),
                        DrawList->Type() );
            msg << DrawList->GetClass() << wxT( ")" );
            wxMessageBox( msg );
            break;
        }
        }
    }

    return FALSE;
}


/*****************************************************************************
* Routine to test if an object has non empty intersection with the box       *
* defined by x1/y1 and x2/y2 (x1 < x2, y1 < y2), and return TRUE if so. This *
* routine is used to pick all points in a given box.                         *
*****************************************************************************/
bool DrawStructInBox( int x1, int y1, int x2, int y2, SCH_ITEM* DrawStruct )
{
    int xt1, yt1, xt2, yt2;
    int dx, dy;
    wxString msg;

    switch( DrawStruct->Type() )
    {
    case DRAW_POLYLINE_STRUCT_TYPE:
        #undef  STRUCT
        #define STRUCT ( (DrawPolylineStruct*) DrawStruct )
        for( unsigned i = 0; i < STRUCT->GetCornerCount(); i++ )
        {
            if( STRUCT->m_PolyPoints[i].x >= x1
                && STRUCT->m_PolyPoints[i].x <= x2
                && STRUCT->m_PolyPoints[i].y >= y1
                && STRUCT->m_PolyPoints[i].y <=y2 )
                return TRUE;
        }

        break;

    case DRAW_SEGMENT_STRUCT_TYPE:
        #undef STRUCT
        #define STRUCT ( (EDA_DrawLineStruct*) DrawStruct )
        if( STRUCT->m_Start.x >= x1 && STRUCT->m_Start.x <= x2
            && STRUCT->m_Start.y >= y1 && STRUCT->m_Start.y <=y2 )
            return TRUE;
        if( (STRUCT->m_End.x >= x1) && (STRUCT->m_End.x <= x2)
           && (STRUCT->m_End.y >= y1) && (STRUCT->m_End.y <=y2) )
            return TRUE;
        break;

    case DRAW_BUSENTRY_STRUCT_TYPE:
        #undef STRUCT
        #define STRUCT ( (DrawBusEntryStruct*) DrawStruct )
        if( STRUCT->m_Pos.x >= x1 && STRUCT->m_Pos.x <= x2
            && STRUCT->m_Pos.y >= y1 && STRUCT->m_Pos.y <=y2 )
            return TRUE;
        if( (STRUCT->m_End().x >= x1) && ( STRUCT->m_End().x <= x2)
           && ( STRUCT->m_End().y >= y1) && ( STRUCT->m_End().y <=y2) )
            return TRUE;
        break;

    case DRAW_JUNCTION_STRUCT_TYPE:
        #undef STRUCT
        #define STRUCT ( (DrawJunctionStruct*) DrawStruct )
        if( (STRUCT->m_Pos.x >= x1) && (STRUCT->m_Pos.x <= x2)
           && (STRUCT->m_Pos.y >= y1) && (STRUCT->m_Pos.y <= y2) )
            return TRUE;
        break;


    case DRAW_NOCONNECT_STRUCT_TYPE:
        #undef STRUCT
        #define STRUCT ( (DrawNoConnectStruct*) DrawStruct )
        if( (STRUCT->m_Pos.x >= x1) && (STRUCT->m_Pos.x <= x2)
           && (STRUCT->m_Pos.y >= y1) && (STRUCT->m_Pos.y <= y2) )
            return TRUE;
        break;


    case TYPE_MARKER_SCH:
        #undef STRUCT
        #define STRUCT ( (MARKER_SCH*) DrawStruct )
        if( (STRUCT->m_Pos.x >= x1) && (STRUCT->m_Pos.x <= x2)
           && (STRUCT->m_Pos.y >= y1) && (STRUCT->m_Pos.y <= y2) )
            return TRUE;
        break;

    case TYPE_SCH_LABEL:
    case TYPE_SCH_TEXT:
        #undef STRUCT
        #define STRUCT ( (SCH_TEXT*) DrawStruct )
        dx  = STRUCT->m_Size.x * STRUCT->GetLength();
        dy  = STRUCT->m_Size.y;
        xt1 = xt2 = STRUCT->m_Pos.x;
        yt1 = yt2 = STRUCT->m_Pos.y;

        switch( STRUCT->m_Orient )
        {
        case 0:             /* HORIZONTAL  Left justified */
            xt2 += dx; yt2 -= dy;
            break;

        case 1:             /* VERTICAL UP */
            xt2 -= dy; yt2 -= dx;
            break;

        case 2:             /* horizontal  Right justified  */
            xt2 -= dx; yt2 -= dy;
            break;

        case 3:             /* vertical DOWN */
            xt2 -= dy; yt2 += dx;
            break;
        }

        if( IsBox1InBox2( xt1, yt1, xt2, yt2, x1, y1, x2, y2 ) )
            return TRUE;
        break;

    case TYPE_SCH_HIERLABEL:
    case TYPE_SCH_GLOBALLABEL:
        #undef STRUCT
        #define STRUCT ( (SCH_LABEL*) DrawStruct )
        dx  = STRUCT->m_Size.x * ( STRUCT->GetLength() + 1);    /* total length
                                                                 **/
        dy  = STRUCT->m_Size.y / 2;                             /* half height
                                                                 **/
        xt1 = xt2 = STRUCT->m_Pos.x;
        yt1 = yt2 = STRUCT->m_Pos.y;

        switch( STRUCT->m_Orient )
        {
        case 0:             /* HORIZONTAL */
            xt2 -= dx; yt2 += dy; yt1 -= dy;
            break;

        case 1:             /* VERTICAL UP */
            xt1 -= dy; xt2 += dy; yt2 += dx;
            break;

        case 2:             /* horizontal inverse */
            xt2 += dx; yt2 += dy; yt1 -= dy;
            break;

        case 3:             /* vertical DOWN */
            xt1 -= dy; xt2 += dy; yt2 -= dx;
            break;
        }

        if( IsBox1InBox2( xt1, yt1, xt2, yt2, x1, y1, x2, y2 ) )
            return TRUE;
        break;

    case TYPE_SCH_COMPONENT:
    {
        #undef STRUCT
        #define STRUCT ( (SCH_COMPONENT*) DrawStruct )
        EDA_Rect BoundaryBox = STRUCT->GetBoundaryBox();
        xt1 = BoundaryBox.GetX();
        yt1 = BoundaryBox.GetY();
        xt2 = BoundaryBox.GetRight();
        yt2 = BoundaryBox.GetBottom();
        if( IsBox1InBox2( xt1, yt1, xt2, yt2, x1, y1, x2, y2 ) )
            return TRUE;
        break;
    }

    case DRAW_SHEET_STRUCT_TYPE:
        #undef STRUCT
        #define STRUCT ( (SCH_SHEET*) DrawStruct )
        /* Recalculate the coordinates of the worksheet component */
        xt1 = STRUCT->m_Pos.x;
        yt1 = STRUCT->m_Pos.y;
        xt2 = STRUCT->m_Pos.x + STRUCT->m_Size.x;
        yt2 = STRUCT->m_Pos.y + STRUCT->m_Size.y;

        if( IsBox1InBox2( xt1, yt1, xt2, yt2, x1, y1, x2, y2 ) )
            return TRUE;
        break;

    case DRAW_HIERARCHICAL_PIN_SHEET_STRUCT_TYPE:
        break;

    default:
        msg.Printf( wxT( "DrawStructInBox() Err: unexpected StructType %d (" ),
                    DrawStruct->Type() );
        msg << DrawStruct->GetClass() << wxT( ")" );
        wxMessageBox( msg );
        break;
    }

    return FALSE;
}


/****************************************************************************/
static bool IsBox1InBox2( int StartX1, int StartY1, int EndX1, int EndY1,
                          int StartX2, int StartY2, int EndX2, int EndY2 )
{
/****************************************************************************/
/*  Routine detects that the rectangle 1 (Box1) and the rectangle 2 (Box2) is
 * Overlap.
 * Returns TRUE or FALSE.
 *
 * These assume that there is recovery if at least one corner
 * A 'Box' is included in the other
 */
    int cX, cY;

    if( StartX1 > EndX1 )
        EXCHG( StartX1, EndX1 );
    if( StartX2 > EndX2 )
        EXCHG( StartX2, EndX2 );
    if( StartY1 > EndY1 )
        EXCHG( StartY1, EndY1 );
    if( StartY2 > EndY2 )
        EXCHG( StartY2, EndY2 );

    /* Test the 4 corners of the rectangle 1 */
    cX = StartX1;
    cY = StartY1;
    if( (cX >= StartX2) && (cX <= EndX2) && (cY >= StartY2) && (cY <= EndY2) )
        return TRUE;

    cX = EndX1;
    cY = StartY1;
    if( (cX >= StartX2) && (cX <= EndX2) && (cY >= StartY2) && (cY <= EndY2) )
        return TRUE;

    cX = EndX1;
    cY = EndY1;
    if( (cX >= StartX2) && (cX <= EndX2) && (cY >= StartY2) && (cY <= EndY2) )
        return TRUE;

    cX = StartX1;
    cY = EndY1;
    if( (cX >= StartX2) && (cX <= EndX2) && (cY >= StartY2) && (cY <= EndY2) )
        return TRUE;

    /* Test the 4 corners of the rectangle 2 */
    cX = StartX2;
    cY = StartY2;
    if( (cX >= StartX1) && (cX <= EndX1) && (cY >= StartY1) && (cY <= EndY1) )
        return TRUE;

    cX = EndX2;
    cY = StartY2;
    if( (cX >= StartX1) && (cX <= EndX1) && (cY >= StartY1) && (cY <= EndY1) )
        return TRUE;

    cX = EndX2;
    cY = EndY2;
    if( (cX >= StartX1) && (cX <= EndX1) && (cY >= StartY1) && (cY <= EndY1) )
        return TRUE;

    cX = StartX2;
    cY = EndY2;
    if( (cX >= StartX1) && (cX <= EndX1) && (cY >= StartY1) && (cY <= EndY1) )
        return TRUE;

    return FALSE;
}


SCH_SHEET_PIN* LocateSheetLabel( SCH_SHEET* Sheet, const wxPoint& pos )
{
    int size, dy, minx, maxx;
    SCH_SHEET_PIN* SheetLabel;

    SheetLabel = Sheet->m_Label;
    while( SheetLabel
           && SheetLabel->Type() == DRAW_HIERARCHICAL_PIN_SHEET_STRUCT_TYPE )
    {
        size = ( SheetLabel->GetLength() + 1 ) * SheetLabel->m_Size.x;
        if( SheetLabel->m_Edge )
            size = -size;
        minx = SheetLabel->m_Pos.x; maxx = SheetLabel->m_Pos.x + size;
        if( maxx < minx )
            EXCHG( maxx, minx );
        dy = SheetLabel->m_Size.x / 2;
        if( (ABS( pos.y - SheetLabel->m_Pos.y ) <= dy )
           && (pos.x <= maxx)
           && (pos.x >= minx) )
            return SheetLabel;
        SheetLabel = SheetLabel->Next();
    }

    return NULL;
}


LIB_PIN* LocateAnyPin( SCH_ITEM* DrawList, const wxPoint& RefPos,
                       SCH_COMPONENT** libpart )
{
    SCH_ITEM* DrawStruct;
    LIB_COMPONENT* Entry;
    SCH_COMPONENT* schItem = NULL;
    LIB_PIN* Pin = NULL;

    for( DrawStruct = DrawList; DrawStruct != NULL;
        DrawStruct = DrawStruct->Next() )
    {
        if( DrawStruct->Type() != TYPE_SCH_COMPONENT )
            continue;
        schItem = (SCH_COMPONENT*) DrawStruct;
        Entry   = CMP_LIBRARY::FindLibraryComponent( schItem->m_ChipName );

        if( Entry == NULL )
            continue;

        /* we use LocateDrawItem to locate pins. but this function suppose a
         * component.
         * at 0,0 location
         * So we must calculate the ref position relative to the component
         */
        wxPoint libPos = RefPos - schItem->m_Pos;
        Pin = (LIB_PIN*) Entry->LocateDrawItem( schItem->m_Multi,
                                                schItem->m_Convert,
                                                COMPONENT_PIN_DRAW_TYPE,
                                                libPos, schItem->m_Transform );
        if( Pin )
            break;
    }

    if( libpart )
        *libpart = schItem;
    return Pin;
}


SCH_SHEET_PIN* LocateAnyPinSheet( const wxPoint& RefPos, SCH_ITEM* DrawList )
{
    SCH_ITEM* DrawStruct;
    SCH_SHEET_PIN* PinSheet = NULL;

    for( DrawStruct = DrawList; DrawStruct != NULL;
        DrawStruct = DrawStruct->Next() )
    {
        if( DrawStruct->Type() != DRAW_SHEET_STRUCT_TYPE )
            continue;

        PinSheet = LocateSheetLabel( (SCH_SHEET*) DrawStruct, RefPos );
        if( PinSheet )
            break;
    }

    return PinSheet;
}
