/* === This file is part of Calamares - <https://github.com/calamares> ===
 *
 *   Copyright (c) 2017, Kyle Robbertze <kyle@aims.ac.za>
 *   Copyright 2017, 2020, 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 "PackageTreeItem.h"

#include "utils/Logger.h"
#include "utils/Variant.h"

static Qt::CheckState
parentCheckState( PackageTreeItem* parent )
{
    if ( parent )
    {
        // Avoid partially-checked .. a package can't be partial
        return parent->isSelected() == Qt::Unchecked ? Qt::Unchecked : Qt::Checked;
    }
    else
    {
        return Qt::Unchecked;
    }
}

PackageTreeItem::PackageTreeItem( const QString& packageName, PackageTreeItem* parent )
    : m_parentItem( parent )
    , m_packageName( packageName )
    , m_selected( parentCheckState( parent ) )
    , m_isGroup( false )
    , m_showReadOnly( parent ? parent->isImmutable() : false )
{
}

PackageTreeItem::PackageTreeItem( const QVariantMap& groupData, PackageTreeItem* parent )
    : m_parentItem( parent )
    , m_name( CalamaresUtils::getString( groupData, "name" ) )
    , m_selected( parentCheckState( parent ) )
    , m_description( CalamaresUtils::getString( groupData, "description" ) )
    , m_preScript( CalamaresUtils::getString( groupData, "pre-install" ) )
    , m_postScript( CalamaresUtils::getString( groupData, "post-install" ) )
    , m_isGroup( true )
    , m_isCritical( CalamaresUtils::getBool( groupData, "critical", false ) )
    , m_isHidden( CalamaresUtils::getBool( groupData, "hidden", false ) )
    , m_showReadOnly( CalamaresUtils::getBool( groupData, "immutable", false ) )
    , m_startExpanded( CalamaresUtils::getBool( groupData, "expanded", false ) )
{
}

PackageTreeItem::PackageTreeItem::PackageTreeItem()
    : m_parentItem( nullptr )
    , m_name( QStringLiteral( "<root>" ) )
    , m_selected( Qt::Checked )
    , m_isGroup( true )
{
}

PackageTreeItem::~PackageTreeItem()
{
    qDeleteAll( m_childItems );
}

void
PackageTreeItem::appendChild( PackageTreeItem* child )
{
    m_childItems.append( child );
}

PackageTreeItem*
PackageTreeItem::child( int row )
{
    return m_childItems.value( row );
}

int
PackageTreeItem::childCount() const
{
    return m_childItems.count();
}

int
PackageTreeItem::row() const
{
    if ( m_parentItem )
    {
        return m_parentItem->m_childItems.indexOf( const_cast< PackageTreeItem* >( this ) );
    }
    return 0;
}

QVariant
PackageTreeItem::data( int column ) const
{
    if ( isPackage() )  // packages have a packagename, groups don't
    {
        switch ( column )
        {
        case 0:
            return QVariant( packageName() );
        default:
            return QVariant();
        }
    }
    else
    {
        switch ( column )  // group
        {
        case 0:
            return QVariant( name() );
        case 1:
            return QVariant( description() );
        default:
            return QVariant();
        }
    }
}

PackageTreeItem*
PackageTreeItem::parentItem()
{
    return m_parentItem;
}

const PackageTreeItem*
PackageTreeItem::parentItem() const
{
    return m_parentItem;
}


bool
PackageTreeItem::hiddenSelected() const
{
    if ( !m_isHidden )
    {
        return m_selected != Qt::Unchecked;
    }

    if ( m_selected == Qt::Unchecked )
    {
        return false;
    }

    const PackageTreeItem* currentItem = parentItem();
    while ( currentItem != nullptr )
    {
        if ( !currentItem->isHidden() )
        {
            return currentItem->isSelected() != Qt::Unchecked;
        }
        currentItem = currentItem->parentItem();
    }

    /* Has no non-hidden parents */
    return m_selected != Qt::Unchecked;
}


void
PackageTreeItem::setSelected( Qt::CheckState isSelected )
{
    if ( parentItem() == nullptr )
    {
        // This is the root, it is always checked so don't change state
        return;
    }

    m_selected = isSelected;
    setChildrenSelected( isSelected );

    // Look for suitable parent item which may change checked-state
    // when one of its children changes.
    PackageTreeItem* currentItem = parentItem();
    while ( ( currentItem != nullptr ) && ( currentItem->childCount() == 0 ) )
    {
        currentItem = currentItem->parentItem();
    }
    if ( currentItem == nullptr )
    {
        // Reached the root .. don't bother
        return;
    }

    currentItem->updateSelected();
}

void
PackageTreeItem::updateSelected()
{
    // Figure out checked-state based on the children
    int childrenSelected = 0;
    int childrenPartiallySelected = 0;
    for ( int i = 0; i < childCount(); i++ )
    {
        if ( child( i )->isSelected() == Qt::Checked )
        {
            childrenSelected++;
        }
        if ( child( i )->isSelected() == Qt::PartiallyChecked )
        {
            childrenPartiallySelected++;
        }
    }
    if ( !childrenSelected && !childrenPartiallySelected )
    {
        setSelected( Qt::Unchecked );
    }
    else if ( childrenSelected == childCount() )
    {
        setSelected( Qt::Checked );
    }
    else
    {
        setSelected( Qt::PartiallyChecked );
    }
}


void
PackageTreeItem::setChildrenSelected( Qt::CheckState isSelected )
{
    if ( isSelected != Qt::PartiallyChecked )
        // Children are never root; don't need to use setSelected on them.
        for ( auto child : m_childItems )
        {
            child->m_selected = isSelected;
            child->setChildrenSelected( isSelected );
        }
}

int
PackageTreeItem::type() const
{
    return QStandardItem::UserType;
}

QVariant
PackageTreeItem::toOperation() const
{
    // If it's a package with a pre- or post-script, replace
    // with the more complicated datastructure.
    if ( !m_preScript.isEmpty() || !m_postScript.isEmpty() )
    {
        QMap< QString, QVariant > sdetails;
        sdetails.insert( "pre-script", m_preScript );
        sdetails.insert( "package", m_packageName );
        sdetails.insert( "post-script", m_postScript );
        return sdetails;
    }
    else
    {
        return m_packageName;
    }
}

bool
PackageTreeItem::operator==( const PackageTreeItem& rhs ) const
{
    if ( isGroup() != rhs.isGroup() )
    {
        // Different kinds
        return false;
    }

    if ( isGroup() )
    {
        return name() == rhs.name() && description() == rhs.description() && preScript() == rhs.preScript()
            && postScript() == rhs.postScript() && isCritical() == rhs.isCritical() && isHidden() == rhs.isHidden()
            && m_showReadOnly == rhs.m_showReadOnly && expandOnStart() == rhs.expandOnStart();
    }
    else
    {
        return packageName() == rhs.packageName();
    }
}