/**************************************/
/*  erc.cpp - Electrical Rules Check  */
/**************************************/

#include "fctsys.h"
#include "common.h"
#include "class_drawpanel.h"
#include "kicad_string.h"
#include "bitmaps.h"
#include "class_sch_screen.h"
#include "wxEeschemaStruct.h"

#include "general.h"
#include "netlist.h"
#include "lib_pin.h"
#include "protos.h"
#include "erc.h"
#include "sch_marker.h"
#include "sch_component.h"
#include "sch_sheet.h"


/* ERC tests :
 *  1 - conflicts between connected pins ( example: 2 connected outputs )
 *  2 - minimal connections requirements ( 1 input *must* be connected to an
 * output, or a passive pin )
 */


/*
 *  Electrical type of pins:
 *  PIN_INPUT = usual pin input: must be connected
 *  PIN_OUTPUT = usual output
 *  PIN_BIDI = input or output (like port for a microprocessor)
 *  PIN_TRISTATE = tris state bus pin
 *  PIN_PASSIVE = pin for passive components: must be connected, and can be
 * connected to any pin
 *  PIN_UNSPECIFIED = unknown electrical properties: creates always a warning
 * when connected
 *  PIN_POWER_IN = power input (GND, VCC for ICs). Must be connected to a power
 * output.
 *  PIN_POWER_OUT = output of a regulator: intended to be connected to power
 * input pins
 *  PIN_OPENCOLLECTOR = pin type open collector
 *  PIN_OPENEMITTER = pin type open emitter
 *  PIN_NC = not connected (must be left open)
 *
 *  Minimal requirements:
 *  All pins *must* be connected (except PIN_NC).
 *  When a pin is not connected in schematic, the user must place a "non
 * connected" symbol to this pin.
 *  This ensures a forgotten connection will be detected.
 */

/* Messages for conflicts :
 *  PIN_INPUT, PIN_OUTPUT, PIN_BIDI, PIN_TRISTATE, PIN_PASSIVE,
 *  PIN_UNSPECIFIED, PIN_POWER_IN, PIN_POWER_OUT, PIN_OPENCOLLECTOR,
 *  PIN_OPENEMITTER, PIN_NC
 *  These messages are used to show the ERC matrix in ERC dialog
 */

// Messages for matrix rows:
const wxChar* CommentERC_H[] =
{
    wxT( "Input Pin...." ),
    wxT( "Output Pin..." ),
    wxT( "BiDi Pin....." ),
    wxT( "3 State Pin.." ),
    wxT( "Passive Pin.." ),
    wxT( "Unspec Pin..." ),
    wxT( "Power IN Pin." ),
    wxT( "PowerOUT Pin." ),
    wxT( "Open Coll...." ),
    wxT( "Open Emit...." ),
    wxT( "No Conn......" ),
    NULL
};

// Messages for matrix columns
const wxChar* CommentERC_V[] =
{
    wxT( "Input Pin" ),
    wxT( "Output Pin" ),
    wxT( "BiDi Pin" ),
    wxT( "3 State Pin" ),
    wxT( "Passive Pin" ),
    wxT( "Unspec Pin" ),
    wxT( "Power IN Pin" ),
    wxT( "PowerOUT Pin" ),
    wxT( "Open Coll" ),
    wxT( "Open Emit" ),
    wxT( "No Conn" ),
    NULL
};


/* Look up table which gives the diag for a pair of connected pins
 *  Can be modified by ERC options.
 *  at start up: must be loaded by DefaultDiagErc
 */
int  DiagErc[PIN_NMAX][PIN_NMAX];
bool DiagErcTableInit;       // go to TRUE after DiagErc init

/* Default Look up table which gives the diag for a pair of connected pins
 *  Same as DiagErc, but cannot be modified
 *  Used to init or reset DiagErc
 *  note also, to avoid inconsistancy:
 *    DefaultDiagErc[i][j] = DefaultDiagErc[j][i]
 */
int DefaultDiagErc[PIN_NMAX][PIN_NMAX] =
{
/*         I,   O,    Bi,   3S,   Pas,  UnS,  PwrI, PwrO, OC,   OE,   NC */
/* I */  { OK,  OK,   OK,   OK,   OK,   WAR,  OK,   OK,   OK,   OK,   ERR },
/* O */  { OK,  ERR,  OK,   WAR,  OK,   WAR,  OK,   ERR,  ERR,  ERR,  ERR },
/* Bi*/  { OK,  OK,   OK,   OK,   OK,   WAR,  OK,   WAR,  OK,   WAR,  ERR },
/* 3S*/  { OK,  WAR,  OK,   OK,   OK,   WAR,  WAR,  ERR,  WAR,  WAR,  ERR },
/*Pas*/  { OK,  OK,   OK,   OK,   OK,   WAR,  OK,   OK,   OK,   OK,   ERR },
/*UnS */ { WAR, WAR,  WAR,  WAR,  WAR,  WAR,  WAR,  WAR,  WAR,  WAR,  ERR },
/*PwrI*/ { OK,  OK,   OK,   WAR,  OK,   WAR,  OK,   OK,   OK,   OK,   ERR },
/*PwrO*/ { OK,  ERR,  WAR,  ERR,  OK,   WAR,  OK,   ERR,  ERR,  ERR,  ERR },
/* OC */ { OK,  ERR,  OK,   WAR,  OK,   WAR,  OK,   ERR,  OK,   OK,   ERR },
/* OE */ { OK,  ERR,  WAR,  WAR,  OK,   WAR,  OK,   ERR,  OK,   OK,   ERR },
/* NC */ { ERR, ERR,  ERR,  ERR,  ERR,  ERR,  ERR,  ERR,  ERR,  ERR,  ERR }
};


/* Look up table which gives the minimal drive for a pair of connected pins on
 * a net
 *  Initial state of a net is NOC (Net with No Connection)
 *  Can be updated to NPI (Pin Isolated), NET_NC (Net with a no connect symbol),
 *                  NOD (Not Driven) or DRV (DRIven)
 *
 *  Can be updated to NET_NC with no error only if there is only one pin in net
 *
 *  Nets are OK when their final state is NET_NC or DRV
 *  Nets with the state NOD have no source signal
 */
static int MinimalReq[PIN_NMAX][PIN_NMAX] =
{
/*         In   Out, Bi,  3S,  Pas, UnS, PwrI,PwrO,OC,  OE,  NC */
/* In*/  { NOD, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/*Out*/  { DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, NPI },
/* Bi*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/* 3S*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/*Pas*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/*UnS*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/*PwrI*/ { NOD, DRV, NOD, NOD, NOD, NOD, NOD, DRV, NOD, NOD, NPI },
/*PwrO*/ { DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, DRV, NPI },
/* OC*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/* OE*/  { DRV, DRV, DRV, DRV, DRV, DRV, NOD, DRV, DRV, DRV, NPI },
/* NC*/  { NPI, NPI, NPI, NPI, NPI, NPI, NPI, NPI, NPI, NPI, NPI }
};


/**
 * Function TestDuplicateSheetNames( )
 * inside a given sheet, one cannot have sheets with duplicate names (file
 * names can be duplicated).
 * @return the error count
 * @param aCreateMarker: true = create error markers in schematic,
 *                       false = calculate error count only
 */
int TestDuplicateSheetNames( bool aCreateMarker )
{
    int         err_count = 0;
    SCH_SCREENS ScreenList;      // Created the list of screen

    for( SCH_SCREEN* Screen = ScreenList.GetFirst();
         Screen != NULL;
         Screen = ScreenList.GetNext() )
    {
        for( SCH_ITEM* ref_item = Screen->GetDrawItems();
             ref_item != NULL;
             ref_item = ref_item->Next() )
        {
            // search for a sheet;
            if( ref_item->Type() != SCH_SHEET_T )
                continue;
            for( SCH_ITEM* item_to_test = ref_item->Next();
                 item_to_test != NULL;
                 item_to_test = item_to_test->Next() )
            {
                if( item_to_test->Type() != SCH_SHEET_T )
                    continue;

                // We have found a second sheet: compare names
                if( ( (SCH_SHEET*) ref_item )->m_SheetName.CmpNoCase(
                        ( ( SCH_SHEET* ) item_to_test )-> m_SheetName )
                    == 0 )
                {
                    if( aCreateMarker )
                    {
                        /* Create a new marker type ERC error*/
                        SCH_MARKER* Marker = new SCH_MARKER();
                        Marker->m_TimeStamp = GetTimeStamp();
                        Marker->SetData( ERCE_DUPLICATE_SHEET_NAME,
                                         ( (SCH_SHEET*) item_to_test )->m_Pos,
                                         _( "Duplicate Sheet name" ),
                                         ( (SCH_SHEET*) item_to_test )->m_Pos );
                        Marker->SetMarkerType( MARK_ERC );
                        Marker->SetErrorLevel( ERR );
                        Marker->SetNext( Screen->GetDrawItems() );
                        Screen->SetDrawItems( Marker );
                    }

                    err_count++;
                }
            }
        }
    }

    return err_count;
}


/* Creates an ERC marker to show the ERC problem about aNetItemRef
 * or between aNetItemRef and aNetItemTst
 *  if MinConn < 0: this is an error on labels
 */
void Diagnose( EDA_DRAW_PANEL* aPanel,
               NETLIST_OBJECT* aNetItemRef,
               NETLIST_OBJECT* aNetItemTst,
               int aMinConn, int aDiag )
{
    SCH_MARKER* Marker = NULL;
    SCH_SCREEN* screen;
    int         ii, jj;

    if( aDiag == OK )
        return;

    /* Create new marker for ERC error. */
    Marker = new SCH_MARKER();
    Marker->m_TimeStamp = GetTimeStamp();

    Marker->SetMarkerType( MARK_ERC );
    Marker->SetErrorLevel( WAR );
    screen = aNetItemRef->m_SheetList.LastScreen();
    Marker->SetNext( screen->GetDrawItems() );
    screen->SetDrawItems( Marker );
    g_EESchemaVar.NbErrorErc++;
    g_EESchemaVar.NbWarningErc++;

    wxString msg;

    if( aMinConn < 0 )
    {
        if( (aNetItemRef->m_Type == NET_HIERLABEL)
            || (aNetItemRef->m_Type == NET_HIERBUSLABELMEMBER) )
        {
            msg.Printf( _( "HLabel %s not connected to SheetLabel" ),
                        GetChars( aNetItemRef->m_Label ) );
        }
        else
        {
            msg.Printf( _( "SheetLabel %s not connected to HLabel" ),
                        GetChars( aNetItemRef->m_Label ) );
        }


        Marker->SetData( ERCE_HIERACHICAL_LABEL,
                         aNetItemRef->m_Start,
                         msg,
                         aNetItemRef->m_Start );
        return;
    }

    ii = aNetItemRef->m_ElectricalType;

    wxString string_pinnum, cmp_ref;
    char     ascii_buf[5];
    ascii_buf[4] = 0;
    memcpy( ascii_buf, &aNetItemRef->m_PinNum, 4 );
    string_pinnum = FROM_UTF8( ascii_buf );
    cmp_ref = wxT( "?" );

    if( aNetItemRef->m_Type == NET_PIN && aNetItemRef->m_Link )
        cmp_ref = ( (SCH_COMPONENT*) aNetItemRef->m_Link )->GetRef( &aNetItemRef->m_SheetList );

    if( aNetItemTst == NULL )
    {
        if( aMinConn == NOC )    /* Only 1 element in the net. */
        {
            msg.Printf( _( "Cmp %s, Pin %s (%s) Unconnected" ),
                        GetChars( cmp_ref ), GetChars( string_pinnum ),
                        MsgPinElectricType[ii] );
            Marker->SetData( ERCE_PIN_NOT_CONNECTED,
                             aNetItemRef->m_Start,
                             msg,
                             aNetItemRef->m_Start );
            return;
        }

        if( aMinConn == NOD )    /* Nothing driving the net. */
        {
            if( aNetItemRef->m_Type == NET_PIN && aNetItemRef->m_Link )
                cmp_ref = ( (SCH_COMPONENT*) aNetItemRef->m_Link )->GetRef(
                    &aNetItemRef->m_SheetList );
            msg.Printf( _( "Cmp %s, Pin %s (%s) not driven (Net %d)" ),
                        GetChars( cmp_ref ), GetChars( string_pinnum ),
                        MsgPinElectricType[ii], aNetItemRef->GetNet() );
            Marker->SetData( ERCE_PIN_NOT_DRIVEN,
                             aNetItemRef->m_Start,
                             msg,
                             aNetItemRef->m_Start );
            return;
        }

        if( aDiag == UNC )
        {
            msg.Printf( _( "More than 1 Pin connected to UnConnect symbol" ) );
            Marker->SetData( ERCE_NOCONNECT_CONNECTED,
                             aNetItemRef->m_Start,
                             msg,
                             aNetItemRef->m_Start );
            return;
        }
    }

    if( aNetItemTst )         /* Error between 2 pins */
    {
        jj = aNetItemTst->m_ElectricalType;
        int errortype = ERCE_PIN_TO_PIN_WARNING;
        if( aDiag == ERR )
        {
            Marker->SetErrorLevel( ERR );
            g_EESchemaVar.NbWarningErc--;
            errortype = ERCE_PIN_TO_PIN_ERROR;
        }

        wxString alt_string_pinnum, alt_cmp;
        memcpy( ascii_buf, &aNetItemTst->m_PinNum, 4 );
        alt_string_pinnum = FROM_UTF8( ascii_buf );
        alt_cmp = wxT( "?" );
        if( aNetItemTst->m_Type == NET_PIN && aNetItemTst->m_Link )
            alt_cmp = ( (SCH_COMPONENT*) aNetItemTst->m_Link )->GetRef(
                &aNetItemTst->m_SheetList );
        msg.Printf( _( "Cmp %s, Pin %s (%s) connected to " ),
                    GetChars( cmp_ref ), GetChars( string_pinnum ), MsgPinElectricType[ii] );
        Marker->SetData( errortype,
                         aNetItemRef->m_Start,
                         msg,
                         aNetItemRef->m_Start );
        msg.Printf( _( "Cmp %s, Pin %s (%s) (net %d)" ),
                    GetChars( alt_cmp ), GetChars( alt_string_pinnum ), MsgPinElectricType[jj],
                    aNetItemRef->GetNet() );
        Marker->SetAuxiliaryData( msg, aNetItemTst->m_Start );
    }
}


/* Routine testing electrical conflicts between NetItemRef and other items
 * of the same net
 */
void TestOthersItems( EDA_DRAW_PANEL* panel,
                      unsigned NetItemRef,
                      unsigned netstart,
                      int* NetNbItems, int* MinConnexion )
{
    unsigned NetItemTst;

    int      ref_elect_type, jj, erc = OK, local_minconn;

    /* Analysis of the table of connections. */
    ref_elect_type = g_NetObjectslist[NetItemRef]->m_ElectricalType;

    NetItemTst    = netstart;
    local_minconn = NOC;
    if( ref_elect_type == PIN_NC )
        local_minconn = NPI;

    /* Test pins connected to NetItemRef */
    for( ; ; NetItemTst++ )
    {
        if( NetItemRef == NetItemTst )
            continue;

        /* We examine only a given net. We stop the search if the net changes
         **/
        if( ( NetItemTst >= g_NetObjectslist.size() ) // End of list
            || ( g_NetObjectslist[NetItemRef]->GetNet() !=
                 g_NetObjectslist[NetItemTst]->GetNet() ) ) // End of net
        {
            /* End net code found: minimum connection test. */
            if( (*MinConnexion < NET_NC ) && (local_minconn < NET_NC ) )
            {
                /* Not connected or not driven pin. */
                bool seterr = true;
                if( local_minconn == NOC
                    && g_NetObjectslist[NetItemRef]->m_Type == NET_PIN )
                {
                    /* This pin is not connected: for multiple part per
                     * package, and duplicated pin,
                     * search for an other instance of this pin
                     * this will be flagged only is all instances of this pin
                     * are not connected
                     * TODO test also if instances connected are connected to
                     * the same net
                     */
                    for( unsigned duppin = 0;
                         duppin < g_NetObjectslist.size();
                         duppin++ )
                    {
                        if( g_NetObjectslist[duppin]->m_Type != NET_PIN )
                            continue;
                        if( duppin == NetItemRef )
                            continue;
                        if( g_NetObjectslist[NetItemRef]->m_PinNum !=
                            g_NetObjectslist[duppin]->m_PinNum )
                            continue;

                        if( ( (SCH_COMPONENT*) g_NetObjectslist[NetItemRef]->
                             m_Link )->GetRef( &g_NetObjectslist[NetItemRef]->
                                               m_SheetList ) !=
                           ( (SCH_COMPONENT*) g_NetObjectslist[duppin]->m_Link )
                           ->GetRef( &g_NetObjectslist[duppin]->m_SheetList ) )
                            continue;

                        // Same component and same pin. Do dot create error for
                        // this pin
                        // if the other pin is connected (i.e. if duppin net
                        // has an other item)
                        if( (duppin > 0)
                           && ( g_NetObjectslist[duppin]->GetNet() ==
                               g_NetObjectslist[duppin - 1]->GetNet() ) )
                            seterr = false;
                        if( (duppin < g_NetObjectslist.size() - 1)
                           && ( g_NetObjectslist[duppin]->GetNet() ==
                               g_NetObjectslist[duppin + 1]->GetNet() ) )
                            seterr = false;
                    }
                }
                if( seterr )
                    Diagnose( panel,
                              g_NetObjectslist[NetItemRef],
                              NULL,
                              local_minconn,
                              WAR );
                *MinConnexion = DRV;   // inhibiting other messages of this
                                       // type for the net.
            }
            return;
        }

        switch( g_NetObjectslist[NetItemTst]->m_Type )
        {
        case NET_ITEM_UNSPECIFIED:
        case NET_SEGMENT:
        case NET_BUS:
        case NET_JONCTION:
        case NET_LABEL:
        case NET_HIERLABEL:
        case NET_BUSLABELMEMBER:
        case NET_HIERBUSLABELMEMBER:
        case NET_SHEETBUSLABELMEMBER:
        case NET_SHEETLABEL:
        case NET_GLOBLABEL:
        case NET_GLOBBUSLABELMEMBER:
        case NET_PINLABEL:
            break;

        case NET_NOCONNECT:
            local_minconn = MAX( NET_NC, local_minconn );
            break;

        case NET_PIN:
            jj = g_NetObjectslist[NetItemTst]->m_ElectricalType;
            local_minconn = MAX( MinimalReq[ref_elect_type][jj], local_minconn );

            if( NetItemTst <= NetItemRef )
                break;

            *NetNbItems += 1;
            if( erc == OK )
            {
                erc = DiagErc[ref_elect_type][jj];
                if( erc != OK )
                {
                    if( g_NetObjectslist[NetItemTst]->m_FlagOfConnection == 0 )
                    {
                        Diagnose( panel,
                                  g_NetObjectslist[NetItemRef],
                                  g_NetObjectslist[NetItemTst],
                                  0,
                                  erc );
                        g_NetObjectslist[NetItemTst]->m_FlagOfConnection =
                            NOCONNECT_SYMBOL_PRESENT;
                    }
                }
            }
            break;
        }
    }
}


/* Create the Diagnostic file (<xxx>.erc file)
 */
bool WriteDiagnosticERC( const wxString& FullFileName )
{
    SCH_ITEM*       DrawStruct;
    SCH_MARKER*     Marker;
    char            Line[1024];
    static FILE*    OutErc;
    SCH_SHEET_PATH* Sheet;
    wxString        msg;

    if( ( OutErc = wxFopen( FullFileName, wxT( "wt" ) ) ) == NULL )
        return FALSE;

    DateAndTime( Line );
    msg = _( "ERC report" );

    fprintf( OutErc, "%s (%s)\n", TO_UTF8( msg ), Line );

    SCH_SHEET_LIST SheetList;

    for( Sheet = SheetList.GetFirst(); Sheet != NULL; Sheet = SheetList.GetNext() )
    {
        if( Sheet->Last() == g_RootSheet )
        {
            msg.Printf( _( "\n***** Sheet / (Root) \n" ) );
        }
        else
        {
            wxString str = Sheet->PathHumanReadable();
            msg.Printf( _( "\n***** Sheet %s\n" ), GetChars( str ) );
        }

        fprintf( OutErc, "%s", TO_UTF8( msg ) );

        DrawStruct = Sheet->LastDrawList();
        for( ; DrawStruct != NULL; DrawStruct = DrawStruct->Next() )
        {
            if( DrawStruct->Type() != SCH_MARKER_T )
                continue;

            Marker = (SCH_MARKER*) DrawStruct;
            if( Marker->GetMarkerType() != MARK_ERC )
                continue;
            msg = Marker->GetReporter().ShowReport();
            fprintf( OutErc, "%s", TO_UTF8( msg ) );
        }
    }

    msg.Printf( _( "\n >> Errors ERC: %d\n" ), g_EESchemaVar.NbErrorErc );
    fprintf( OutErc, "%s", TO_UTF8( msg ) );
    fclose( OutErc );

    return TRUE;
}


static bool IsLabelsConnected( NETLIST_OBJECT* a, NETLIST_OBJECT* b )
{
    int at = a->m_Type;
    int bt = b->m_Type;

    if( ( at == NET_HIERLABEL || at == NET_HIERBUSLABELMEMBER )
       && ( bt == NET_SHEETLABEL || bt == NET_SHEETBUSLABELMEMBER ) )
    {
        if( a->m_SheetList == b->m_SheetListInclude )
        {
            return true; //connected!
        }
    }
    return false; //these two are unconnected
}


/* Routine to perform erc on a sheetLabel that is connected to a corresponding
 * sub sheet Glabel
 */
void TestLabel( EDA_DRAW_PANEL* panel, unsigned NetItemRef, unsigned StartNet )
{
    unsigned NetItemTst;
    int      erc = 1;


    NetItemTst = StartNet;

    /* Review the list of labels connected to NetItemRef. */
    for( ; ; NetItemTst++ )
    {
        if( NetItemTst == NetItemRef )
            continue;

        /* Is always in the same net? */
        if( ( NetItemTst ==  g_NetObjectslist.size() )
           || ( g_NetObjectslist[NetItemRef]->GetNet() !=
                g_NetObjectslist[NetItemTst]->GetNet() ) )
        {
            /* End Netcode found. */
            if( erc )
            {
                /* Glabel or SheetLabel orphaned. */
                Diagnose( panel, g_NetObjectslist[NetItemRef], NULL, -1, WAR );
            }
            return;
        }
        if( IsLabelsConnected( g_NetObjectslist[NetItemRef],
                               g_NetObjectslist[NetItemTst] ) )
            erc = 0;

        //same thing, different order.
        if( IsLabelsConnected( g_NetObjectslist[NetItemTst],
                               g_NetObjectslist[NetItemRef] ) )
            erc = 0;
    }
}
