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

#include "QskLayoutItem.h"
#include "QskControl.h"
#include "QskLayoutConstraint.h"

#include <QQuickItem>

QskLayoutItem::QskLayoutItem( QQuickItem* item,
        int row, int column, int rowSpan, int columnSpan ):
    Inherited( row, column, qMax( rowSpan, 1 ), qMax( columnSpan, 1 ), Qt::Alignment() ),
    m_isGeometryDirty( false ),
    m_isStretchable( false ),
    m_retainSizeWhenHidden( false ),
    m_unlimitedRowSpan( rowSpan <= 0 ),
    m_unlimitedColumnSpan( columnSpan <= 0 ),
    m_updateMode( UpdateWhenVisible ),
    m_item( item )
{
}

QskLayoutItem::QskLayoutItem( const QSizeF& size, int stretch, int row, int column ):
    Inherited( row, column, 1, 1, Qt::Alignment() ),
    m_isGeometryDirty( false ),
    m_isStretchable( stretch > 0 ),
    m_retainSizeWhenHidden( false ),
    m_unlimitedRowSpan( false ),
    m_unlimitedColumnSpan( false ),
    m_updateMode( UpdateWhenVisible ),
    m_spacingHint( size ),
    m_item( nullptr )
{
}

QskLayoutItem::~QskLayoutItem()
{
}

QskLayoutItem::UpdateMode QskLayoutItem::updateMode() const
{
    return m_updateMode;
}

void QskLayoutItem::setUpdateMode( UpdateMode mode )
{
    m_updateMode = mode;
}

bool QskLayoutItem::retainSizeWhenHidden() const
{
    return m_retainSizeWhenHidden;
}

void QskLayoutItem::setRetainSizeWhenHidden( bool on )
{
    m_retainSizeWhenHidden = on;
}

QSizeF QskLayoutItem::spacingHint() const
{
    return m_spacingHint;
}

void QskLayoutItem::setSpacingHint( const QSizeF& hint )
{
    m_spacingHint = hint;
}

QSizeF QskLayoutItem::sizeHint(
    Qt::SizeHint whichHint, const QSizeF& constraint ) const
{
    if ( whichHint < Qt::MinimumSize || whichHint > Qt::MaximumSize )
        return QSizeF( 0, 0 );

    if ( m_item == nullptr )
    {
        if ( m_isStretchable && whichHint == Qt::MaximumSize )
            return QSizeF( QskLayoutConstraint::unlimited, QskLayoutConstraint::unlimited );

        // a spacer item
        return m_spacingHint;
    }

    QSizeF hint( 0, 0 );

    if ( whichHint == Qt::PreferredSize && QskLayoutConstraint::hasDynamicConstraint( m_item ) )
    {
        const quint32 growFlags = QLayoutPolicy::GrowFlag | QLayoutPolicy::ExpandFlag;
        hint = QskLayoutConstraint::effectiveConstraint( m_item, whichHint );

        if ( constraint.width() > 0 )
        {
            qreal w = constraint.width();
            if ( !( sizePolicy( Qt::Horizontal ) & growFlags ) )
                w = qMin( w, hint.width() );

            hint.setWidth( w );
            hint.setHeight( QskLayoutConstraint::heightForWidth( m_item, w ) );
        }
        else if ( constraint.height() > 0 )
        {
            qreal h = constraint.height();
            if ( !( sizePolicy( Qt::Vertical ) & growFlags ) )
                h = qMin( h, hint.height() );

            hint.setWidth( QskLayoutConstraint::widthForHeight( m_item, h ) );
            hint.setHeight( h );
        }
        else
        {
            if ( dynamicConstraintOrientation() == Qt::Horizontal )
                hint.setWidth( QskLayoutConstraint::widthForHeight( m_item, hint.height() ) );
            else
                hint.setHeight( QskLayoutConstraint::heightForWidth( m_item, hint.width() ) );
        }
    }
    else
    {
        hint = QskLayoutConstraint::effectiveConstraint( m_item, whichHint );
    }

    return hint;
}

QLayoutPolicy::Policy QskLayoutItem::sizePolicy( Qt::Orientation orientation ) const
{
    auto policy = QskLayoutConstraint::sizePolicy( m_item ).policy( orientation );
#if 1
    if ( ( policy == QskSizePolicy::Preferred ) && m_item )
    {
        // QskSizePolicy::Preferred without having a preferred size is the default
        // setting of QskControl - taken from what QWidget does - but this combination
        // doesn't make much sense. Usually every derived control is supposed
        // to set specific values, but in case it has been forgotten we better
        // ignore the preferred size then.

        const QSizeF hint = QskLayoutConstraint::effectiveConstraint( m_item, Qt::PreferredSize );

        const qreal h = ( orientation == Qt::Horizontal ) ? hint.width() : hint.height();
        if ( h <= 0 )
            policy = QskSizePolicy::Ignored;
    }
#endif

    return static_cast< QLayoutPolicy::Policy >( policy );
}

void QskLayoutItem::setGeometry( const QRectF& rect )
{
    if ( m_item == nullptr )
        return;

    if( m_updateMode == UpdateNone )
    {
        if ( !m_isGeometryDirty )
            m_isGeometryDirty = ( rect != m_item->boundingRect() );

        return;
    }

    if ( m_updateMode == UpdateWhenVisible )
    {
        if ( !m_item->isVisible() )
            return;
    }

    m_isGeometryDirty = false;
    m_item->setPosition( rect.topLeft() );
    m_item->setSize( rect.size() );
}

bool QskLayoutItem::hasDynamicConstraint() const
{
    if ( m_item )
        return QskLayoutConstraint::hasDynamicConstraint( m_item );

    return false;
}

Qt::Orientation QskLayoutItem::dynamicConstraintOrientation() const
{
    Qt::Orientation orientation = Qt::Vertical;

    if ( const QskControl* control = qobject_cast< const QskControl* >( m_item ) )
    {
        const QskSizePolicy& policy = control->sizePolicy();
        if ( policy.horizontalPolicy() == QskSizePolicy::Constrained )
            return Qt::Horizontal;
        else
            return Qt::Vertical;
    }

    return orientation;
}

bool QskLayoutItem::isIgnored() const
{
    if ( m_item && !m_item->isVisible() )
        return !m_retainSizeWhenHidden;

    return false;
}

QLayoutPolicy::ControlTypes QskLayoutItem::controlTypes( LayoutSide side ) const
{
    return Inherited::controlTypes( side );
}
