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

#include "QskCheckBoxSkinlet.h"
#include "QskCheckBox.h"
#include "QskTextOptions.h"
#include "QskFunctions.h"

QskCheckBoxSkinlet::QskCheckBoxSkinlet( QskSkin* skin )
    : QskSkinlet( skin )
{
    setNodeRoles( { BoxRole, IndicatorRole, TextRole, RippleRole } );
}

QskCheckBoxSkinlet::~QskCheckBoxSkinlet()
{
}

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

    const auto checkBox = static_cast< const QskCheckBox* >( skinnable );

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

    if ( subControl == Q::Box )
        return boxRect( checkBox, contentsRect );

    if ( subControl == Q::Indicator )
    {
        const auto boxRect = subControlRect( skinnable, contentsRect, Q::Box );
        return skinnable->innerBox( Q::Box, boxRect );
    }

    if ( subControl == Q::Text )
        return textRect( checkBox, contentsRect );

    if ( subControl == Q::Ripple )
        return rippleRect( checkBox, contentsRect );

    return contentsRect;
}

QRectF QskCheckBoxSkinlet::textRect(
    const QskCheckBox* checkBox, const QRectF& contentsRect ) const
{
    using Q = QskCheckBox;

    const auto boxRect = subControlRect( checkBox, contentsRect, Q::Box );
    const qreal spacing = checkBox->spacingHint( Q::Panel );

    auto r = subControlRect( checkBox, contentsRect, Q::Panel );
    r = checkBox->innerBox( Q::Panel, r );

    if ( checkBox->layoutMirroring() )
        r.setRight( boxRect.left() - spacing );
    else
        r.setLeft( boxRect.right() + spacing  );

    return r;
}

QRectF QskCheckBoxSkinlet::boxRect(
    const QskCheckBox* checkBox, const QRectF& contentsRect ) const
{
    const auto size = checkBox->strutSizeHint( QskCheckBox::Box );

    auto r = checkBox->innerBox( QskCheckBox::Panel, contentsRect );

    if ( checkBox->layoutMirroring() )
        r.setLeft( r.right() - size.width() );
    else
        r.setWidth( size.width() );

    r.setTop( r.top() + 0.5 * ( r.height() - size.height() ) );
    r.setHeight( size.height() );

    return r;
}

QRectF QskCheckBoxSkinlet::rippleRect(
    const QskCheckBox* checkBox, const QRectF& rect ) const
{
    const auto rippleSize = checkBox->strutSizeHint( QskCheckBox::Ripple );
    const auto boxSize = checkBox->strutSizeHint( QskCheckBox::Box );

    const auto w = ( rippleSize.width() - boxSize.width() ) / 2;
    const auto h = ( rippleSize.height() - boxSize.height() ) / 2;

    auto r = boxRect( checkBox, rect );
    r = r.marginsAdded( { w, h, w, h } );

    return r;
}

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

    auto checkBox = static_cast< const QskCheckBox* >( skinnable );

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

        case BoxRole:
            return updateBoxNode( skinnable, node, Q::Box );

        case IndicatorRole:
            return updateSymbolNode( checkBox, node, Q::Indicator );

        case TextRole:
            return updateTextNode( checkBox, node );

        case RippleRole:
            return updateBoxNode( checkBox, node, Q::Ripple );
    }

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

QSGNode* QskCheckBoxSkinlet::updateTextNode(
    const QskCheckBox* checkBox, QSGNode* node ) const
{
    using Q = QskCheckBox;

    const auto rect = checkBox->subControlRect( Q::Text );
    const auto alignH = checkBox->layoutMirroring()
        ? Qt::AlignRight : Qt::AlignLeft;

    return QskSkinlet::updateTextNode( checkBox, node,
        rect, alignH | Qt::AlignVCenter, checkBox->text(), Q::Text );
}

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

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

    auto checkBox = static_cast< const QskCheckBox* >( skinnable );

    auto size = skinnable->strutSizeHint( QskCheckBox::Box );
    size = skinnable->outerBoxSize( Q::Panel, size );

    auto text = checkBox->text();
    if ( !text.isEmpty() )
    {
        qreal extra = skinnable->spacingHint( Q::Panel );

        if ( which == Qt::MinimumSize )
            text = 'W';

        const auto font = skinnable->effectiveFont( Q::Text );
        extra += qskHorizontalAdvance( font, text );

        size.setWidth( size.width() + extra );
    }

    return size.expandedTo( skinnable->strutSizeHint( Q::Panel ) );
}

#include "moc_QskCheckBoxSkinlet.cpp"
