/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2004-2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2008 Wayne Stambaugh <stambaughw@gmail.com>
 * Copyright (C) 2004-2017 KiCad Developers, see AUTHORS.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 <algorithm>
#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 <project_rescue.h>
#include <properties.h>

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

#include <wx/progdlg.h>
#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, SCH_IO_MGR::SCH_FILE_T aPluginType ) :
    // start @ != 0 so each additional library added
    // is immediately detectable, zero would not be.
    m_mod_hash( PART_LIBS::s_modify_generation ),
    m_pluginType( aPluginType )
{
    type = aType;
    isModified = false;
    timeStamp = 0;
    timeStamp = wxDateTime::Now();
    versionMajor = 0;       // Will be updated after reading the lib file
    versionMinor = 0;       // Will be updated after reading the lib file

    fileName = aFileName;

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

    m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
    m_properties = std::make_unique<PROPERTIES>();
}


PART_LIB::~PART_LIB()
{
}


void PART_LIB::Save( bool aSaveDocFile )
{
    wxCHECK_RET( m_plugin != NULL, wxString::Format( "no plugin defined for library `%s`.",
                                                     fileName.GetFullPath() ) );

    PROPERTIES props;

    if( !aSaveDocFile )
        props[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";

    m_plugin->SaveLibrary( fileName.GetFullPath(), &props );
    isModified = false;
}


void PART_LIB::Create( const wxString& aFileName )
{
    wxString tmpFileName = fileName.GetFullPath();

    if( !aFileName.IsEmpty() )
        tmpFileName = aFileName;

    m_plugin->CreateSymbolLib( tmpFileName, m_properties.get() );
}


void PART_LIB::SetPluginType( SCH_IO_MGR::SCH_FILE_T aPluginType )
{
    if( m_pluginType != aPluginType )
    {
        m_pluginType = aPluginType;
        m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
    }
}


bool PART_LIB::IsCache() const
{
    return m_properties->Exists( SCH_LEGACY_PLUGIN::PropNoDocFile );
}


void PART_LIB::SetCache()
{
    (*m_properties)[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
}


bool PART_LIB::IsBuffering() const
{
    return m_properties->Exists( SCH_LEGACY_PLUGIN::PropBuffering );
}


void PART_LIB::EnableBuffering( bool aEnable )
{
    if( aEnable )
        (*m_properties)[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
    else
        m_properties->Clear( SCH_LEGACY_PLUGIN::PropBuffering );
}


void PART_LIB::GetAliasNames( wxArrayString& aNames ) const
{
    m_plugin->EnumerateSymbolLib( aNames, fileName.GetFullPath(), m_properties.get() );

    aNames.Sort();
}


void PART_LIB::GetAliases( std::vector<LIB_ALIAS*>& aAliases ) const
{
    m_plugin->EnumerateSymbolLib( aAliases, fileName.GetFullPath(), m_properties.get() );

    std::sort( aAliases.begin(), aAliases.end(),
            [](LIB_ALIAS *lhs, LIB_ALIAS *rhs) -> bool
                { return lhs->GetName() < rhs->GetName(); });
}


void PART_LIB::GetEntryTypePowerNames( wxArrayString& aNames ) const
{
    std::vector<LIB_ALIAS*> aliases;

    m_plugin->EnumerateSymbolLib( aliases, fileName.GetFullPath() );

    for( size_t i = 0;  i < aliases.size();  i++ )
    {
        LIB_ALIAS* alias = aliases[i];

        LIB_PART* root = alias->GetPart();

        if( !root || !root->IsPower() )
            continue;

        aNames.Add( alias->GetName() );
    }

    aNames.Sort();
}


LIB_ALIAS* PART_LIB::FindAlias( const wxString& aName ) const
{
    LIB_ALIAS* alias = m_plugin->LoadSymbol( fileName.GetFullPath(), aName, m_properties.get() );

    // Set the library to this even though technically the legacy cache plugin owns the
    // symbols.  This allows the symbol library table conversion tool to determine the
    // correct library where the symbol was found.
    if( alias && alias->GetPart() && !alias->GetPart()->GetLib() )
        alias->GetPart()->SetLib( const_cast<PART_LIB*>( this ) );

    return alias;
}


LIB_ALIAS* PART_LIB::FindAlias( const LIB_ID& aLibId ) const
{
    return FindAlias( aLibId.Format().wx_str() );
}


LIB_PART* PART_LIB::FindPart( const wxString& aName ) const
{
    LIB_ALIAS* alias = FindAlias( aName );

    if( alias != NULL )
        return alias->GetPart();

    return NULL;
}


LIB_PART* PART_LIB::FindPart( const LIB_ID& aLibId ) const
{
    return FindPart( aLibId.Format().wx_str() );
}


bool PART_LIB::HasPowerParts() const
{
    // return true if at least one power part is found in lib
    std::vector<LIB_ALIAS*> aliases;

    m_plugin->EnumerateSymbolLib( aliases, fileName.GetFullPath(), m_properties.get() );

    for( size_t i = 0;  i < aliases.size();  i++ )
    {
        LIB_ALIAS* alias = aliases[i];

        LIB_PART* root = alias->GetPart();

        if( !root || root->IsPower() )
            return true;
    }

    return false;
}


void PART_LIB::AddPart( LIB_PART* aPart )
{
    // add a clone, not the caller's copy, the plugin take ownership of the new symbol.
    m_plugin->SaveSymbol( fileName.GetFullPath(), new LIB_PART( *aPart, this ), m_properties.get() );

    // If we are not buffering, the library file is updated immediately when the plugin
    // SaveSymbol() function is called.
    if( IsBuffering() )
        isModified = true;

    ++m_mod_hash;
}


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

    m_plugin->DeleteAlias( fileName.GetFullPath(), aEntry->GetName(), m_properties.get() );

    // If we are not buffering, the library file is updated immediately when the plugin
    // SaveSymbol() function is called.
    if( IsBuffering() )
        isModified = true;

    ++m_mod_hash;
    return NULL;
}


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

    m_plugin->DeleteSymbol( fileName.GetFullPath(), aOldPart->GetName(), m_properties.get() );

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

    m_plugin->SaveSymbol( fileName.GetFullPath(), my_part, m_properties.get() );

    // If we are not buffering, the library file is updated immediately when the plugin
    // SaveSymbol() function is called.
    if( IsBuffering() )
        isModified = true;

    ++m_mod_hash;
    return my_part;
}


PART_LIB* PART_LIB::LoadLibrary( const wxString& aFileName )
{
    std::unique_ptr<PART_LIB> lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA, aFileName ) );

    std::vector<LIB_ALIAS*> aliases;
    // This loads the library.
    lib->GetAliases( aliases );

    // Now, set the LIB_PART m_library member but it will only be used
    // when loading legacy libraries in the future. Once the symbols in the
    // schematic have a full #LIB_ID, this will not get called.
    for( size_t ii = 0; ii < aliases.size(); ii++ )
    {
        LIB_ALIAS* alias = aliases[ii];

        if( alias->GetPart() )
            alias->GetPart()->SetLib( lib.get() );
    }

    PART_LIB* ret = lib.release();
    return ret;
}


PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName )
{
    PART_LIB* lib;

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

    if( lib )
        return lib;

    lib = PART_LIB::LoadLibrary( aFileName );

    push_back( lib );

    return lib;
}


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

    if( lib )
        return lib;

    lib = PART_LIB::LoadLibrary( aFileName );

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

    return lib;
}


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;
}


PART_LIB* PART_LIBS::GetCacheLibrary()
{
    for( PART_LIBS::iterator it = begin();  it!=end();  ++it )
    {
        if( it->IsCache() )
            return &*it;
    }

    return NULL;
}


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

    return NULL;
}


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

    for( 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 LIB_ID& aLibId, const wxString& aLibraryName )
{
    LIB_PART* part = NULL;

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

        part = lib.FindPart( aLibId.GetLibItemName().wx_str() );

        if( part )
            break;
    }

    return part;
}


LIB_ALIAS* PART_LIBS::FindLibraryAlias( const LIB_ID& aLibId, const wxString& aLibraryName )
{
    LIB_ALIAS* entry = NULL;

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

        entry = lib.FindAlias( aLibId.GetLibItemName().wx_str() );

        if( entry )
            break;
    }

    return entry;
}


void PART_LIBS::FindLibraryNearEntries( std::vector<LIB_ALIAS*>& aCandidates,
                                        const wxString& aEntryName,
                                        const wxString& aLibraryName )
{
    for( PART_LIB& lib : *this )
    {
        if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
            continue;

        wxArrayString aliasNames;

        lib.GetAliasNames( aliasNames );

        if( aliasNames.IsEmpty() )
            continue;

        for( size_t i = 0;  i < aliasNames.size();  i++ )
        {
            if( aliasNames[i].CmpNoCase( aEntryName ) == 0 )
                aCandidates.push_back( lib.FindAlias( aliasNames[i] ) );
        }
    }
}


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->GetModHash();
    }

    // Rebuilding the cache (m_cache) does not change the GetModHash() value,
    // but changes PART_LIBS::s_modify_generation.
    // Take this change in account:
    hash += PART_LIBS::s_modify_generation;

    return hash;
}


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

    PARAM_CFG_ARRAY ca;

    if( aPaths )
        ca.push_back( new PARAM_CFG_FILENAME( "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
    {
        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() + "-cache" );
    new_name.SetExt( SchematicLibraryFileExtension );

    if( new_name.FileExists() )
        return new_name.GetFullPath();
    else
    {
        wxFileName old_name = aFullProjectFilename;
        old_name.SetExt( "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, bool aShowProgress )
{
    wxString        filename;
    wxString        libs_not_found;
    SEARCH_STACK*   lib_search = aProject->SchSearchS();

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

    wxArrayString   lib_names;

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

    // Post symbol library table, this should be empty.  Only the cache library should get loaded.
    if( !lib_names.empty() )
    {
        wxProgressDialog lib_dialog( _( "Loading Symbol Libraries" ),
                                     wxEmptyString,
                                     lib_names.GetCount(),
                                     NULL,
                                     wxPD_APP_MODAL );

        if( aShowProgress )
        {
            lib_dialog.Show();
        }

        wxString progress_message;

        for( unsigned i = 0; i < lib_names.GetCount();  ++i )
        {
            if( aShowProgress )
            {
                lib_dialog.Update( i, _( "Loading " + lib_names[i] ) );
            }

            // lib_names[] does not store the file extension. Set it.
            // Remember lib_names[i] can contain a '.' in name, so using a wxFileName
            // before adding the extension can create incorrect full filename
            wxString fullname = lib_names[i] + "." + SchematicLibraryFileExtension;
            // Now the full name is set, we can use a wxFileName.
            wxFileName fn( fullname );

            // 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.GetFullPath();
                    libs_not_found += '\n';
                    continue;
                }
            }
            else
            {   // ensure the lib filename has a absolute path.
                // If the lib has no absolute path, and is found in the cwd by fn.FileExists(),
                // make a full absolute path, to avoid issues with load library functions which
                // expects an absolute path.
                if( !fn.IsAbsolute() )
                    fn.MakeAbsolute();

                filename = fn.GetFullPath();
            }

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

                wxLogError( msg );
            }
        }
    }

    // add the special cache library.
    wxString cache_name = CacheName( aProject->GetProjectFullName() );
    PART_LIB* cache_lib;

    if( !cache_name.IsEmpty() )
    {
        try
        {
            cache_lib = AddLibrary( cache_name );

            if( cache_lib )
                cache_lib->SetCache();
        }
        catch( const IO_ERROR& ioe )
        {
            wxString msg = wxString::Format( _(
                    "Part library \"%s\" failed to load.\nError: %s" ),
                    GetChars( cache_name ),
                    GetChars( ioe.What() )
                    );

            THROW_IO_ERROR( msg );
        }
    }

    // Print the libraries not found
    if( !libs_not_found.IsEmpty() )
    {
        // Use a different exception type so catch()er can route to proper use
        // of the HTML_MESSAGE_BOX.
        THROW_PARSE_ERROR( wxEmptyString, __func__, TO_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
}