/******************************************************************************
 * QSkinny - Copyright (C) The authors
 *           SPDX-License-Identifier: BSD-3-Clause
 *****************************************************************************/

#include "QskStackBox.h"
#include "QskStackBoxAnimator.h"
#include "QskEvent.h"
#include "QskQuick.h"

#include <QPointer>

class QskStackBox::PrivateData
{
  public:
    QVector< QQuickItem* > items;
    QPointer< QskStackBoxAnimator > animator;

    int currentIndex = -1;
    Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter;
};

QskStackBox::QskStackBox( QQuickItem* parent )
    : QskStackBox( false, parent )
{
}

QskStackBox::QskStackBox( bool autoAddChildren, QQuickItem* parent )
    : QskIndexedLayoutBox( parent )
    , m_data( new PrivateData() )
{
    setAutoAddChildren( autoAddChildren );
}

QskStackBox::~QskStackBox()
{
}

void QskStackBox::setDefaultAlignment( Qt::Alignment alignment )
{
    if ( alignment != m_data->defaultAlignment )
    {
        m_data->defaultAlignment = alignment;
        Q_EMIT defaultAlignmentChanged( alignment );

        polish();
    }
}

Qt::Alignment QskStackBox::defaultAlignment() const
{
    return m_data->defaultAlignment;
}

void QskStackBox::setAnimator( QskStackBoxAnimator* animator )
{
    if ( m_data->animator == animator )
        return;

    if ( m_data->animator )
    {
        m_data->animator->stop();
        delete m_data->animator;
    }

    if ( animator )
    {
        animator->stop();
        animator->setParent( this );
    }

    m_data->animator = animator;
}

const QskStackBoxAnimator* QskStackBox::animator() const
{
    return m_data->animator;
}

QskStackBoxAnimator* QskStackBox::animator()
{
    return m_data->animator;
}

QskStackBoxAnimator* QskStackBox::effectiveAnimator()
{
    if ( m_data->animator )
        return m_data->animator;

    // getting an animation from the skin. TODO ...

    return nullptr;
}

int QskStackBox::itemCount() const
{
    return m_data->items.count();
}

QQuickItem* QskStackBox::itemAtIndex( int index ) const
{
    return m_data->items.value( index );
}

int QskStackBox::indexOf( const QQuickItem* item ) const
{
    if ( item && ( item->parentItem() == this ) )
    {
        for ( int i = 0; i < m_data->items.count(); i++ )
        {
            if ( item == m_data->items[i] )
                return i;
        }
    }

    return -1;
}

QQuickItem* QskStackBox::currentItem() const
{
    return itemAtIndex( m_data->currentIndex );
}

int QskStackBox::currentIndex() const
{
    return m_data->currentIndex;
}

void QskStackBox::setCurrentIndex( int index )
{
    if ( index < 0 || index >= itemCount() )
    {
        // hide the current item
        index = -1;
    }

    if ( index == m_data->currentIndex )
        return;

    // stop and complete the running transition
    auto animator = effectiveAnimator();
    if ( animator )
        animator->stop();

    if ( window() && isVisible() && isInitiallyPainted() && animator )
    {
        // start the animation
        animator->setStartIndex( m_data->currentIndex );
        animator->setEndIndex( index );
        animator->setWindow( window() );
        animator->start();
    }
    else
    {
        auto item1 = itemAtIndex( m_data->currentIndex );
        auto item2 = itemAtIndex( index );

        if ( item1 )
            item1->setVisible( false );

        if ( item2 )
            item2->setVisible( true );
    }

    m_data->currentIndex = index;
    polish();

    Q_EMIT currentIndexChanged( m_data->currentIndex );
}

qreal QskStackBox::transientIndex() const
{
    if ( auto animator = m_data->animator )
    {
        if ( animator->isRunning() )
            return animator->transientIndex();
    }

    return currentIndex();
}

void QskStackBox::setCurrentItem( const QQuickItem* item )
{
    setCurrentIndex( indexOf( item ) );
}

void QskStackBox::addItem( QQuickItem* item )
{
    insertItem( -1, item );
}

void QskStackBox::addItem( QQuickItem* item, Qt::Alignment alignment )
{
    insertItem( -1, item, alignment );
}

void QskStackBox::insertItem( int index, QQuickItem* item )
{
    if ( item == nullptr || item == this )
        return;

    reparentItem( item );

    if ( !qskPlacementPolicy( item ).isEffective() )
    {
        qWarning() << "Inserting an item that is to be ignored for layouting"
            << item->metaObject()->className();

        qskSetPlacementPolicy( item, QskPlacementPolicy() );
    }

    const bool doAppend = ( index < 0 ) || ( index >= itemCount() );

    if ( item->parentItem() == this )
    {
        const int oldIndex = indexOf( item );
        if ( oldIndex >= 0 )
        {
            // the item had been inserted before

            if ( ( index == oldIndex ) || ( doAppend && ( oldIndex == itemCount() - 1 ) ) )
            {
                // already in place
                return;
            }

            m_data->items.removeAt( oldIndex );
        }
    }

    if ( doAppend )
        index = itemCount();

    m_data->items.insert( index, item );

    const int oldCurrentIndex = m_data->currentIndex;

    if ( m_data->items.count() == 1 )
    {
        m_data->currentIndex = 0;
        item->setVisible( true );
    }
    else
    {
        item->setVisible( false );

        if ( index <= m_data->currentIndex )
            m_data->currentIndex++;
    }

    if ( oldCurrentIndex != m_data->currentIndex )
        Q_EMIT currentIndexChanged( m_data->currentIndex );

    resetImplicitSize();
    polish();
}

void QskStackBox::insertItem(
    int index, QQuickItem* item, Qt::Alignment alignment )
{
    if ( auto control = qskControlCast( item ) )
        control->setLayoutAlignmentHint( alignment );

    insertItem( index, item );
}

void QskStackBox::removeAt( int index )
{
    removeItemInternal( index, true );
}

void QskStackBox::removeItemInternal( int index, bool unparent )
{
    if ( index < 0 || index >= m_data->items.count() )
        return;

    if ( unparent )
    {
        if ( auto item = m_data->items[ index ] )
            unparentItem( item );
    }

    m_data->items.removeAt( index );

    auto& currentIndex = m_data->currentIndex;

    if ( index <= currentIndex )
    {
        currentIndex--;

        if ( currentIndex < 0 && !m_data->items.isEmpty() )
            currentIndex = 0;

        if ( currentIndex >= 0 )
            m_data->items[ currentIndex ]->setVisible( true );

        Q_EMIT currentIndexChanged( currentIndex );
    }

    resetImplicitSize();
    polish();
}

void QskStackBox::removeItem( const QQuickItem* item )
{
    removeAt( indexOf( item ) );
}

void QskStackBox::autoAddItem( QQuickItem* item )
{
    removeAt( indexOf( item ) );
}

void QskStackBox::autoRemoveItem( QQuickItem* item )
{
    removeItemInternal( indexOf( item ), false );
}

void QskStackBox::clear( bool autoDelete )
{
    for ( const auto item : std::as_const( m_data->items ) )
    {
        if( autoDelete && ( item->parent() == this ) )
            delete item;
        else
            item->setParentItem( nullptr );
    }

    m_data->items.clear();

    if ( m_data->currentIndex >= 0 )
    {
        m_data->currentIndex = -1;
        Q_EMIT currentIndexChanged( m_data->currentIndex );
    }
}

QRectF QskStackBox::geometryForItemAt( int index ) const
{
    const auto r = layoutRect();

    if ( const auto item = m_data->items.value( index ) )
    {
        auto alignment = qskLayoutAlignmentHint( item );
        if ( alignment == 0 )
            alignment = m_data->defaultAlignment;

        return qskConstrainedItemRect( item, r, alignment );
    }

    return QRectF( r.x(), r.y(), 0.0, 0.0 );
}

void QskStackBox::updateLayout()
{
    if ( maybeUnresized() )
        return;

    for ( int i = 0; i < m_data->items.count(); i++ )
    {
        auto item = m_data->items[ i ];

        const auto visibility =
            ( i == m_data->currentIndex ) ? Qsk::Visible : Qsk::Hidden;

        if ( qskPlacementPolicy( item ).isAdjusting( visibility ) )
        {
            const auto rect = geometryForItemAt( i );
            qskSetItemGeometry( m_data->items[ i ], rect );
        }
    }
}

QSizeF QskStackBox::layoutSizeHint(
    Qt::SizeHint which, const QSizeF& constraint ) const
{
    if ( which == Qt::MaximumSize )
        return QSizeF();

    qreal w = -1.0;
    qreal h = -1.0;

    for ( const auto item : std::as_const( m_data->items ) )
    {
        /*
            We ignore the retainSizeWhenVisible flag and include all
            invisible items. Maybe we should offer a flag to control this ?
         */
        const auto policy = qskSizePolicy( item );

        if ( constraint.width() >= 0.0 && policy.isConstrained( Qt::Vertical ) )
        {
            const auto hint = qskSizeConstraint( item, which, constraint );
            h = qMax( h, hint.height() );
        }
        else if ( constraint.height() >= 0.0 && policy.isConstrained( Qt::Horizontal ) )
        {
            const auto hint = qskSizeConstraint( item, which, constraint );
            w = qMax( w, hint.width() );
        }
        else
        {
            const auto hint = qskSizeConstraint( item, which, QSizeF() );

            w = qMax( w, hint.width() );
            h = qMax( h, hint.height() );
        }
    }

    // minimum layout hint needs to be cached TODO ...
    return QSizeF( w, h );
}

bool QskStackBox::event( QEvent* event )
{
    switch ( static_cast< int >( event->type() ) )
    {
        case QEvent::LayoutRequest:
        {
            resetImplicitSize();
            polish();
            break;
        }
        case QEvent::ContentsRectChange:
        case QskEvent::GeometryChange:
        {
            polish();
            break;
        }
    }

    return Inherited::event( event );
}

void QskStackBox::dump() const
{
    auto debug = qDebug();

    QDebugStateSaver saver( debug );
    debug.nospace();

    const auto constraint = sizeConstraint();

    debug << "QskStackBox"
          << " w:" << constraint.width() << " h:" << constraint.height() << '\n';

    for ( int i = 0; i < m_data->items.count(); i++ )
    {
        const auto item = m_data->items[i];

        debug << "  " << i << ": ";

        const auto size = qskSizeConstraint( item, Qt::PreferredSize );
        debug << item->metaObject()->className()
              << " w:" << size.width() << " h:" << size.height();

        if ( i == m_data->currentIndex )
            debug << " [X]";

        debug << '\n';
    }
}

#include "moc_QskStackBox.cpp"
