/*********************/
/* dangling_ends.cpp */
/*********************/

#include "fctsys.h"
#include "gr_basic.h"
#include "common.h"

#include "program.h"
#include "general.h"
#include "netlist.h"
#include "protos.h"
#include "class_library.h"
#include "class_pin.h"


enum End_Type {
    UNKNOWN = 0,
    WIRE_START_END,
    WIRE_END_END,
    BUS_START_END,
    BUS_END_END,
    JUNCTION_END,
    PIN_END,
    LABEL_END,
    ENTRY_END,
    SHEET_LABEL_END
};

// A helper class to store a list of items that can be connected to something:
class DANGLING_END_ITEM
{
public:
    const void* m_Item;         // a pointer to the parent
    wxPoint     m_Pos;          // the position of the connecting point
    int         m_Type;         // type of parent

    DANGLING_END_ITEM( int type, const void* aItem )
    {
        m_Item = aItem;
        m_Type = type;
    }
};

static void TestWireForDangling( std::vector <DANGLING_END_ITEM>& aItemList,
                                 SCH_LINE*                        DrawRef,
                                 WinEDA_SchematicFrame*           frame,
                                 wxDC*                            aDC );
void        TestLabelForDangling( std::vector <DANGLING_END_ITEM>& aItemList,
                                  SCH_TEXT*                        aLabel,
                                  WinEDA_SchematicFrame*           aFrame,
                                  wxDC*                            aDC );
void        RebuildEndPointsList( std::vector <DANGLING_END_ITEM>& aItemList, SCH_ITEM* aDrawList );


/* Returns true if the point P is on the segment S. */
bool SegmentIntersect( wxPoint aSegStart, wxPoint aSegEnd, wxPoint aTestPoint )
{
    wxPoint vectSeg   = aSegEnd - aSegStart;    // Vector from S1 to S2
    wxPoint vectPoint = aTestPoint - aSegStart; // Vector from S1 to P

    // Use long long here to avoid overflow in calculations
    if( (long long) vectSeg.x * vectPoint.y - (long long) vectSeg.y * vectPoint.x )
        return false;        /* Cross product non-zero, vectors not parallel */

    if( ( (long long) vectSeg.x * vectPoint.x + (long long) vectSeg.y * vectPoint.y ) <
       ( (long long) vectPoint.x * vectPoint.x + (long long) vectPoint.y * vectPoint.y ) )
        return false;          /* Point not on segment */

    return true;
}


void WinEDA_SchematicFrame::TestDanglingEnds( SCH_ITEM* DrawList, wxDC* DC )
{
    // this list is static to avoid many useles memory allocation.
    std::vector <DANGLING_END_ITEM> itemList;

    RebuildEndPointsList( itemList, DrawList );

    for( SCH_ITEM* item = DrawList; item; item = item->Next() )
    {
        switch( item->Type() )
        {
        case TYPE_SCH_GLOBALLABEL:
        case TYPE_SCH_HIERLABEL:
        case TYPE_SCH_LABEL:
            TestLabelForDangling( itemList, (SCH_LABEL*) item, this, DC );
            break;

        case DRAW_SHEET_STRUCT_TYPE:
        {
             SCH_SHEET* sheet = (SCH_SHEET*) item;
           // Read the hierarchical pins list and teast for dangling pins:
            BOOST_FOREACH( SCH_SHEET_PIN & pinsheet, sheet->GetSheetPins() )
            {
                TestLabelForDangling( itemList, &pinsheet, this, DC );
            }
        }
            break;

        case DRAW_SEGMENT_STRUCT_TYPE:
            #undef STRUCT
            #define STRUCT ( (SCH_LINE*) item )
            if( STRUCT->GetLayer() == LAYER_WIRE )
            {
                TestWireForDangling( itemList, STRUCT, this, DC );
                break;
            }
            if( STRUCT->GetLayer() == LAYER_NOTES )
                break;
            if( STRUCT->GetLayer() == LAYER_BUS )
            {
                STRUCT->m_StartIsDangling = STRUCT->m_EndIsDangling = false;
                break;
            }
            break;

        default:
            break;
        }
    }
}


/**
 * Test if point pos is on a pin end.
 *
 * @param DrawList = List of SCH_ITEMs to check.
 * @return a LIB_PIN pointer to the located pin or NULL if no pin was found.
 */
LIB_PIN* WinEDA_SchematicFrame::LocatePinEnd( SCH_ITEM*      DrawList,
                                              const wxPoint& pos )
{
    SCH_COMPONENT* DrawLibItem;
    LIB_PIN* Pin;
    wxPoint pinpos;

    Pin = LocateAnyPin( DrawList, pos, &DrawLibItem );
    if( !Pin )
        return NULL;

    pinpos = Pin->m_Pos;

    if( DrawLibItem == NULL )
        NEGATE( pinpos.y );     // In libraries Y axis is bottom to top
                                // and in schematic Y axis is top to bottom

    else                        // calculate the pin position in schematic
        pinpos = TransformCoordinate( DrawLibItem->m_Transform, pinpos )
                 + DrawLibItem->m_Pos;

    if( pos == pinpos )
        return Pin;
    return NULL;
}


void TestWireForDangling( std::vector <DANGLING_END_ITEM>& aItemList,
                          SCH_LINE*                        DrawRef,
                          WinEDA_SchematicFrame*           frame,
                          wxDC*                            DC )
{
    bool Sdangstate = true, Edangstate = true;

    BOOST_FOREACH( DANGLING_END_ITEM terminal_item, aItemList )
    {
        if( terminal_item.m_Item == DrawRef )
            continue;

        if( DrawRef->m_Start == terminal_item.m_Pos )
            Sdangstate = false;

        if( DrawRef->m_End == terminal_item.m_Pos )
            Edangstate = false;

        if( (Sdangstate == false) && (Edangstate == false) )
            break;
    }

    if( (Sdangstate != DrawRef->m_StartIsDangling)
       || (Edangstate != DrawRef->m_EndIsDangling) )
    {
        if( DC )
            RedrawOneStruct( frame->DrawPanel, DC, DrawRef, g_XorMode );
        DrawRef->m_StartIsDangling = Sdangstate;
        DrawRef->m_EndIsDangling   = Edangstate;
        if( DC )
            RedrawOneStruct( frame->DrawPanel, DC, DrawRef,
                             GR_DEFAULT_DRAWMODE );
    }
}


void TestLabelForDangling( std::vector <DANGLING_END_ITEM>& aItemList,
                           SCH_TEXT*                        aLabel,
                           WinEDA_SchematicFrame*           aFrame,
                           wxDC*                            aDC )
{
    bool dangstate = true;

    for( unsigned ii = 0; ii < aItemList.size(); ii++ )
    {
        DANGLING_END_ITEM & terminal_item = aItemList[ii];

        if( terminal_item.m_Item == aLabel )
            continue;

        switch( terminal_item.m_Type )
        {
        case PIN_END:
        case LABEL_END:
        case SHEET_LABEL_END:
            if( aLabel->m_Pos == terminal_item.m_Pos )
                dangstate = false;
            break;

        case WIRE_START_END:
        case BUS_START_END:
        {
            // these schematic items have created 2 DANGLING_END_ITEM
            // one per end.
            ii++;
            DANGLING_END_ITEM & next_terminal = aItemList[ii];
            dangstate = !SegmentIntersect( terminal_item.m_Pos,
                                           next_terminal.m_Pos,
                                           aLabel->m_Pos );
        }
            break;

        case UNKNOWN:
        case JUNCTION_END:
        case ENTRY_END:
        case WIRE_END_END:
        case BUS_END_END:
            break;
        }

        if( dangstate == false )
            break;
    }

    if( dangstate != aLabel->m_IsDangling )
    {
        if( aDC )
            RedrawOneStruct( aFrame->DrawPanel, aDC, aLabel, g_XorMode );
        aLabel->m_IsDangling = dangstate;
        if( aDC )
            RedrawOneStruct( aFrame->DrawPanel, aDC, aLabel, GR_DEFAULT_DRAWMODE );
    }
}


/* Returns the physical position of the pin relative to the component
 * orientation. */
wxPoint ReturnPinPhysicalPosition( LIB_PIN* Pin, SCH_COMPONENT* DrawLibItem )
{
    wxPoint PinPos = Pin->m_Pos;

    if( DrawLibItem == NULL )
        NEGATE( PinPos.y );

    else
        PinPos = TransformCoordinate( DrawLibItem->m_Transform,
                                      Pin->m_Pos ) + DrawLibItem->m_Pos;

    return PinPos;
}


void RebuildEndPointsList( std::vector <DANGLING_END_ITEM>& aItemList, SCH_ITEM* aDrawList )
{
    SCH_ITEM* schItem;

    aItemList.clear();

    for( schItem = aDrawList; schItem != NULL; schItem = schItem->Next() )
    {
        switch( schItem->Type() )
        {
        case TYPE_SCH_LABEL:
        case TYPE_SCH_GLOBALLABEL:
        case TYPE_SCH_HIERLABEL:
        {
            #undef STRUCT
            #define STRUCT ( (SCH_LABEL*) schItem )
            DANGLING_END_ITEM item( LABEL_END, schItem );
            item.m_Pos  = STRUCT->m_Pos;
            aItemList.push_back( item );
        }
        break;

        case DRAW_SEGMENT_STRUCT_TYPE:
            #undef STRUCT
            #define STRUCT ( (SCH_LINE*) schItem )
            if( STRUCT->GetLayer() == LAYER_NOTES )
                break;
            if( ( STRUCT->GetLayer() == LAYER_BUS )
               || (STRUCT->GetLayer() == LAYER_WIRE ) )
            {
                DANGLING_END_ITEM item( (STRUCT->GetLayer() == LAYER_BUS) ?
                                       BUS_START_END : WIRE_START_END, schItem );
                item.m_Pos  = STRUCT->m_Start;
                DANGLING_END_ITEM item1( (STRUCT->GetLayer() == LAYER_BUS) ?
                                          BUS_END_END : WIRE_END_END, schItem );
                item1.m_Pos  = STRUCT->m_End;

                aItemList.push_back( item );
                aItemList.push_back( item1 );
            }
            break;

        case DRAW_JUNCTION_STRUCT_TYPE:
        {
            #undef STRUCT
            #define STRUCT ( (SCH_JUNCTION*) schItem )
            DANGLING_END_ITEM item( JUNCTION_END, schItem );
            item.m_Pos  = STRUCT->m_Pos;
            aItemList.push_back( item );
        }
        break;

        case DRAW_BUSENTRY_STRUCT_TYPE:
        {
            #undef STRUCT
            #define STRUCT ( (SCH_BUS_ENTRY*) schItem )
            DANGLING_END_ITEM item( ENTRY_END, schItem );
            item.m_Pos  = STRUCT->m_Pos;

            DANGLING_END_ITEM item1( ENTRY_END, schItem );
            item1.m_Pos  = STRUCT->m_End();
            aItemList.push_back( item );
            aItemList.push_back( item1 );
        }
        break;

        case TYPE_SCH_COMPONENT:
        {
            #undef STRUCT
            #define STRUCT ( (SCH_COMPONENT*) schItem )
            LIB_COMPONENT* Entry;
            Entry = CMP_LIBRARY::FindLibraryComponent( STRUCT->m_ChipName );
            if( Entry == NULL )
                break;

            for( LIB_PIN* Pin = Entry->GetNextPin(); Pin != NULL;
                Pin = Entry->GetNextPin( Pin ) )
            {
                wxASSERT( Pin->Type() == COMPONENT_PIN_DRAW_TYPE );

                if( Pin->m_Unit && STRUCT->m_Multi
                   && ( STRUCT->m_Multi != Pin->m_Unit ) )
                    continue;

                if( Pin->m_Convert && STRUCT->m_Convert
                   && ( STRUCT->m_Convert != Pin->m_Convert ) )
                    continue;

                DANGLING_END_ITEM item( PIN_END, Pin );
                item.m_Pos  = ReturnPinPhysicalPosition( Pin, STRUCT );
                aItemList.push_back( item );
            }

            break;
        }

        case DRAW_SHEET_STRUCT_TYPE:
        {
            SCH_SHEET* sheet = (SCH_SHEET*) schItem;

            // Using BOOST_FOREACH here creates problems (bad pointer value to pinsheet).
            // I do not know why.
            for( unsigned ii = 0; ii < sheet->GetSheetPins().size(); ii++ )
            {
                SCH_SHEET_PIN &pinsheet = sheet->GetSheetPins()[ii];
                wxASSERT( pinsheet.Type() == DRAW_HIERARCHICAL_PIN_SHEET_STRUCT_TYPE );

                DANGLING_END_ITEM item( SHEET_LABEL_END, &pinsheet );
                item.m_Pos  = pinsheet.m_Pos;
                aItemList.push_back( item );
            }
        }
        break;

        default:
            ;
        }
    }
}
