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

#include "QskLayoutElement.h"
#include "QskLayoutMetrics.h"
#include "QskSizePolicy.h"

static inline bool qskCanGrow(
    const QskLayoutElement* element, Qt::Orientation orientation )
{
    const quint32 growFlags = QskSizePolicy::GrowFlag | QskSizePolicy::ExpandFlag;
    return element->sizePolicy().policy( orientation ) & growFlags;
}

QskLayoutElement::QskLayoutElement()
{
}

QskLayoutElement::~QskLayoutElement()
{
}

QSizeF QskLayoutElement::sizeConstraint(
    Qt::SizeHint which, const QSizeF& constraint ) const
{
    if ( constraint.isValid() )
        return constraint;

    auto policy = sizePolicy();

    bool ignoreWidth = false;
    bool ignoreHeight = false;

    if ( which == Qt::PreferredSize )
    {
        /*
            First we are checking the IgnoreFlag, to avoid doing
            pointless calculations.
         */
        ignoreWidth = policy.policy( Qt::Horizontal ) & QskSizePolicy::IgnoreFlag;
        ignoreHeight = policy.policy( Qt::Vertical ) & QskSizePolicy::IgnoreFlag;

        if ( ( ignoreWidth && ignoreHeight )
            || ( ignoreWidth && constraint.height() >= 0.0 )
            || ( ignoreHeight && constraint.width() >= 0.0 ) )
        {
            return QSizeF();
        }
    }

    const auto whichH = policy.effectiveSizeHintType( which, Qt::Horizontal );
    const auto whichV = policy.effectiveSizeHintType( which, Qt::Vertical );

    QSizeF size;

    int constraintType = QskSizePolicy::Unconstrained;

    /*
        We apply a constraint - even, when the policy is unconstrained.
        Do we really want to do this ???
     */
    if ( constraint.height() >= 0.0 )
    {
        const auto h = boundedSize( Qt::Vertical, constraint.height() );
        size = sizeHint( whichV, QSizeF( -1.0, h ) );

        if ( ( whichH != whichV ) || ( size.height() != h ) )
            constraintType = QskSizePolicy::WidthForHeight;
    }
    else if ( constraint.width() >= 0.0 )
    {
        const auto w = boundedSize( Qt::Horizontal, constraint.width() );
        size = sizeHint( whichH, QSizeF( w, -1.0 ) );

        if ( ( whichV != whichH ) || ( size.width() != w ) )
            constraintType = QskSizePolicy::HeightForWidth;
    }
    else
    {
        constraintType = policy.constraintType();

        switch( constraintType )
        {
            case QskSizePolicy::WidthForHeight:
            {
                size = sizeHint( whichV );
                break;
            }
            case QskSizePolicy::HeightForWidth:
            {
                size = sizeHint( whichH );
                break;
            }
            default:
            {
                if ( whichV != whichH )
                {
                    if ( !ignoreWidth )
                        size.rwidth() = sizeHint( whichH ).width();

                    if ( !ignoreHeight )
                        size.rheight() = sizeHint( whichV ).height();
                }
                else
                {
                    size = sizeHint( whichH );
                }
            }
        }
    }

    switch( constraintType )
    {
        case QskSizePolicy::HeightForWidth:
        {
            const QSizeF c( size.width(), -1.0 );
            size.rheight() = sizeHint( whichV, c ).height();
            break;
        }
        case QskSizePolicy::WidthForHeight:
        {
            const QSizeF c( -1.0, size.height() );
            size.rwidth() = sizeHint( whichH, c ).width();
            break;
        }
    }

    if ( ignoreWidth || constraint.width() >= 0.0 )
        size.rwidth() = -1.0;

    if ( ignoreHeight || constraint.height() >= 0.0 )
        size.rheight() = -1.0;

    return size;
}

QSizeF QskLayoutElement::constrainedSize( const QSizeF& size ) const
{
    if ( size.width() <= 0.0 && size.height() <= 0.0 )
        return QSizeF( 0.0, 0.0 );

    QSizeF min, max;

    const auto policy = sizePolicy();

    switch( policy.constraintType() )
    {
        case QskSizePolicy::WidthForHeight:
        {
            const qreal h = boundedSize( Qt::Vertical, size.height() );

            min = sizeConstraint( Qt::MinimumSize, QSizeF( -1.0, h ) );
            max = sizeConstraint( Qt::MaximumSize, QSizeF( -1.0, h ) );

            min.rheight() = max.rheight() = h;
            break;
        }
        case QskSizePolicy::HeightForWidth:
        {
            const qreal w = boundedSize( Qt::Horizontal, size.width() );

            min = sizeConstraint( Qt::MinimumSize, QSizeF( w, -1.0 ) );
            max = sizeConstraint( Qt::MaximumSize, QSizeF( w, -1.0 ) );

            min.rwidth() = max.rwidth() = w;
            break;
        }
        default:
        {
            min = sizeConstraint( Qt::MinimumSize, QSizeF() );
            max = sizeConstraint( Qt::MaximumSize, QSizeF() );
        }
    }

    qreal width = size.width();
    qreal height = size.height();

    if ( max.width() >= 0.0 )
        width = qMin( width, max.width() );

    if ( max.height() >= 0.0 )
        height = qMin( height, max.height() );

    width = qMax( width, min.width() );
    height = qMax( height, min.height() );

    return QSizeF( width, height );
}

QskLayoutMetrics QskLayoutElement::metrics(
    Qt::Orientation orientation, qreal constraint ) const
{
    const auto policy = sizePolicy().policy( orientation );

    qreal minimum, preferred, maximum;

    const auto expandFlags = QskSizePolicy::GrowFlag | QskSizePolicy::ExpandFlag;

    if ( ( policy & QskSizePolicy::ShrinkFlag ) &&
        ( policy & expandFlags ) && ( policy & QskSizePolicy::IgnoreFlag ) )
    {
        // we don't need to calculate the preferred size

        minimum = metric( orientation, Qt::MinimumSize, -1.0 );
        maximum = metric( orientation, Qt::MaximumSize, -1.0 );
        preferred = minimum;
    }
    else
    {
        if ( constraint >= 0.0 )
        {
            if ( !( policy & QskSizePolicy::ConstrainedFlag ) )
                constraint = -1.0;
        }

        preferred = metric( orientation, Qt::PreferredSize, constraint );

        if ( policy & QskSizePolicy::ShrinkFlag )
            minimum = metric( orientation, Qt::MinimumSize, -1.0 );
        else
            minimum = preferred;

        if ( policy & expandFlags )
            maximum = metric( orientation, Qt::MaximumSize, -1.0 );
        else
            maximum = preferred;

        if ( policy & QskSizePolicy::IgnoreFlag )
            preferred = minimum;
    }

    return QskLayoutMetrics( minimum, preferred, maximum );
}

qreal QskLayoutElement::metric( Qt::Orientation orientation,
    Qt::SizeHint which, qreal constraint ) const
{
    qreal metric = 0.0;

    if ( which == Qt::MinimumSize )
    {
        const auto hint = sizeHint( Qt::MinimumSize );

        if ( orientation == Qt::Horizontal )
            metric = hint.width();
        else
            metric = hint.height();

        if ( metric < 0.0 )
            metric = 0.0;
    }
    else if ( which == Qt::MaximumSize )
    {
        const auto hint = sizeHint( Qt::MaximumSize );

        if ( orientation == Qt::Horizontal )
            metric = hint.width();
        else
            metric = hint.height();

        if ( metric < 0.0 )
            metric = QskLayoutMetrics::unlimited;
    }
    else if ( which == Qt::PreferredSize )
    {
        const auto constraintType = sizePolicy().constraintType();

        if ( constraintType == QskSizePolicy::Unconstrained )
        {
            const auto hint = sizeHint( Qt::PreferredSize );

            if ( orientation == Qt::Horizontal )
                metric = hint.width();
            else
                metric = hint.height();
        }
        else
        {
            if ( constraint < 0.0 )
            {
                auto hint = sizeHint( Qt::PreferredSize );

                if ( orientation == Qt::Horizontal )
                {
                    if ( constraintType == QskSizePolicy::WidthForHeight )
                        hint.setWidth( widthForHeight( hint.height() ) );

                    metric = hint.width();
                }
                else
                {
                    if ( constraintType == QskSizePolicy::HeightForWidth )
                        hint.setHeight( heightForWidth( hint.width() ) );

                    metric = hint.height();
                }
            }
            else
            {
                if ( orientation == Qt::Horizontal )
                {
                    if ( !qskCanGrow( this, Qt::Vertical ) )
                    {
                        const auto maxH = sizeHint( Qt::PreferredSize ).height();

                        if ( maxH >= 0.0 )
                            constraint = qMin( constraint, maxH );
                    }

                    metric = widthForHeight( constraint );
                }
                else
                {
                    if ( !qskCanGrow( this, Qt::Horizontal ) )
                    {
                        const auto maxW = sizeHint( Qt::PreferredSize ).width();

                        if ( maxW >= 0.0 )
                            constraint = qMin( constraint, maxW );
                    }

                    metric = heightForWidth( constraint );
                }
            }
        }

        if ( metric < 0.0 )
            metric = 0.0;
    }

    return metric;
}

qreal QskLayoutElement::boundedSize( Qt::Orientation orientation, qreal size ) const
{
    const auto policy = sizePolicy();

    const auto whichMin = policy.effectiveSizeHintType( Qt::MinimumSize, orientation );
    const auto whichMax = policy.effectiveSizeHintType( Qt::MaximumSize, orientation );

    const auto hintMin = sizeHint( whichMin );
    const auto hintMax = ( whichMax == whichMin ) ? hintMin : sizeHint( whichMax );

    if ( orientation == Qt::Horizontal )
    {
        if ( hintMax.width() >= 0.0 )
            size = qMin( size, hintMax.width() );

        size = qMax( size, hintMin.width() );
    }
    else
    {
        if ( hintMax.height() >= 0.0 )
            size = qMin( size, hintMax.height() );

        size = qMax( size, hintMin.height() );
    }

    return size;
}

#include "QskQuick.h"

QskItemLayoutElement::QskItemLayoutElement( const QQuickItem* item )
    : m_item( item )
{
}

QskSizePolicy QskItemLayoutElement::sizePolicy() const
{
    return qskSizePolicy( m_item );
}

QSizeF QskItemLayoutElement::sizeHint( Qt::SizeHint which,
    const QSizeF& constraint ) const
{
    return qskEffectiveSizeHint( m_item, which, constraint );
}

Qt::Alignment QskItemLayoutElement::alignment() const
{
    return qskLayoutAlignmentHint( m_item );
}
