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

#include "QskPushButtonSkinlet.h"
#include "QskPushButton.h"
#include "QskTextOptions.h"

#include "QskAnimationHint.h"
#include "QskGraphic.h"
#include "QskSubcontrolLayoutEngine.h"
#include "QskSGNode.h"

static inline Qt::Orientation qskOrientation( const QskPushButton* button )
{
    // For the moment we only handle the orientation TODO ...

    const auto direction = button->flagHint(
        QskPushButton::Panel | QskAspect::Direction, Qsk::LeftToRight );

    if ( direction == Qsk::LeftToRight || direction == Qsk::RightToLeft )
        return Qt::Horizontal;
    else
        return Qt::Vertical;
}

namespace
{
    class LayoutEngine : public QskSubcontrolLayoutEngine
    {
      public:
        LayoutEngine( const QskPushButton* button )
            : QskSubcontrolLayoutEngine( qskOrientation( button ) )
        {
            setSpacing( button->spacingHint( QskPushButton::Panel ) );

            setGraphicTextElements( button,
                QskPushButton::Text, button->text(),
                QskPushButton::Icon, button->icon().defaultSize() );

            const auto alignment = button->alignmentHint(
                QskPushButton::Panel, Qt::AlignCenter );

            setFixedContent( QskPushButton::Text, Qt::Horizontal, alignment );
        }
    };
}

QskPushButtonSkinlet::QskPushButtonSkinlet( QskSkin* skin )
    : Inherited( skin )
{
    setNodeRoles( { PanelRole, SplashRole, IconRole, TextRole } );
}

QskPushButtonSkinlet::~QskPushButtonSkinlet() = default;

QRectF QskPushButtonSkinlet::subControlRect( const QskSkinnable* skinnable,
    const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
    using Q = QskPushButton;

    const auto button = static_cast< const QskPushButton* >( skinnable );

    if ( subControl == Q::Panel )
        return contentsRect;

    if ( subControl == Q::Splash )
        return splashRect( button, contentsRect );

    if ( ( subControl == Q::Text ) || ( subControl == Q::Icon ) )
    {
        const auto r = button->subControlContentsRect( contentsRect, Q::Panel );

        LayoutEngine layoutEngine( button );
        layoutEngine.setGeometries( r );

        return layoutEngine.subControlRect( subControl );
    }

    return Inherited::subControlRect( skinnable, contentsRect, subControl );
}

QSGNode* QskPushButtonSkinlet::updateSubNode(
    const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
    using Q = QskPushButton;

    const auto button = static_cast< const QskPushButton* >( skinnable );

    switch ( nodeRole )
    {
        case PanelRole:
            return updateBoxNode( button, node, Q::Panel );

        case SplashRole:
            return updateSplashNode( button, node );

        case TextRole:
            return updateTextNode( button, node );

        case IconRole:
            return updateGraphicNode( button, node, button->icon(), Q::Icon );
    }

    return Inherited::updateSubNode( skinnable, nodeRole, node );
}

QRectF QskPushButtonSkinlet::splashRect(
    const QskPushButton* button, const QRectF& contentsRect ) const
{
    using Q = QskPushButton;

    QRectF rect;

    const auto ratio = button->metric( Q::Splash | QskAspect::Size );
    if ( ratio > 0.0 )
    {
        rect = subControlRect( button, contentsRect, Q::Panel );

        const auto pos = button->positionHint( Q::Splash );
        const qreal w = 2.0 * rect.width() * ratio;

        rect.setX( pos - 0.5 * w );
        rect.setWidth( w );
    }

    return rect;
}

QSGNode* QskPushButtonSkinlet::updateTextNode(
    const QskPushButton* button, QSGNode* node ) const
{
    using Q = QskPushButton;

    const auto rect = button->subControlRect( Q::Text ).toAlignedRect();

    const auto textHeight = button->effectiveFontHeight( Q::Text );
    if ( !button->clip() && ( rect.height() < textHeight ) )
        return nullptr;

    const auto alignment = button->alignmentHint( Q::Text, Qt::AlignCenter );

    return QskSkinlet::updateTextNode( button, node, rect,
        alignment, button->text(), Q::Text );
}

QSGNode* QskPushButtonSkinlet::updateSplashNode(
    const QskPushButton* button, QSGNode* node ) const
{
    using Q = QskPushButton;

    const auto splashRect = button->subControlRect( Q::Splash );
    if ( splashRect.isEmpty() )
        return nullptr;

    auto clipNode = updateBoxClipNode( button, node,
        button->subControlRect( Q::Panel ), Q::Panel );

    if ( clipNode )
    {
        auto boxNode = QskSGNode::findChildNode( clipNode, SplashRole );
        boxNode = updateBoxNode( button, boxNode, splashRect, Q::Splash );

        if ( boxNode == nullptr )
            return nullptr;

        QskSGNode::setNodeRole( boxNode, SplashRole );
        if ( boxNode->parent() != clipNode )
            clipNode->appendChildNode( boxNode );
    }

    return clipNode;
}

QSizeF QskPushButtonSkinlet::sizeHint( const QskSkinnable* skinnable,
    Qt::SizeHint which, const QSizeF& ) const
{
    using Q = QskPushButton;

    if ( which != Qt::PreferredSize )
        return QSizeF();

    const auto button = static_cast< const QskPushButton* >( skinnable );

    LayoutEngine layoutEngine( button );

    auto size = layoutEngine.sizeHint( which, QSizeF() );
    size = button->outerBoxSize( Q::Panel, size );
    size = size.expandedTo( button->strutSizeHint( Q::Panel ) );
    size = size.grownBy( skinnable->marginHint( Q::Panel ) );

    return size;
}

#include "moc_QskPushButtonSkinlet.cpp"
