/* === This file is part of Calamares - <https://github.com/calamares> ===
 *
 *   Copyright 2019, Adriaan de Groot <groot@kde.org>
 *
 *   Calamares 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.
 *
 *   Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
 */

#include "PackageChooserViewStep.h"

#ifdef HAVE_XML
#include "ItemAppData.h"
#endif
#ifdef HAVE_APPSTREAM
#include "ItemAppStream.h"
#include <AppStreamQt/pool.h>
#include <memory>
#endif
#include "PackageChooserPage.h"
#include "PackageModel.h"

#include "GlobalStorage.h"
#include "JobQueue.h"

#include "locale/TranslatableConfiguration.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include "utils/Variant.h"

#include <QDesktopServices>
#include <QVariantMap>

CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlugin< PackageChooserViewStep >(); )

PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
    : Calamares::ViewStep( parent )
    , m_widget( nullptr )
    , m_model( nullptr )
    , m_mode( PackageChooserMode::Required )
    , m_stepName( nullptr )
{
    emit nextStatusChanged( false );
}


PackageChooserViewStep::~PackageChooserViewStep()
{
    if ( m_widget && m_widget->parent() == nullptr )
    {
        m_widget->deleteLater();
    }
    delete m_model;
    delete m_stepName;
}


QString
PackageChooserViewStep::prettyName() const
{
    return m_stepName ? m_stepName->get() : tr( "Packages" );
}


QWidget*
PackageChooserViewStep::widget()
{
    if ( !m_widget )
    {
        m_widget = new PackageChooserPage( m_mode, nullptr );
        connect( m_widget, &PackageChooserPage::selectionChanged, [=]() {
            emit nextStatusChanged( this->isNextEnabled() );
        } );

        if ( m_model )
        {
            hookupModel();
        }
        else
        {
            cWarning() << "PackageChooser Widget created before model.";
        }
    }
    return m_widget;
}


bool
PackageChooserViewStep::isNextEnabled() const
{
    if ( !m_model )
    {
        return false;
    }

    if ( !m_widget )
    {
        // No way to have changed anything
        return true;
    }

    switch ( m_mode )
    {
    case PackageChooserMode::Optional:
    case PackageChooserMode::OptionalMultiple:
        // zero or one OR zero or more
        return true;
    case PackageChooserMode::Required:
    case PackageChooserMode::RequiredMultiple:
        // exactly one OR one or more
        return m_widget->hasSelection();
    }

    NOTREACHED return true;
}

bool
PackageChooserViewStep::isBackEnabled() const
{
    return true;
}

bool
PackageChooserViewStep::isAtBeginning() const
{
    return true;
}


bool
PackageChooserViewStep::isAtEnd() const
{
    return true;
}

void
PackageChooserViewStep::onActivate()
{
    if ( !m_description.isEmpty() )
    {
        m_widget->setDescription( m_description );
    }
    if ( !m_widget->hasSelection() )
    {
        m_widget->setSelection( m_defaultIdx );
    }
}

void
PackageChooserViewStep::onLeave()
{
    QString key = QStringLiteral( "packagechooser_%1" ).arg( m_id );
    QString value;
    if ( m_widget->hasSelection() )
    {
        value = m_widget->selectedPackageIds().join( ',' );
    }
    Calamares::JobQueue::instance()->globalStorage()->insert( key, value );

    cDebug() << "PackageChooser" << key << "selected" << value;
}

Calamares::JobList
PackageChooserViewStep::jobs() const
{
    Calamares::JobList l;
    return l;
}

void
PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap )
{
    QString mode = CalamaresUtils::getString( configurationMap, "mode" );
    bool mode_ok = false;
    if ( !mode.isEmpty() )
    {
        m_mode = roleNames().find( mode, mode_ok );
    }
    if ( !mode_ok )
    {
        m_mode = PackageChooserMode::Required;
    }

    m_id = CalamaresUtils::getString( configurationMap, "id" );
    if ( m_id.isEmpty() )
    {
        // Not set, so use the instance id
        m_id = moduleInstanceKey().id();
    }

    bool labels_ok = false;
    auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
    if ( labels_ok )
    {
        if ( labels.contains( "step" ) )
        {
            m_stepName = new CalamaresUtils::Locale::TranslatedString( labels, "step" );
        }
    }

    m_description = CalamaresUtils::getString( configurationMap, "description" );

    QString default_item_id = CalamaresUtils::getString( configurationMap, "default" );
    m_defaultIdx = QModelIndex();

    bool first_time = !m_model;
    QVariantList list_items = configurationMap.value( "items" ).toList();
    if ( !list_items.isEmpty() )
    {
        // remove null item if unused
        if ( m_mode == PackageChooserMode::Required ||
             m_mode == PackageChooserMode::RequiredMultiple ||
             !default_item_id.isEmpty() )
        {

            for (int list_item_n = 0; list_item_n < list_items.count(); ++list_item_n)
            {
                QVariantMap list_item = list_items.value( list_item_n ).toMap();
                QString list_item_id = list_item.value( "id" ).toString();

                if ( list_item_id.isEmpty() )
                {
                    list_items.removeAt( list_item_n );
                    break;
                }
            }
        }

        fillModel( list_items );
    }

    if ( first_time && m_widget && m_model )
    {
        hookupModel();
    }

    // find default item
    if ( first_time && m_model && !default_item_id.isEmpty() )
    {
        for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n )
        {
            QModelIndex item_idx = m_model->index( item_n, 0 );
            QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole );

            if ( item_id.toString() == default_item_id )
            {
                m_defaultIdx = item_idx;
                break;
            }
        }
    }
}

void
PackageChooserViewStep::fillModel( const QVariantList& items )
{
    if ( !m_model )
    {
        m_model = new PackageListModel( nullptr );
    }

    if ( items.isEmpty() )
    {
        cWarning() << "No *items* for PackageChooser module.";
        return;
    }

#ifdef HAVE_APPSTREAM
    std::unique_ptr< AppStream::Pool > pool;
    bool poolOk = false;
#endif

    cDebug() << "Loading PackageChooser model items from config";
    int item_index = 0;
    for ( const auto& item_it : items )
    {
        ++item_index;
        QVariantMap item_map = item_it.toMap();
        if ( item_map.isEmpty() )
        {
            cWarning() << "PackageChooser entry" << item_index << "is not valid.";
            continue;
        }

        if ( item_map.contains( "appdata" ) )
        {
#ifdef HAVE_XML
            m_model->addPackage( fromAppData( item_map ) );
#else
            cWarning() << "Loading AppData XML is not supported.";
#endif
        }
        else if ( item_map.contains( "appstream" ) )
        {
#ifdef HAVE_APPSTREAM
            if ( !pool )
            {
                pool = std::make_unique< AppStream::Pool >();
                pool->setLocale( QStringLiteral( "ALL" ) );
                poolOk = pool->load();
            }
            if ( pool && poolOk )
            {
                m_model->addPackage( fromAppStream( *pool, item_map ) );
            }
#else
            cWarning() << "Loading AppStream data is not supported.";
#endif
        }
        else
        {
            m_model->addPackage( PackageItem( item_map ) );
        }
    }
}

void
PackageChooserViewStep::hookupModel()
{
    if ( !m_model || !m_widget )
    {
        cError() << "Can't hook up model until widget and model both exist.";
        return;
    }

    m_widget->setModel( m_model );
    for ( int i = 0; i < m_model->packageCount(); ++i )
    {
        const auto& package = m_model->packageData( i );
        if ( package.id.isEmpty() )
        {
            m_widget->setIntroduction( package );
            break;
        }
    }
}
