/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright The 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 3 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, see <http://www.gnu.org/licenses/>.
 */


#include <design_block_info_impl.h>

#include <design_block.h>
#include <design_block_lib_table.h>
#include <kiway.h>
#include <locale_io.h>
#include <progress_reporter.h>
#include <string_utils.h>
#include <thread_pool.h>
#include <wildcards_and_files_ext.h>

#include <kiplatform/io.h>

#include <wx/txtstrm.h>
#include <wx/wfstream.h>


void DESIGN_BLOCK_INFO_IMPL::load( const LOCALE_IO* locale )
{
    DESIGN_BLOCK_LIB_TABLE* dbtable = m_owner->GetTable();

    wxASSERT( dbtable );

    std::unique_ptr<const DESIGN_BLOCK> design_block( dbtable->GetEnumeratedDesignBlock( m_nickname, m_dbname,
                                                                                         locale ) );

    if( design_block )
    {
        m_keywords = design_block->GetKeywords();
        m_doc = design_block->GetLibDescription();
    }

    m_loaded = true;
}


bool DESIGN_BLOCK_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
{
    try
    {
        aFunc();
    }
    catch( const IO_ERROR& ioe )
    {
        m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
        return false;
    }
    catch( const std::exception& se )
    {
        // This is a round about way to do this, but who knows what THROW_IO_ERROR()
        // may be tricked out to do someday, keep it in the game.
        try
        {
            THROW_IO_ERROR( se.what() );
        }
        catch( const IO_ERROR& ioe )
        {
            m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
        }

        return false;
    }

    return true;
}


bool DESIGN_BLOCK_LIST_IMPL::ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable,
                                                   const wxString*         aNickname,
                                                   PROGRESS_REPORTER*      aProgressReporter )
{
    long long int generatedTimestamp = 0;

    if( !CatchErrors(
                [&]()
                {
                    generatedTimestamp = aTable->GenerateTimestamp( aNickname );
                } ) )
    {
        return false;
    }

    if( generatedTimestamp == m_list_timestamp )
        return true;

    // Disable KIID generation: not needed for library parts; sometimes very slow
    KIID_NIL_SET_RESET reset_kiid;

    m_progress_reporter = aProgressReporter;

    m_cancelled = false;
    m_lib_table = aTable;

    // Clear data before reading files
    m_errors.clear();
    m_list.clear();
    m_queue.clear();

    if( aNickname )
    {
        m_queue.push( *aNickname );
    }
    else
    {
        for( const wxString& nickname : aTable->GetLogicalLibs() )
            m_queue.push( nickname );
    }

    if( m_progress_reporter )
    {
        m_progress_reporter->SetMaxProgress( (int) m_queue.size() );
        m_progress_reporter->Report( _( "Loading design_blocks..." ) );
    }

    loadDesignBlocks();

    if( m_progress_reporter )
        m_progress_reporter->AdvancePhase();

    if( m_cancelled )
        m_list_timestamp = 0; // God knows what we got before we were canceled
    else
        m_list_timestamp = generatedTimestamp;

    return m_errors.empty();
}


void DESIGN_BLOCK_LIST_IMPL::loadDesignBlocks()
{
    LOCALE_IO toggle_locale;

    // Parse the design_blocks in parallel. WARNING! This requires changing the locale, which is
    // GLOBAL. It is only thread safe to construct the LOCALE_IO before the threads are created,
    // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
    // from this will cause nasal demons.
    //
    // TODO: blast LOCALE_IO into the sun

    SYNC_QUEUE<std::unique_ptr<DESIGN_BLOCK_INFO>> queue_parsed;

    thread_pool&                     tp = GetKiCadThreadPool();
    size_t                           num_elements = m_queue.size();
    std::vector<std::future<size_t>> returns( num_elements );

    auto db_thread =
            [ this, &queue_parsed, &toggle_locale ]() -> size_t
            {
                wxString nickname;

                if( m_cancelled || !m_queue.pop( nickname ) )
                    return 0;

                wxArrayString dbnames;

                CatchErrors(
                        [&]()
                        {
                            m_lib_table->DesignBlockEnumerate( dbnames, nickname, false, &toggle_locale );
                        } );

                for( wxString dbname : dbnames )
                {
                    CatchErrors(
                            [&]()
                            {
                                auto* dbinfo = new DESIGN_BLOCK_INFO_IMPL( this, nickname, dbname, &toggle_locale );
                                queue_parsed.move_push( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) );
                            } );

                    if( m_cancelled )
                        return 0;
                }

                if( m_progress_reporter )
                    m_progress_reporter->AdvanceProgress();

                return 1;
            };

    for( size_t ii = 0; ii < num_elements; ++ii )
        returns[ii] = tp.submit( db_thread );

    for( const std::future<size_t>& ret : returns )
    {
        std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );

        while( status != std::future_status::ready )
        {
            if( m_progress_reporter )
                m_progress_reporter->KeepRefreshing();

            status = ret.wait_for( std::chrono::milliseconds( 250 ) );
        }
    }

    std::unique_ptr<DESIGN_BLOCK_INFO> dbi;

    while( queue_parsed.pop( dbi ) )
        m_list.push_back( std::move( dbi ) );

    std::sort( m_list.begin(), m_list.end(),
               []( std::unique_ptr<DESIGN_BLOCK_INFO> const& lhs,
                   std::unique_ptr<DESIGN_BLOCK_INFO> const& rhs ) -> bool
               {
                   return *lhs < *rhs;
               } );
}


DESIGN_BLOCK_LIST_IMPL::DESIGN_BLOCK_LIST_IMPL() :
        m_list_timestamp( 0 ),
        m_progress_reporter( nullptr ),
        m_cancelled( false )
{
}
