/***********************/
/* PCBNEW: netlist.cpp */
/***********************/

/*
 *  Function to read a netlist. While reading a netlist:
 *  - Load new footprints
 *  - Initialize net info
 *  - Test for missing or extra footprints
 *  - Recalculate ratsnest
 *
 *  Important remark:
 *  When reading a netlist Pcbnew must identify existing footprints (link
 * between existing footprints an components in netlist)
 *  This identification can be from 2 fields:
 *      - The reference (U2, R5 ..): this is the normal mode
 *      - The Time Stamp (Signature Temporelle), useful after a full schematic
 * reannotation
 *          because references can be changed for the same schematic.
 *  So when reading a netlist this identification ReadPcbNetlist() has
 * selection of the way to identify footprints.
 *  If we want to fully reannotate a schematic this sequence must be used
 *   SAVE your board !!!
 *   Create and read the netlist (to ensure all info is correct, mainly
 * references and time stamp)
 *   Reannotate the schematic (references will be changed, but not time stamp)
 *   Recreate and read the new netlist using the Time Stamp identification
 * (that reinit the new references)
 */
#include "fctsys.h"
#include "common.h"
#include "class_drawpanel.h"
#include "confirm.h"
#include "kicad_string.h"
#include "gestfich.h"
#include "pcbnew.h"
#include "wxPcbStruct.h"
#include "richio.h"
#include "dialog_helpers.h"

#include "dialog_netlist.h"

#include "protos.h"

// constants used by ReadPcbNetlist():
#define TESTONLY   1
#define READMODULE 0


class MODULEtoLOAD
{
public:
    wxString      m_LibName;
    wxString      m_CmpName;
    wxString      m_TimeStampPath;
    MODULEtoLOAD* m_Next;

public: MODULEtoLOAD( const wxString& libname,
                      const wxString& cmpname,
                      const wxString& timestamp_path )
    {
        m_LibName = libname;
        m_CmpName = cmpname;
        m_TimeStampPath = timestamp_path;
        m_Next = NULL;
    }


    ~MODULEtoLOAD() { };

    MODULEtoLOAD* Next() const { return (MODULEtoLOAD*) m_Next; }
    void SetNext( MODULEtoLOAD* next ) { m_Next = next; }
};


static void    SortListModulesToLoadByLibname( int NbModules );
static int     BuildFootprintsListFromNetlistFile(
    const wxString& aNetlistFullFilename,
    wxArrayString&  aBufName );
static FILE *  OpenNetlistFile( const wxString& aFullFileName );
static void    AddToList( const wxString& NameLibCmp,
                          const wxString& NameCmp,
                          const wxString& TimeStampPath );
static int     SetPadNetName( char*       Text,
                              MODULE*     Module,
                              wxTextCtrl* aMessageWindow );
static int     ReadListeModules( const wxString& CmpFullFileName,
                                 const wxString* RefCmp,
                                 const wxString* TimeStampPath,
                                 wxString&       NameModule );
static MODULE* ReadNetModule( WinEDA_PcbFrame* aFrame,
                              wxTextCtrl*      aMessageWindow,
                              const wxString&  CmpFullFileName,
                              char*            Text,
                              int*             UseFichCmp,
                              int              TstOnly,
                              bool             Select_By_Timestamp,
                              bool             aChangeFootprint );
static void LoadListeModules( WinEDA_PcbFrame* aPcbFrame );


static int           s_NbNewModules;
static MODULEtoLOAD* s_ModuleToLoad_List;

#define BUFFER_CHAR_SIZE 2048

/**
 * Function OpenNetlistFile
 *  used to open a netlist file
 */
FILE * OpenNetlistFile( const wxString& aFullFileName )
{
    if( aFullFileName.IsEmpty() )
        return FALSE;  /* No filename: exit */

    FILE * netfile = wxFopen( aFullFileName, wxT( "rt" ) );
    if( netfile == NULL )
    {
        wxString msg;
        msg.Printf( _( "Netlist file %s not found" ), GetChars( aFullFileName ) );
        DisplayError( NULL, msg );
    }

    return netfile;
}


/**
 * Function ReadPcbNetlist
 * Update footprints (load missing footprints and delete on request extra
 * footprints)
 * Update connectivity info ( Net Name list )
 * Update Reference, value and "TIME STAMP"
 * @param aNetlistFullFilename = netlist file name (*.net)
 * @param aCmpFullFileName = cmp/footprint list file name (*.cmp) if not found,
 * @param aMessageWindow  - Please document me.
 * @param aChangeFootprint - Please document me.
 * @param aDeleteBadTracks - Please document me.
 * @param aDeleteExtraFootprints - Please document me.
 * @param aSelect_By_Timestamp - Please document me.
 * @return true if Ok
 * only the netlist will be used
 *
 *  the format of the netlist is something like:
 # EESchema Netlist Version 1.0 generee le  18/5/2005-12:30:22
 *  (
 *  ( 40C08647 $noname R20 4,7K {Lib=R}
 *  (    1 VCC )
 *  (    2 MODB_1 )
 *  )
 *  ( 40C0863F $noname R18 4,7_k {Lib=R}
 *  (    1 VCC )
 *  (    2 MODA_1 )
 *  )
 *  }
 * #End
 */
bool WinEDA_PcbFrame::ReadPcbNetlist( const wxString&  aNetlistFullFilename,
                                      const wxString&  aCmpFullFileName,
                                      wxTextCtrl*      aMessageWindow,
                                      bool             aChangeFootprint,
                                      bool             aDeleteBadTracks,
                                      bool             aDeleteExtraFootprints,
                                      bool             aSelect_By_Timestamp )
{
    int     State, Comment;
    MODULE* Module = NULL;
    D_PAD*  PtPad;
    char*   Text;
    int     UseFichCmp = 1;

    FILE * netfile = OpenNetlistFile( aNetlistFullFilename );
    if( !netfile )
        return false;

    FILE_LINE_READER netlistReader( netfile, aNetlistFullFilename );
    char*   Line = netlistReader;

    SetLastNetListRead( aNetlistFullFilename );

    if( aMessageWindow )
    {
        wxString msg;
        msg.Printf( _( "Reading Netlist \"%s\"" ), GetChars( aNetlistFullFilename ) );
        aMessageWindow->AppendText( msg + wxT( "\n" ) );
    }

    // Clear undo and redo lists to avoid inconsistencies between lists
    GetScreen()->ClearUndoRedoList();

    OnModify();
    GetBoard()->m_Status_Pcb = 0;
    State = 0;
    Comment = 0;
    s_NbNewModules = 0;

    wxBusyCursor dummy;        // Shows an hourglass while calculating

    /* First, read the netlist: Build the list of footprints to load (new
     * footprints)
     */
    while( netlistReader.ReadLine() )
    {
        Text = StrPurge( Line );

        if( Comment ) /* Comments in progress */
        {
            if( ( Text = strchr( Text, '}' ) ) == NULL )
                continue;
            Comment = 0;
        }
        if( *Text == '{' ) /* Start Comment */
        {
            Comment = 1;
            if( ( Text = strchr( Text, '}' ) ) == NULL )
                continue;
        }

        if( *Text == '(' )
            State++;

        if( *Text == ')' )
            State--;

        if( State == 2 )
        {
            Module = ReadNetModule( this,
                                    aMessageWindow,
                                    aCmpFullFileName,
                                    Text,
                                    &UseFichCmp,
                                    TESTONLY,
                                    aSelect_By_Timestamp,
                                    aChangeFootprint );
            continue;
        }

        if( State >= 3 ) /* Do not analyzed pad description here. */
        {
            State--;
        }
    }

    /* Load new footprints */
    if( s_NbNewModules )
    {
        LoadListeModules( this );

        // Free module list:
        MODULEtoLOAD* item, * next_item;
        for( item = s_ModuleToLoad_List; item != NULL; item = next_item )
        {
            next_item = item->Next();
            delete item;
        }

        s_ModuleToLoad_List = NULL;
    }

    /* Second read , All footprints are on board, one must update the schematic
     * info (pad netnames) */
    netlistReader.Rewind();
    while( netlistReader.ReadLine() )
    {
        Text = StrPurge( Line );

        if( Comment )                                       /* we are reading a
                                                             * comment */
        {
            if( ( Text = strchr( Text, '}' ) ) == NULL )    /* this is the end
                                                             * of a comment */
                continue;
            Comment = 0;
        }
        if( *Text == '{' ) /* this is the beginning of a comment */
        {
            Comment = 1;
            if( ( Text = strchr( Text, '}' ) ) == NULL )
                continue;
        }

        if( *Text == '(' )
            State++;
        if( *Text == ')' )
            State--;

        if( State == 2 )
        {
            Module = ReadNetModule( this,
                                    aMessageWindow,
                                    aCmpFullFileName,
                                    Text,
                                    &UseFichCmp,
                                    READMODULE,
                                    aSelect_By_Timestamp,
                                    aChangeFootprint );
            if( Module == NULL ) // the module could not be created (perhaps
                                 // footprint not found in library)
            {
                continue;
            }
            else /* clear pads netnames */
            {
                PtPad = Module->m_Pads;
                for( ; PtPad != NULL; PtPad = (D_PAD*) PtPad->Next() )
                {
                    PtPad->SetNetname( wxEmptyString );
                }
            }
            continue;
        }

        if( State >= 3 )
        {
            if( Module )
            {
                SetPadNetName( Text, Module, aMessageWindow );
            }
            State--;
        }
    }

    // Delete footprints not found in netlist:
    if( aDeleteExtraFootprints )
    {
        wxArrayString ModuleListFromNetlist;
        /* Build list of modules in the netlist */
        int NbModulesNetListe =
            BuildFootprintsListFromNetlistFile( aNetlistFullFilename,
                                                ModuleListFromNetlist );
        if( NbModulesNetListe  )
        {
            MODULE* NextModule;
            Module = GetBoard()->m_Modules;
            bool    ask_for_confirmation = true;
            for( ; Module != NULL; Module = NextModule )
            {
                int ii;
                NextModule = Module->Next();
                if( Module->m_ModuleStatus & MODULE_is_LOCKED )
                    continue;
                for( ii = 0; ii < NbModulesNetListe; ii++ )
                {
                    if( Module->m_Reference->m_Text.CmpNoCase(
                            ModuleListFromNetlist[ii] ) == 0 )
                    {
                        break; /* Module is already in net list. */
                    }
                }

                if( ii == NbModulesNetListe )   /* Module not found in
                                                 * net list. */
                {
                    if( ask_for_confirmation )
                    {
                        ask_for_confirmation = false;
                        if( !IsOK( NULL,
                                   _( "Ok to delete footprints not in netlist?" ) ) )
                            break;
                    }
                    Module->DeleteStructure();
                }
            }
        }
    }

    /* Rebuild the connectivity */
    Compile_Ratsnest( NULL, true );

    if( GetBoard()->m_Track )
    {
        if( aDeleteBadTracks )    // Remove erroneous tracks
        {
            RemoveMisConnectedTracks( NULL );
            Compile_Ratsnest( NULL, true );
        }
    }

    GetBoard()->DisplayInfo( this );
    DrawPanel->Refresh();

    return true;
}


/* Load the description of a footprint, net list type Pcbnew
 * and update the corresponding module.
 *
 * If TstOnly == 0 if the module does not exist, it is responsible
 * If TstOnly! = 0 if the module does not exist, it is added to the list
 * Load modules has
 * Text contains the first line of description
 * UseFichCmp is a flag
 * If! = 0, file components. CMP will be used
 * Is reset to 0 if the file does not exist
 *
 * Analyze lines like:
 * ($ 40C08647 noname R20 4.7 K Lib = (R)
 * (1 VCC)
 * (2 MODB_1)
 */
MODULE* ReadNetModule( WinEDA_PcbFrame* aFrame,
                       wxTextCtrl*      aMessageWindow,
                       const wxString&  aCmpFullFileName,
                       char*            Text,
                       int*             UseFichCmp,
                       int              TstOnly,
                       bool             aSelect_By_Timestamp,
                       bool             aChangeFootprint )
{
    MODULE*  Module;
    char*    text;
    wxString TimeStampPath;
    wxString TextNameLibMod;
    wxString TextValeur;
    wxString TextCmpName;
    wxString NameLibCmp;
    int      Error = 0;
    char     Line[1024];
    bool     Found;

    strcpy( Line, Text );

    TextValeur = wxT( "~" );

    if( ( text = strtok( Line, " ()\t\n" ) ) == NULL )
        Error = 1;
    else
        TimeStampPath = CONV_FROM_UTF8( text );

    if( ( text = strtok( NULL, " ()\t\n" ) ) == NULL )
        Error = 1;
    else
        TextNameLibMod = CONV_FROM_UTF8( text );

    if( ( text = strtok( NULL, " ()\t\n" ) ) == NULL )
        Error = 1;
    else
        TextCmpName = CONV_FROM_UTF8( text );

    if( ( text = strtok( NULL, " ()\t\n" ) ) == NULL )
        Error = -1;
    else
        TextValeur = CONV_FROM_UTF8( text );

    if( Error > 0 )
        return NULL;

    /* Test if module is already loaded. */
    Module = aFrame->GetBoard()->m_Modules;
    MODULE* NextModule;

    for( Found = false; Module != NULL; Module = NextModule )
    {
        NextModule = Module->Next();
        if( aSelect_By_Timestamp ) /* identification by time stamp */
        {
            if( TimeStampPath.CmpNoCase( Module->m_Path ) == 0 )
                Found = true;
        }
        else    /* identification by Reference */
        {
            if( TextCmpName.CmpNoCase( Module->m_Reference->m_Text ) == 0 )
                Found = true;
        }
        if( Found ) // test footprint matching for existing modules:  current
                    // m_LibRef and module name in netlist must match
        {
            if( TstOnly != TESTONLY )
            {
                NameLibCmp = TextNameLibMod;    // Use footprint name from netlist
                if( *UseFichCmp )               // Try to get footprint name from .cmp file
                {
                    if( aSelect_By_Timestamp )
                    {
                        *UseFichCmp = ReadListeModules( aCmpFullFileName,
                                                        NULL,
                                                        &TimeStampPath,
                                                        NameLibCmp );
                    }
                    else
                    {
                        *UseFichCmp = ReadListeModules( aCmpFullFileName,
                                                        &TextCmpName,
                                                        NULL,
                                                        NameLibCmp );
                    }
                }

                /* Module mismatch: current module and module specified in
                 * net list are  different.
                 */
                if( Module->m_LibRef.CmpNoCase( NameLibCmp ) != 0 )
                {
                    if( aChangeFootprint )   // footprint exchange allowed.
                    {
                        MODULE* NewModule =
                            aFrame->Get_Librairie_Module( wxEmptyString,
                                                          NameLibCmp,
                                                          true );
                        if( NewModule )  /* Change old module to the new module
                                          * (and delete the old one) */
                        {
                            aFrame->Exchange_Module( Module, NewModule, NULL );
                            Module = NewModule;
                        }
                    }
                    else
                    {
                        wxString msg;
                        msg.Printf( _( "Component \"%s\": Mismatch! module \
is [%s] and netlist said [%s]\n" ),
                                    GetChars( TextCmpName ),
                                    GetChars( Module->m_LibRef ),
                                    GetChars( NameLibCmp ) );

                        if( aMessageWindow )
                            aMessageWindow->AppendText( msg );
                    }
                }
            }

            break;
        }
    }

    if( Module == NULL )    /* a new module must be loaded from libs */
    {
        NameLibCmp = TextNameLibMod;    // Use footprint name from netlist
        if( *UseFichCmp )               // Try to get footprint name from .cmp file
        {
            if( aSelect_By_Timestamp == 1 )
            {
                *UseFichCmp = ReadListeModules( aCmpFullFileName,
                                                NULL,
                                                &TimeStampPath,
                                                NameLibCmp );
            }
            else
            {
                *UseFichCmp = ReadListeModules( aCmpFullFileName,
                                                &TextCmpName,
                                                NULL,
                                                NameLibCmp );
            }
        }


        if( TstOnly == TESTONLY )
            AddToList( NameLibCmp, TextCmpName, TimeStampPath );
        else
        {
            if( aMessageWindow )
            {
                wxString msg;
                msg.Printf( _( "Component [%s] not found" ),
                            GetChars( TextCmpName ) );
                aMessageWindow->AppendText( msg + wxT( "\n" ) );
            }
        }
        return NULL;    /* The module could not be loaded. */
    }

    /* Fields update ( reference, value and "Time Stamp") */
    Module->m_Reference->m_Text = TextCmpName;
    Module->m_Value->m_Text     = TextValeur;
    Module->m_Path = TimeStampPath;

    return Module;
}


/**
 * Function SetPadNetName
 *  Update a pad netname in a given footprint
 *  @param Text = Text from netlist (format: (pad = net) )
 *  @param Module = the given footprint
 *  @param aMessageWindow = a wxTextCtrl to print error and warning message
 * (can be NULL)
 */
int SetPadNetName( char*       Text,
                   MODULE*     Module,
                   wxTextCtrl* aMessageWindow )
{
    D_PAD*   pad;
    char*    TextPinName, * TextNetName;
    int      Error = 0;
    bool     trouve;
    char     Line[256];
    wxString Msg;

    if( Module == NULL )
        return 0;

    strcpy( Line, Text );

    if( ( TextPinName = strtok( Line, " ()\t\n" ) ) == NULL )
        Error = 1;
    if( ( TextNetName = strtok( NULL, " ()\t\n" ) ) == NULL )
        Error = 1;
    if( Error )
        return 0;

    pad = Module->m_Pads; trouve = FALSE;
    for( ; pad != NULL; pad = (D_PAD*) pad->Next() )
    {
        if( strnicmp( TextPinName, pad->m_Padname, 4 ) == 0 )
        {
            trouve = true;
            if( *TextNetName != '?' )
                pad->SetNetname( CONV_FROM_UTF8( TextNetName ) );
            else
                pad->SetNetname( wxEmptyString );
        }
    }

    if( !trouve )
    {
        if( aMessageWindow )
        {
            wxString pin_name = CONV_FROM_UTF8( TextPinName );
            Msg.Printf( _( "Module [%s]: Pad [%s] not found" ),
                        GetChars( Module->m_Reference->m_Text ),
                        GetChars( pin_name ) );
            aMessageWindow->AppendText( Msg + wxT( "\n" ) );
        }
    }

    return trouve;
}


/**
 * build and shows a list of existing modules on board
 * The user can select a module from this list
 * @return a pointer to the selected module or NULL
 */
MODULE* WinEDA_PcbFrame::ListAndSelectModuleName( void )
{
    MODULE* Module;

    if( GetBoard()->m_Modules == NULL )
    {
        DisplayError( this, _( "No Modules" ) );
        return 0;
    }

    wxArrayString listnames;
    Module = (MODULE*) GetBoard()->m_Modules;
    for( ; Module != NULL; Module = (MODULE*) Module->Next() )
        listnames.Add( Module->m_Reference->m_Text );

    WinEDAListBox dlg( this, _( "Components" ), listnames, wxEmptyString );

    if( dlg.ShowModal() != wxID_OK )
        return NULL;

    wxString ref = dlg.GetTextSelection();
    Module = (MODULE*) GetBoard()->m_Modules;
    for( ; Module != NULL; Module = Module->Next() )
    {
        if( Module->m_Reference->m_Text == ref )
            break;
    }

    return Module;
}


/**
 * Function TestFor_Duplicate_Missing_And_Extra_Footprints
 * Build a list from the given board and netlist :
 *  1 - for duplicate footprints on board
 *  2 - for missing footprints
 *  3 - for footprints not in netlist
 * @param aFrame = current active frame
 * @param aNetlistFullFilename = the given netlist
 * @param aPcb = the given board
 */
void TestFor_Duplicate_Missing_And_Extra_Footprints( wxWindow*       aFrame,
                                                     const wxString& aNetlistFullFilename,
                                                     BOARD*          aPcb )
#define MAX_LEN_TXT 32
{
    int ii;
    MODULE*           Module, * pt_aux;
    int               NbModulesNetListe, nberr = 0;
    WinEDA_TextFrame* List;
    wxArrayString     ModuleListFromNetlist;

    if( aPcb->m_Modules == NULL )
    {
        DisplayInfoMessage( aFrame, _( "No modules" ), 10 );
        return;
    }

    /* Build the list of references of the net list modules. */

    NbModulesNetListe =
        BuildFootprintsListFromNetlistFile( aNetlistFullFilename,
                                            ModuleListFromNetlist );
    if( NbModulesNetListe < 0 )
        return;  /* File not found */

    if( NbModulesNetListe == 0 )
    {
        DisplayError( aFrame, _( "No modules in NetList" ), 10 );
        return;
    }

    List = new WinEDA_TextFrame( aFrame, _( "Check Modules" ) );

    /* Search for duplicate footprints. */
    List->Append( _( "Duplicates" ) );

    Module = aPcb->m_Modules;

    for( ; Module != NULL; Module = Module->Next() )
    {
        pt_aux = Module->Next();
        for( ; pt_aux != NULL; pt_aux = pt_aux->Next() )
        {
            if( Module->m_Reference->m_Text.CmpNoCase( pt_aux->m_Reference->
                                                       m_Text ) == 0 )
            {
                List->Append( Module->m_Reference->m_Text );
                nberr++;
                break;
            }
        }
    }

    /* Search for the missing module by the net list. */
    List->Append( _( "Lack:" ) );

    for( ii = 0; ii < NbModulesNetListe; ii++ )
    {
        Module = (MODULE*) aPcb->m_Modules;
        for( ; Module != NULL; Module = Module->Next() )
        {
            if( Module->m_Reference->m_Text.CmpNoCase(
                    ModuleListFromNetlist[ii] ) == 0 )
            {
                break;
            }
        }

        if( Module == NULL )    // Module missing, not found in board
        {
            List->Append( ModuleListFromNetlist[ii] );
            nberr++;
        }
    }

    /* Search for modules not in net list. */
    List->Append( _( "Not in Netlist:" ) );

    Module = (MODULE*) aPcb->m_Modules;
    for( ; Module != NULL; Module = Module->Next() )
    {
        for( ii = 0; ii < NbModulesNetListe; ii++ )
        {
            if( Module->m_Reference->m_Text.CmpNoCase(
                    ModuleListFromNetlist[ii] ) == 0 )
            {
                break; /* Module is in net list. */
            }
        }

        if( ii == NbModulesNetListe )   /* Module not found in netlist */
        {
            List->Append( Module->m_Reference->m_Text );
            nberr++;
        }
    }

    List->ShowModal(); List->Destroy();
}


/**
 * Function BuildFootprintsListFromNetlistFile
 *  Fill BufName with footprints names read from the netlist.
 * @param aNetlistFullFilename = netlist file name
 * @param aBufName = wxArrayString to fill with footprint names
 * @return Footprint count, or -1 if netlist file cannot opened
 */
int BuildFootprintsListFromNetlistFile( const wxString& aNetlistFullFilename,
                                        wxArrayString&  aBufName )
{
    int  nb_modules_lus;
    int  State, Comment;
    char * Text, * LibModName;

    FILE * netfile = OpenNetlistFile( aNetlistFullFilename );
    if( !netfile )
        return -1;

    FILE_LINE_READER netlistReader( netfile, aNetlistFullFilename );
    char* Line = netlistReader;

    State = 0; Comment = 0;
    nb_modules_lus = 0;

    while( netlistReader.ReadLine( ) )
    {
        Text = StrPurge( Line );
        if( Comment )
        {
            if( ( Text = strchr( Text, '}' ) ) == NULL )
                continue;
            Comment = 0;
        }
        if( *Text == '{' ) /* Comments. */
        {
            Comment = 1;
            if( ( Text = strchr( Text, '}' ) ) == NULL )
                continue;
        }

        if( *Text == '(' )
            State++;
        if( *Text == ')' )
            State--;

        if( State == 2 )
        {
            int Error = 0;
            if( strtok( Line, " ()\t\n" ) == NULL )
                Error = 1;  /* TimeStamp */
            if( ( LibModName = strtok( NULL, " ()\t\n" ) ) == NULL )
                Error = 1;
            /* Load the name of the component. */
            if( ( Text = strtok( NULL, " ()\t\n" ) ) == NULL )
                Error = 1;
            nb_modules_lus++;
            aBufName.Add( CONV_FROM_UTF8( Text ) );
            continue;
        }

        if( State >= 3 )
        {
            State--;
        }
    }

    return nb_modules_lus;
}


/*
 * Get the file CMP giving the equivalence modules / components
 * Returns:
 * If this file exists returns:
 * 1 and the module name in NameModule
 * -1 If not found in module file
 * Otherwise 0;
 *
 * Call settings:
 * RefCmp (NULL if selection by TimeStamp)
 * TimeStamp (time signature if it exists, NULL otherwise)
 * Pointer to the buffer receiving the name of the module
 *
 * Example file:
 *
 * Cmp-Mod V01 Genere by Pcbnew 29/10/2003-13: 11:6 *
 *  BeginCmp
 *  TimeStamp = /322D3011;
 *  Reference = BUS1;
 *  ValeurCmp = BUSPC;
 *  IdModule  = BUS_PC;
 *  EndCmp
 *
 *  BeginCmp
 *  TimeStamp = /32307DE2/AA450F67;
 *  Reference = C1;
 *  ValeurCmp = 47uF;
 *  IdModule  = CP6;
 *  EndCmp
 *
 */
int ReadListeModules( const wxString& CmpFullFileName, const wxString* RefCmp,
                      const wxString* TimeStamp, wxString& NameModule )
{
    wxString refcurrcmp, timestamp, idmod;
    char*    ptcar;
    FILE*    FichCmp;

    if( (RefCmp == NULL) && (TimeStamp == 0) )
        return 0;

    FichCmp = wxFopen( CmpFullFileName, wxT( "rt" ) );
    if( FichCmp == NULL )
    {
        wxString msg;
        msg.Printf( _( "File <%s> not found, use Netlist for lib module selection" ),
                    GetChars( CmpFullFileName ) );
        DisplayError( NULL, msg, 20 );
        return 0;
    }

    FILE_LINE_READER netlistReader( FichCmp, CmpFullFileName );
    char* Line = netlistReader;

    while( netlistReader.ReadLine() )
    {
        if( strnicmp( Line, "BeginCmp", 8 ) != 0 )
            continue;

        /* Begin component description. */
        refcurrcmp.Empty();
        idmod.Empty();
        timestamp.Empty();
        while( netlistReader.ReadLine() )
        {
            if( strnicmp( Line, "EndCmp", 6 ) == 0 )
                break;

            if( strnicmp( Line, "Reference =", 11 ) == 0 )
            {
                ptcar = Line + 11;
                ptcar = strtok( ptcar, " =;\t\n" );
                if( ptcar )
                    refcurrcmp = CONV_FROM_UTF8( ptcar );
                continue;
            }

            if( strnicmp( Line, "IdModule  =", 11 ) == 0 )
            {
                ptcar = Line + 11;
                ptcar = strtok( ptcar, " =;\t\n" );
                if( ptcar )
                    idmod = CONV_FROM_UTF8( ptcar );
                continue;
            }
            if( strnicmp( Line, "TimeStamp =", 11 ) == 0 )
            {
                ptcar = Line + 11;
                ptcar = strtok( ptcar, " =;\t\n" );
                if( ptcar )
                    timestamp = CONV_FROM_UTF8( ptcar );
            }
        }

        /* Check if component read is valid. */
        if( RefCmp )
        {
            if( RefCmp->CmpNoCase( refcurrcmp ) == 0 )  // Found!
            {
                NameModule = idmod;
                return 1;
            }
        }
        else if( TimeStamp )
        {
            if( TimeStamp->CmpNoCase( timestamp ) == 0
                && !timestamp.IsEmpty() )                // Found
            {
                NameModule = idmod;
                return 1;
            }
        }
    }

    return -1;
}


/* This function add to the current list of footprints found in netlist
 *  a new MODULEtoLOAD item (a descriptor of footprints)
 */
void AddToList( const wxString& NameLibCmp, const wxString& CmpName, const wxString& path )
{
    MODULEtoLOAD* NewMod;

    NewMod = new MODULEtoLOAD( NameLibCmp, CmpName, path );
    NewMod->SetNext( s_ModuleToLoad_List );
    s_ModuleToLoad_List = NewMod;
    s_NbNewModules++;
}


/* Load new modules from library.
 * If a module is being loaded it is duplicated, which avoids reading
 * unnecessary library.
 */
void LoadListeModules( WinEDA_PcbFrame* aPcbFrame )
{
    MODULEtoLOAD* ref, * cmp;
    int           ii;
    MODULE*       Module = NULL;
    wxPoint       ModuleBestPosition;

    if( s_NbNewModules == 0 )
        return;

    SortListModulesToLoadByLibname( s_NbNewModules );
    ref = cmp = s_ModuleToLoad_List;

    // Calculate the footprint "best" position:
    if( aPcbFrame->SetBoardBoundaryBoxFromEdgesOnly() )
    {
        ModuleBestPosition.x =
            aPcbFrame->GetBoard()->m_BoundaryBox.GetRight() + 5000;
        ModuleBestPosition.y =
            aPcbFrame->GetBoard()->m_BoundaryBox.GetBottom() + 10000;
    }
    else
        ModuleBestPosition = wxPoint( 0, 0 );

    for( ii = 0; ii < s_NbNewModules; ii++, cmp = cmp->Next() )
    {
        if( (ii == 0) || ( ref->m_LibName != cmp->m_LibName) )
        {
            /* New footprint : must be loaded from a library */
            Module = aPcbFrame->Get_Librairie_Module( wxEmptyString,
                                                      cmp->m_LibName,
                                                      FALSE );
            ref    = cmp;
            if( Module == NULL )
            {
                wxString msg;
                msg.Printf( _( "Component [%s]: footprint <%s> not found" ),
                            GetChars( cmp->m_CmpName ),
                            GetChars( cmp->m_LibName ) );
                DisplayError( NULL, msg );
                continue;
            }
            Module->SetPosition( ModuleBestPosition );

            /* Update schematic links : reference "Time Stamp" and schematic
             *hierarchical path */
            Module->m_Reference->m_Text = cmp->m_CmpName;
            Module->m_TimeStamp = GetTimeStamp();
            Module->m_Path = cmp->m_TimeStampPath;
        }
        else
        {
            /* Footprint already loaded from a library, duplicate it (faster)
             */
            MODULE* newmodule;
            if( Module == NULL )
                continue;            /* Module does not exist in library. */

            newmodule = new MODULE( aPcbFrame->GetBoard() );
            newmodule->Copy( Module );

            aPcbFrame->GetBoard()->Add( newmodule, ADD_APPEND );

            Module = newmodule;
            Module->m_Reference->m_Text = cmp->m_CmpName;
            Module->m_TimeStamp = GetTimeStamp();
            Module->m_Path = cmp->m_TimeStampPath;
        }
    }
}


/* Routines used by qsort to sort a load module. */
static int SortByLibName( MODULEtoLOAD** ref, MODULEtoLOAD** cmp )
{
    int ii = (*ref)->m_LibName.CmpNoCase( (*cmp)->m_LibName );

    return ii;
}


/* Sort the module list in alphabetical order by module name.
 */
void SortListModulesToLoadByLibname( int NbModules )
{
    MODULEtoLOAD** base_list, * item;
    int            ii;

    base_list = (MODULEtoLOAD**) MyMalloc( NbModules * sizeof(MODULEtoLOAD*) );

    for( ii = 0, item = s_ModuleToLoad_List; ii < NbModules; ii++ )
    {
        base_list[ii] = item;
        item = item->Next();
    }

    qsort( base_list, NbModules, sizeof(MODULEtoLOAD*),
           ( int ( * )( const void*, const void* ) )SortByLibName );

    s_ModuleToLoad_List = *base_list;

    for( ii = 0; ii < NbModules - 1; ii++ )
    {
        item = base_list[ii];
        item->SetNext( base_list[ii + 1] );
    }

    item = base_list[ii];
    item->SetNext( NULL );

    free( base_list );
}
