/******************************************************************************
 * QSkinny - Copyright (C) 2016 Uwe Rathmann
 * This file may be used under the terms of the QSkinny License, Version 1.0
 *****************************************************************************/

#include "QskLayout.h"
#include "QskLayoutItem.h"
#include "QskLayoutEngine.h"
#include "QskSizePolicy.h"
#include "QskWindow.h"
#include "QskEvent.h"

#include <limits>

class QskLayout::PrivateData
{
public:
    PrivateData():
        isActive( true )
    {
    }

    bool isActive : 1;

    QskLayoutEngine engine;
};

QskLayout::QskLayout( QQuickItem* parent ):
    QskControl( parent ),
    m_data( new PrivateData() )
{
    // what about maintaining a tab chain for the children - TODO ...
}

QskLayout::~QskLayout()
{
    disconnect( this, 0, this, 0 ); // destructor runs on invalidate else
    setActive( false );
}

void QskLayout::setActive( bool on )
{
    if ( on == m_data->isActive )
        return;

    m_data->isActive = on;

    for ( int i = 0; i < itemCount(); ++i )
    {
        QQuickItem* item = itemAtIndex( i );
        if ( item )
            setItemActive( item, on );
    }

    if ( on )
    {
        resetImplicitSize();
        polish();
    }
}

bool QskLayout::isActive() const
{
    return m_data->isActive;
}

int QskLayout::itemCount() const
{
    return m_data->engine.itemCount();
}

QQuickItem* QskLayout::itemAtIndex( int index ) const
{
    QskLayoutItem* layoutItem = m_data->engine.layoutItemAt( index );
    if ( layoutItem )
        return layoutItem->item();

    return nullptr;
}

int QskLayout::indexOf( const QQuickItem* item ) const
{
    if ( item != nullptr )
        return m_data->engine.indexOf( item );

    return -1;
}

void QskLayout::insertItemInternal( QskLayoutItem* layoutItem, int index )
{
    // check if item is already inserted ???

    QQuickItem* item = layoutItem->item();

    if ( index > itemCount() )
        index = -1; // append

    QskLayoutEngine& engine = this->engine();

    if ( item )
    {
        if ( item->parent() == nullptr )
            item->setParent( this );

        if ( item->parentItem() != this )
            item->setParentItem( this );

        if ( index >= 0 )
        {
            // to have a proper focus tab chain

            for ( int i = index; i < engine.itemCount(); i++ )
            {
                QskLayoutItem* layoutItem = engine.layoutItemAt( index );
                if ( layoutItem && layoutItem->item() )
                    item->stackBefore( layoutItem->item() );
            }
        }
    }

    engine.insertLayoutItem( layoutItem, index );

    if ( m_data->isActive )
    {
        setItemActive( item, true );

        resetImplicitSize();
        polish();
    }
}

void QskLayout::removeAt( int index )
{
    QskLayoutEngine& engine = this->engine();

    QskLayoutItem* layoutItem = engine.layoutItemAt( index );
    if ( layoutItem == nullptr )
        return;

    setItemActive( layoutItem->item(), false );
    engine.removeItem( layoutItem );

    layoutItemRemoved( layoutItem, index );

    delete layoutItem;

    if ( m_data->isActive )
    {
        resetImplicitSize();
        polish();
    }
}

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

void QskLayout::clear()
{
    const bool isActive = m_data->isActive;
    setActive( false );

    for ( int i = itemCount() - 1; i >= 0; i-- )
        removeAt( i );

    setActive( isActive );
}

void QskLayout::setupLayoutItem( QskLayoutItem* layoutItem, int index )
{
    Q_UNUSED( layoutItem )
    Q_UNUSED( index )
}

void QskLayout::layoutItemInserted( QskLayoutItem* layoutItem, int index )
{
    Q_UNUSED( layoutItem )
    Q_UNUSED( index )
}

void QskLayout::layoutItemRemoved( QskLayoutItem* layoutItem, int index )
{
    Q_UNUSED( layoutItem )
    Q_UNUSED( index )
}

void QskLayout::activate()
{
    if ( m_data->isActive )
        polish();
}

void QskLayout::invalidate()
{
    engine().invalidate();
    activate();

    resetImplicitSize();
}

void QskLayout::adjustItem( const QQuickItem* item )
{
    adjustItemAt( indexOf( item ) );
}

void QskLayout::adjustItemAt( int index )
{
    QskLayoutItem* layoutItem = engine().layoutItemAt( index );
    if ( layoutItem == nullptr )
        return;

    // setting UpdateNone to all others ???
    layoutItem->setUpdateMode( QskLayoutItem::UpdateAlways );
    engine().setGeometries( alignedLayoutRect( layoutRect() ) );
    layoutItem->setUpdateMode( QskLayoutItem::UpdateWhenVisible );
}

void QskLayout::updateLayout()
{
    if ( m_data->isActive )
        engine().setGeometries( alignedLayoutRect( layoutRect() ) );
}

QRectF QskLayout::alignedLayoutRect( const QRectF& rect ) const
{
    return rect;
}

void QskLayout::geometryChangeEvent( QskGeometryChangeEvent* event )
{
    Inherited::geometryChangeEvent( event );

    if ( event->isResized() )
        activate();
}

void QskLayout::setItemActive( const QQuickItem* item, bool on )
{
    if ( item == nullptr )
        return;

    // QskControl sends QEvent::LayoutRequest

    const bool hasLayoutRequests = qobject_cast< const QskControl* >( item );
    if ( !hasLayoutRequests )
    {
        if ( on )
        {
            connect( item, &QQuickItem::implicitWidthChanged,
                this, &QskLayout::invalidate );

            connect( item, &QQuickItem::implicitHeightChanged,
                this, &QskLayout::invalidate );
        }
        else
        {
            disconnect( item, &QQuickItem::implicitWidthChanged,
                this, &QskLayout::invalidate );

            disconnect( item, &QQuickItem::implicitHeightChanged,
                this, &QskLayout::invalidate );
        }
    }

    if ( on )
        connect( item, &QQuickItem::visibleChanged, this, &QskLayout::activate );
    else
        disconnect( item, &QQuickItem::visibleChanged, this, &QskLayout::activate );
}

QskLayoutEngine& QskLayout::engine()
{
    return m_data->engine;
}

const QskLayoutEngine& QskLayout::engine() const
{
    return m_data->engine;
}

void QskLayout::itemChange( ItemChange change, const ItemChangeData& value )
{
    Inherited::itemChange( change, value );

    switch( change )
    {
        case ItemChildRemovedChange:
        {
            removeItem( value.item );
            break;
        }
        case QQuickItem::ItemVisibleHasChanged:
        {
            if ( value.boolValue )
                activate();
            break;
        }
        case QQuickItem::ItemSceneChange:
        {
            if ( value.window )
                activate();
            break;
        }
        default:
            break;
    }
}

bool QskLayout::event( QEvent* event )
{
    switch( event->type() )
    {
        case QEvent::LayoutRequest:
        {
            invalidate();
            break;
        }
        case QEvent::LayoutDirectionChange:
        {
            m_data->engine.setVisualDirection(
                layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight );

            activate();
            break;
        }
        case QEvent::ContentsRectChange:
        {
            activate();
            break;
        }
        default:
            break;
    }

    return Inherited::event( event );
}

#include "moc_QskLayout.cpp"
