/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2008-2015 Wayne Stambaugh <stambaughw@verizon.net>
 * Copyright (C) 2004-2015 KiCad Developers, see change_log.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file class_library.cpp
 */

#include <fctsys.h>
#include <kiface_i.h>
#include <gr_basic.h>
#include <macros.h>
#include <kicad_string.h>
#include <gestfich.h>
#include <eda_doc.h>
#include <wxstruct.h>
#include <richio.h>
#include <config_params.h>
#include <wildcards_and_files_ext.h>
//#include <richio.h>

#include <general.h>
#include <class_library.h>

#include <boost/foreach.hpp>

#include <wx/tokenzr.h>
#include <wx/regex.h>

#define duplicate_name_msg  \
    _(  "Library '%s' has duplicate entry name '%s'.\n" \
        "This may cause some unexpected behavior when loading components into a schematic." )


PART_LIB::PART_LIB( int aType, const wxString& aFileName ) :
    // start @ != 0 so each additional library added
    // is immediately detectable, zero would not be.
    m_mod_hash( PART_LIBS::s_modify_generation )
{
    type = aType;
    isModified = false;
    timeStamp = 0;
    isCache = false;
    timeStamp = wxDateTime::Now();

    fileName = aFileName;

    if( !fileName.IsOk() )
        fileName = wxT( "unnamed.lib" );
}


PART_LIB::~PART_LIB()
{
    // When the library is destroyed, all of the alias objects on the heap should be deleted.
    for( LIB_ALIAS_MAP::iterator it = m_amap.begin();  it != m_amap.end();  ++it )
    {
        wxLogTrace( traceSchLibMem, wxT( "Removing alias %s from library %s." ),
                    GetChars( it->second->GetName() ), GetChars( GetLogicalName() ) );
        LIB_PART* part = it->second->GetPart();
        LIB_ALIAS* alias = it->second;
        delete alias;

        // When the last alias of a part is destroyed, the part is no longer required and it
        // too is destroyed.
        if( part && part->GetAliasCount() == 0 )
            delete part;
    }

    m_amap.clear();
}


void PART_LIB::GetEntryNames( wxArrayString& aNames, bool aSort, bool aMakeUpperCase )
{
    for( LIB_ALIAS_MAP::iterator it = m_amap.begin();  it!=m_amap.end();  it++ )
    {
        if( aMakeUpperCase )
        {
            wxString tmp = (*it).first;
            tmp.MakeUpper();
            aNames.Add( tmp );
        }
        else
        {
            aNames.Add( (*it).first );
        }
    }

    if( aSort )
        aNames.Sort();
}


/**
 * Function sortFunction
 * simple function used as comparator to sort a std::vector<wxArrayString>&.
 *
 * @param aItem1 is the first comparison parameter.
 * @param aItem2 is the second.
 * @return bool - which item should be put first in the sorted list.
 */
bool sortFunction( wxArrayString aItem1, wxArrayString aItem2 )
{
    return( aItem1.Item( 0 ) < aItem2.Item( 0 ) );
}


void PART_LIB::SearchEntryNames( std::vector<wxArrayString>& aNames,
                                    const wxString& aNameSearch,
                                    const wxString& aKeySearch,
                                    bool aSort )
{
    for( LIB_ALIAS_MAP::iterator it = m_amap.begin();  it != m_amap.end();  ++it )
    {
        if( !!aKeySearch && KeyWordOk( aKeySearch, it->second->GetKeyWords() ) )
        {
            wxArrayString item;

            item.Add( it->first );
            item.Add( GetLogicalName() );
            aNames.push_back( item );
        }

        if( !aNameSearch.IsEmpty() &&
                WildCompareString( aNameSearch, it->second->GetName(), false ) )
        {
            wxArrayString item;

            item.Add( it->first );
            item.Add( GetLogicalName() );
            aNames.push_back( item );
        }
    }

    if( aSort )
        std::sort( aNames.begin(), aNames.end(), sortFunction );
}


void PART_LIB::SearchEntryNames( wxArrayString& aNames, const wxRegEx& aRe, bool aSort )
{
    if( !aRe.IsValid() )
        return;

    LIB_ALIAS_MAP::iterator it;

    for( it = m_amap.begin();  it!=m_amap.end();  it++ )
    {
        if( aRe.Matches( it->second->GetKeyWords() ) )
            aNames.Add( it->first );
    }

    if( aSort )
        aNames.Sort();
}


bool PART_LIB::Conflicts( LIB_PART* aPart )
{
    wxCHECK_MSG( aPart != NULL, false,
                 wxT( "Cannot test NULL component for conflicts in library " ) + GetName() );

    for( size_t i=0;  i<aPart->m_aliases.size();  i++ )
    {
        LIB_ALIAS_MAP::iterator it = m_amap.find( aPart->m_aliases[i]->GetName() );

        if( it != m_amap.end() )
            return true;
    }

    return false;
}


LIB_ALIAS* PART_LIB::FindEntry( const wxString& aName )
{
    LIB_ALIAS_MAP::iterator it = m_amap.find( aName );

    if( it != m_amap.end() )
        return it->second;

    return NULL;
}


LIB_ALIAS* PART_LIB::GetFirstEntry()
{
    if( m_amap.size() )
        return m_amap.begin()->second;
    else
        return NULL;
}


LIB_PART* PART_LIB::FindPart( const wxString& aName )
{
#if 0 && defined(DEBUG)
    if( !aName.Cmp( wxT( "TI_STELLARIS_BOOSTERPACK" ) ) )
    {
        int breakhere = 1;
        (void) breakhere;
    }
#endif

    if( LIB_ALIAS* alias = FindEntry( aName ) )
    {
        return alias->GetPart();
    }

    return NULL;
}


bool PART_LIB::AddAlias( LIB_ALIAS* aAlias )
{
    wxASSERT( aAlias );

#if defined(DEBUG) && 0
    if( !aAlias->GetName().Cmp( wxT( "TI_STELLARIS_BOOSTERPACK" ) ) )
    {
        int breakhere = 1;
        (void) breakhere;
    }
#endif

    LIB_ALIAS_MAP::iterator it = m_amap.find( aAlias->GetName() );

    if( it != m_amap.end() )
    {
        wxString msg;

        msg.Printf( _( "Cannot add duplicate alias '%s' to library '%s'." ),
                    GetChars( aAlias->GetName() ),
                    GetChars( fileName.GetName() ) );
        return false;
    }

    wxString name = aAlias->GetName();

    m_amap[ name ] = aAlias;
    isModified = true;
    ++m_mod_hash;

    return true;
}


bool PART_LIB::AddPart( LIB_PART* aPart )
{
    // Conflict detection: See if already existing aliases exist,
    // and if yes, ask user for continue or abort
    // Special case: if the library is the library cache of the project,
    // old aliases are always removed to avoid conflict,
    //      and user is not prompted )
    if( Conflicts( aPart ) && !IsCache() )
    {
        wxFAIL_MSG( wxT( "Cannot add component <" ) + aPart->GetName() +
                    wxT( "> to library <" ) + GetName() + wxT( "> due to name conflict." ) );
        return false;
    }

    // add a clone, not the caller's copy
    LIB_PART* my_part = new LIB_PART( *aPart, this );

    for( size_t i = 0; i < my_part->m_aliases.size(); i++ )
    {
        wxString aliasname = my_part->m_aliases[i]->GetName();

        if( LIB_ALIAS* alias = FindAlias( aliasname ) )
            RemoveEntry( alias );

        m_amap[ aliasname ] = my_part->m_aliases[i];
    }

    isModified = true;
    ++m_mod_hash;

    return true;
}


LIB_ALIAS* PART_LIB::RemoveEntry( LIB_ALIAS* aEntry )
{
    wxCHECK_MSG( aEntry != NULL, NULL, wxT( "NULL pointer cannot be removed from library." ) );

    LIB_ALIAS_MAP::iterator it = m_amap.find( aEntry->GetName() );

    if( it == m_amap.end() )
        return NULL;

    // If the entry pointer doesn't match the name it is mapped to in the library, we
    // have done something terribly wrong.
    wxCHECK_MSG( *it->second == aEntry, NULL,
                 wxT( "Pointer mismatch while attempting to remove entry <" ) +
                 aEntry->GetName() + wxT( "> from library <" ) + GetName() + wxT( ">." ) );

    LIB_ALIAS*  alias = aEntry;
    LIB_PART*   part = alias->GetPart();

    alias = part->RemoveAlias( alias );

    if( !alias )
    {
        delete part;

        if( m_amap.size() > 1 )
        {
            LIB_ALIAS_MAP::iterator next = it;
            next++;

            if( next == m_amap.end() )
                next = m_amap.begin();

            alias = next->second;
        }
    }

    m_amap.erase( it );
    isModified = true;
    ++m_mod_hash;
    return alias;
}


LIB_PART* PART_LIB::ReplacePart( LIB_PART* aOldPart, LIB_PART* aNewPart )
{
    wxASSERT( aOldPart != NULL );
    wxASSERT( aNewPart != NULL );

    /* Remove the old root component.  The component will automatically be deleted
     * when all it's aliases are deleted.  Do not place any code that accesses
     * aOldPart inside this loop that gets evaluated after the last alias is
     * removed in RemoveEntry().  Failure to heed this warning will result in a
     * segfault.
     */
    size_t i = aOldPart->m_aliases.size();

    while( i > 0 )
    {
        i -= 1;
        RemoveEntry( aOldPart->m_aliases[ i ] );
    }

    LIB_PART* my_part = new LIB_PART( *aNewPart, this );

    // Add new aliases to library alias map.
    for( i = 0; i < my_part->m_aliases.size(); i++ )
    {
        wxString aname = my_part->m_aliases[ i ]->GetName();
        m_amap[ aname ] = my_part->m_aliases[ i ];
    }

    isModified = true;
    ++m_mod_hash;
    return my_part;
}


LIB_ALIAS* PART_LIB::GetNextEntry( const wxString& aName )
{
    if( m_amap.empty() )
        return NULL;

    LIB_ALIAS_MAP::iterator it = m_amap.find( aName );

    it++;

    if( it == m_amap.end() )
        it = m_amap.begin();

    return it->second;
}


LIB_ALIAS* PART_LIB::GetPreviousEntry( const wxString& aName )
{
    if( m_amap.empty() )
        return NULL;

    LIB_ALIAS_MAP::iterator it = m_amap.find( aName );

    if( it == m_amap.begin() )
        it = m_amap.end();

    it--;

    return it->second;
}


bool PART_LIB::Load( wxString& aErrorMsg )
{
    FILE*          file;
    char*          line;
    wxString       msg;

    if( fileName.GetFullPath().IsEmpty() )
    {
        aErrorMsg = _( "The component library file name is not set." );
        return false;
    }

    file = wxFopen( fileName.GetFullPath(), wxT( "rt" ) );

    if( file == NULL )
    {
        aErrorMsg = _( "The file could not be opened." );
        return false;
    }

    FILE_LINE_READER reader( file, fileName.GetFullPath() );

    if( !reader.ReadLine() )
    {
        aErrorMsg = _( "The file is empty!" );
        return false;
    }

    // There is no header if this is a symbol library.
    if( type == LIBRARY_TYPE_EESCHEMA )
    {
        wxString tmp;

        line = reader.Line();

        header = FROM_UTF8( line );

        wxStringTokenizer tkn( header );

        /*
         * The file header (first line) in library versions 2.0 and lower
         * apparently started with EESchema-LIB.  Sometime after 2.0, it
         * was changed to EESchema-LIBRARY.  Therefore, the test for
         * EESchema-LIB will work in both cases.  Don't change this unless
         * backwards compatibility is no longer required.
         */
        if( !tkn.HasMoreTokens()
            || !tkn.GetNextToken().Upper().StartsWith(wxT( "EESCHEMA-LIB" ) ) )
        {
            aErrorMsg = _( "The file is NOT an Eeschema library!" );
            return false;
        }

        if( !tkn.HasMoreTokens() )
        {
            aErrorMsg = _( "The file header is missing version and time stamp information." );
            return false;
        }

        if( tkn.GetNextToken() != wxT( "Version" ) || !tkn.HasMoreTokens() )
        {
            aErrorMsg = wxT( "The file header version information is invalid." );
            return false;
        }

        long major, minor;
        wxStringTokenizer vers( tkn.GetNextToken(), wxT( "." ) );

        if( !vers.HasMoreTokens() || !vers.GetNextToken().ToLong( &major )
            || major < 1L || !vers.HasMoreTokens()
            || !vers.GetNextToken().ToLong( & minor ) || minor < 0L
            || minor > 99 )
        {
#if 0       // Note for developers:
            // Not sure this warning is very useful: old designs *must* be always loadable
            wxLogWarning( wxT(
                "The component library '%s' header version "
                "number is invalid.\n\nIn future versions of Eeschema this library may not "
                "load correctly.  To resolve this problem open the library in the library "
                "editor and save it.  If this library is the project cache library, save "
                "the current schematic." ),
                GetChars( GetName() ) );
#endif
        }
        else
        {
            versionMajor = (int) major;
            versionMinor = (int) minor;
        }
    }

    while( reader.ReadLine() )
    {
        line = reader.Line();

        if( type == LIBRARY_TYPE_EESCHEMA && strnicmp( line, "$HEADER", 7 ) == 0 )
        {
            if( !LoadHeader( reader ) )
            {
                aErrorMsg = _( "An error occurred attempting to read the header." );
                return false;
            }

            continue;
        }

        if( strnicmp( line, "DEF", 3 ) == 0 )
        {
            // Read one DEF/ENDDEF part entry from library:
            LIB_PART* part = new LIB_PART( wxEmptyString, this );

            if( part->Load( reader, msg ) )
            {
                // Check for duplicate entry names and warn the user about
                // the potential conflict.
                if( FindEntry( part->GetName() ) != NULL )
                {
                    wxString msg = duplicate_name_msg;

                    wxLogWarning( msg,
                                  GetChars( fileName.GetName() ),
                                  GetChars( part->GetName() ) );
                }

                LoadAliases( part );
            }
            else
            {
                wxLogWarning( _( "Library '%s' component load error %s." ),
                              GetChars( fileName.GetName() ),
                              GetChars( msg ) );
                msg.Clear();
                delete part;
            }
        }
    }

    ++m_mod_hash;

    return true;
}


void PART_LIB::LoadAliases( LIB_PART* aPart )
{
    wxCHECK_RET( aPart, wxT( "Cannot load aliases of NULL part.  Bad programmer!" ) );

    for( size_t i = 0; i < aPart->m_aliases.size(); i++ )
    {
        if( FindEntry( aPart->m_aliases[i]->GetName() ) != NULL )
        {
            wxString msg = duplicate_name_msg;

            wxLogError( msg,
                        GetChars( fileName.GetName() ),
                        GetChars( aPart->m_aliases[i]->GetName() ) );
        }

        wxString aname = aPart->m_aliases[i]->GetName();
        m_amap[ aname ] = aPart->m_aliases[i];
    }
}


bool PART_LIB::LoadHeader( LINE_READER& aLineReader )
{
    char* line, * text, * data;

    while( aLineReader.ReadLine() )
    {
        line = (char*) aLineReader;

        text = strtok( line, " \t\r\n" );
        data = strtok( NULL, " \t\r\n" );

        if( stricmp( text, "TimeStamp" ) == 0 )
            timeStamp = atol( data );

        if( stricmp( text, "$ENDHEADER" ) == 0 )
            return true;
    }

    return false;
}


bool PART_LIB::LoadDocs( wxString& aErrorMsg )
{
    int        lineNumber = 0;
    char       line[8000], * name, * text;
    LIB_ALIAS* entry;
    FILE*      file;
    wxString   msg;
    wxFileName fn = fileName;

    fn.SetExt( DOC_EXT );

    file = wxFopen( fn.GetFullPath(), wxT( "rt" ) );

    if( file == NULL )
    {
        aErrorMsg.Printf( _( "Could not open component document library file '%s'." ),
                          GetChars( fn.GetFullPath() ) );
        return false;
    }

    if( GetLine( file, line, &lineNumber, sizeof(line) ) == NULL )
    {
        aErrorMsg.Printf( _( "Part document library file '%s' is empty." ),
                          GetChars( fn.GetFullPath() ) );
        fclose( file );
        return false;
    }

    if( strnicmp( line, DOCFILE_IDENT, 10 ) != 0 )
    {
        aErrorMsg.Printf( _( "File '%s' is not a valid component library document file." ),
                          GetChars( fn.GetFullPath() ) );
        fclose( file );
        return false;
    }

    while( GetLine( file, line, &lineNumber, sizeof(line) ) )
    {
        if( strncmp( line, "$CMP", 4 ) != 0 )
        {
            aErrorMsg.Printf( wxT( "$CMP command expected in line %d, aborted." ), lineNumber );
            fclose( file );
            return false;
        }

        // Read one $CMP/$ENDCMP part entry from library:
        name = strtok( line + 5, "\n\r" );

        wxString cmpname = FROM_UTF8( name );

        entry = FindEntry( cmpname );

        while( GetLine( file, line, &lineNumber, sizeof(line) ) )
        {
            if( strncmp( line, "$ENDCMP", 7 ) == 0 )
                break;

            text = strtok( line + 2, "\n\r" );

            if( entry )
            {
                switch( line[0] )
                {
                case 'D':
                    entry->SetDescription( FROM_UTF8( text ) );
                    break;

                case 'K':
                    entry->SetKeyWords( FROM_UTF8( text ) );
                    break;

                case 'F':
                    entry->SetDocFileName( FROM_UTF8( text ) );
                    break;
                }
            }
        }
    }

    fclose( file );
    return true;
}


bool PART_LIB::Save( OUTPUTFORMATTER& aFormatter )
{
    if( isModified )
    {
        timeStamp = GetNewTimeStamp();
        isModified = false;
    }

    bool success = true;

    try
    {
        SaveHeader( aFormatter );

        for( LIB_ALIAS_MAP::iterator it=m_amap.begin();  it!=m_amap.end();  it++ )
        {
            if( !it->second->IsRoot() )
                continue;

            it->second->GetPart()->Save( aFormatter );
        }

        aFormatter.Print( 0, "#\n#End Library\n" );
    }
    catch( const IO_ERROR& ioe )
    {
        success = false;
    }

    return success;
}


bool PART_LIB::SaveDocs( OUTPUTFORMATTER& aFormatter )
{
    bool success = true;

    try
    {
        aFormatter.Print( 0, "%s\n", DOCFILE_IDENT );

        for( LIB_ALIAS_MAP::iterator it=m_amap.begin();  it!=m_amap.end();  it++ )
        {
            if( !it->second->SaveDoc( aFormatter ) )
                success = false;
        }

        aFormatter.Print( 0, "#\n#End Doc Library\n" );
    }
    catch( const IO_ERROR& ioe )
    {
        success = false;
    }

    return success;
}


bool PART_LIB::SaveHeader( OUTPUTFORMATTER& aFormatter )
{
    aFormatter.Print( 0, "%s %d.%d\n", LIBFILE_IDENT,
                      LIB_VERSION_MAJOR, LIB_VERSION_MINOR );

    aFormatter.Print( 0, "#encoding utf-8\n");

#if 0
    aFormatter.Print( 0, "$HEADER\n" );
    aFormatter.Print( 0, "TimeStamp %8.8lX\n", m_TimeStamp );
    aFormatter.Print( 0, "Parts %d\n", m_amap.size() );
    aFormatter.Print( 0, "$ENDHEADER\n" ) != 1 );
#endif

    return true;
}


PART_LIB* PART_LIB::LoadLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer )
{
    std::auto_ptr<PART_LIB> lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA, aFileName ) );

    wxBusyCursor ShowWait;

    wxString errorMsg;

    if( !lib->Load( errorMsg ) )
        THROW_IO_ERROR( errorMsg );

    if( USE_OLD_DOC_FILE_FORMAT( lib->versionMajor, lib->versionMinor ) )
    {
#if 1
        // not fatal if error here.
        lib->LoadDocs( errorMsg );
#else
        if( !lib->LoadDocs( errorMsg ) )
            THROW_IO_ERROR( errorMsg );
#endif
    }

    PART_LIB* ret = lib.release();

    return ret;
}


PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer )
{
    PART_LIB* lib;

#if 1
    wxFileName fn = aFileName;
    // Don't reload the library if it is already loaded.
    lib = FindLibrary( fn.GetName() );
    if( lib )
        return lib;
#endif

    lib = PART_LIB::LoadLibrary( aFileName );

    push_back( lib );

    return lib;
}


PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName, PART_LIBS::iterator& aIterator ) throw( IO_ERROR )
{
#if 1
    // Don't reload the library if it is already loaded.
    wxFileName fn( aFileName );
    PART_LIB* lib = FindLibrary( fn.GetName() );

    if( lib )
        return lib;
#endif

    lib = PART_LIB::LoadLibrary( aFileName );

    if( aIterator >= begin() && aIterator < end() )
        insert( aIterator, lib );
    else
        push_back( lib );

    return lib;
}


void PART_LIBS::RemoveLibrary( const wxString& aName )
{
    if( aName.IsEmpty() )
        return;

    for( PART_LIBS::iterator it = begin(); it < end();  ++it )
    {
        if( it->GetName().CmpNoCase( aName ) == 0 )
        {
            erase( it );
            return;
        }
    }
}


PART_LIB* PART_LIBS::FindLibrary( const wxString& aName )
{
    for( PART_LIBS::iterator it = begin();  it!=end();  ++it )
    {
        if( it->GetName() == aName )
            return &*it;
    }

    return NULL;
}


wxArrayString PART_LIBS::GetLibraryNames( bool aSorted )
{
    wxArrayString cacheNames;
    wxArrayString names;

    BOOST_FOREACH( PART_LIB& lib, *this )
    {
        if( lib.IsCache() && aSorted )
            cacheNames.Add( lib.GetName() );
        else
            names.Add( lib.GetName() );
    }

    // Even sorted, the cache library is always at the end of the list.
    if( aSorted )
        names.Sort();

    for( unsigned int i = 0; i<cacheNames.Count(); i++ )
        names.Add( cacheNames.Item( i ) );

    return names;
}


LIB_PART* PART_LIBS::FindLibPart( const wxString& aName, const wxString& aLibraryName )
{
    LIB_PART* part = NULL;

    BOOST_FOREACH( PART_LIB& lib, *this )
    {
        if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
            continue;

        part = lib.FindPart( aName );

        if( part )
            break;
    }

    return part;
}


LIB_ALIAS* PART_LIBS::FindLibraryEntry( const wxString& aName, const wxString& aLibraryName )
{
    LIB_ALIAS* entry = NULL;

    BOOST_FOREACH( PART_LIB& lib, *this )
    {
        if( !!aLibraryName && lib.GetName() != aLibraryName )
            continue;

        entry = lib.FindEntry( aName );

        if( entry )
            break;
    }

    return entry;
}

/* searches all libraries in the list for an entry, using a case insensitive comparison.
 * Used to find an entry, when the normal (case sensitive) search fails.
  */
void PART_LIBS::FindLibraryNearEntries( std::vector<LIB_ALIAS*>& aCandidates,
                                        const wxString& aEntryName,
                                        const wxString& aLibraryName )
{
    BOOST_FOREACH( PART_LIB& lib, *this )
    {
        if( !!aLibraryName && lib.GetName() != aLibraryName )
            continue;

        LIB_ALIAS* entry = lib.GetFirstEntry();

        if( ! entry )
            continue;

        wxString first_entry_name = entry->GetName();
        wxString entry_name = first_entry_name;

        for( ;; )
        {
            if( entry_name.CmpNoCase( aEntryName ) == 0 )
                aCandidates.push_back( entry );

            entry = lib.GetNextEntry( entry_name );
            entry_name = entry->GetName();

            if( first_entry_name == entry_name )
                break;
        }
    }
}


int PART_LIBS::s_modify_generation = 1;     // starts at 1 and goes up


int PART_LIBS::GetModifyHash()
{
    int hash = 0;

    for( PART_LIBS::const_iterator it = begin();  it != end();  ++it )
    {
        hash += it->m_mod_hash;
    }

    return hash;
}


/*
void PART_LIBS::RemoveCacheLibrary()
{
    for( PART_LIBS::iterator it = begin(); it < end();  ++it )
    {
        if( it->IsCache() )
            erase( it-- );
    }
}
*/


void PART_LIBS::LibNamesAndPaths( PROJECT* aProject, bool doSave,
                                  wxString* aPaths, wxArrayString* aNames )
    throw( IO_ERROR, boost::bad_pointer )
{
    wxString pro = aProject->GetProjectFullName();

    PARAM_CFG_ARRAY ca;

    if( aPaths )
        ca.push_back( new PARAM_CFG_FILENAME( wxT( "LibDir" ), aPaths ) );

    if( aNames )
        ca.push_back( new PARAM_CFG_LIBNAME_LIST( wxT( "LibName" ),  aNames, GROUP_SCH_LIBS ) );

    if( doSave )
    {
        aProject->ConfigSave( Kiface().KifaceSearch(), GROUP_SCH, ca );

        /*
        {
            wxString msg = wxString::Format( _(
                "Unable save project's '%s' file" ),
                GetChars( pro )
                );
            THROW_IO_ERROR( msg );
        }
        */
    }
    else
    {
        wxString pro = aProject->GetProjectFullName();

        if( !aProject->ConfigLoad( Kiface().KifaceSearch(), GROUP_SCH, ca ) )
        {
            wxString msg = wxString::Format( _(
                "Unable to load project's '%s' file" ),
                GetChars( pro )
                );
            THROW_IO_ERROR( msg );
        }
    }
}


const wxString PART_LIBS::CacheName( const wxString& aFullProjectFilename )
{
    /* until apr 2009 the project cache lib was named: <root_name>.cache.lib,
     * and after: <root_name>-cache.lib.  So if the <name>-cache.lib is not found,
     * the old file will be renamed and returned.
     */
    wxFileName  new_name = aFullProjectFilename;

    new_name.SetName( new_name.GetName() + wxT( "-cache" ) );
    new_name.SetExt( SchematicLibraryFileExtension );

    if( new_name.FileExists() )
        return new_name.GetFullPath();
    else
    {
        wxFileName old_name = aFullProjectFilename;
        old_name.SetExt( wxT( "cache.lib" ) );

        if( old_name.FileExists() )
        {
            wxRenameFile( old_name.GetFullPath(), new_name.GetFullPath() );
            return new_name.GetFullPath();
        }
    }
    return wxEmptyString;
}


void PART_LIBS::LoadAllLibraries( PROJECT* aProject ) throw( IO_ERROR, boost::bad_pointer )
{
    wxFileName      fn;
    wxString        filename;
    wxString        libs_not_found;
    SEARCH_STACK*   lib_search = aProject->SchSearchS();

#if defined(DEBUG) && 1
    lib_search->Show( __func__ );
#endif

    wxArrayString   lib_names;

    LibNamesAndPaths( aProject, false, NULL, &lib_names );

    // If the list is empty, force loading the standard power symbol library.
    if( !lib_names.GetCount() )
        lib_names.Add( wxT( "power" ) );

    wxASSERT( !size() );    // expect to load into "this" empty container.

    for( unsigned i = 0; i < lib_names.GetCount();  ++i )
    {
        fn.Clear();
        fn.SetName( lib_names[i] );
        fn.SetExt( SchematicLibraryFileExtension );

        // Skip if the file name is not valid..
        if( !fn.IsOk() )
            continue;

        if( !fn.FileExists() )
        {
            filename = lib_search->FindValidPath( fn.GetFullPath() );

            if( !filename )
            {
                libs_not_found += fn.GetName();
                libs_not_found += wxT( '\n' );
                continue;
            }
        }
        else
        {
            filename = fn.GetFullPath();
        }

        try
        {
            AddLibrary( filename );
        }
        catch( const IO_ERROR& ioe )
        {
            wxString msg = wxString::Format( _(
                    "Part library '%s' failed to load. Error:\n"
                    "%s" ),
                    GetChars( filename ),
                    GetChars( ioe.errorText )
                    );

            THROW_IO_ERROR( msg );
        }
    }

    // add the special cache library.
    wxString cache_name = CacheName( aProject->GetProjectFullName() );
    if( !!cache_name )
    {
        try
        {
            if( PART_LIB* lib = AddLibrary( cache_name ) )
                lib->SetCache();
        }
        catch( const IO_ERROR& ioe )
        {
            wxString msg = wxString::Format( _(
                    "Part library '%s' failed to load.\nError: %s" ),
                    GetChars( cache_name ),
                    GetChars( ioe.errorText )
                    );

            THROW_IO_ERROR( msg );
        }
    }

    // Print the libraries not found
    if( !!libs_not_found )
    {
        // Use a different exception type so catch()er can route to proper use
        // of the HTML_MESSAGE_BOX.
        THROW_PARSE_ERROR( wxEmptyString, UTF8( __func__ ),
            UTF8( libs_not_found ), 0, 0 );
    }

#if defined(DEBUG) && 1
    printf( "%s: lib_names:\n", __func__ );

    for( PART_LIBS::const_iterator it = begin(); it < end(); ++it )
        printf( " %s\n", TO_UTF8( it->GetName() ) );
#endif
}
