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

#include "QskSliderSkinlet.h"
#include "QskSlider.h"

#include "QskAspect.h"
#include "QskBoxBorderMetrics.h"
#include "QskFunctions.h"

static inline QRectF qskInnerPanelRect(
    const QskSlider* slider, const QRectF& contentsRect )
{
    using Q = QskSlider;

#if 1
    auto padding = slider->paddingHint( Q::Panel );
    padding += slider->boxBorderMetricsHint( Q::Panel ).widths();

    auto r = slider->subControlRect( contentsRect, Q::Panel );
    r = r.marginsRemoved( padding );
#else
    r = slider->subControlContentsRect( contentsRect, Q::Panel );
#endif

    return r;
}

QskSliderSkinlet::QskSliderSkinlet( QskSkin* skin )
    : Inherited( skin )
{
    setNodeRoles( { PanelRole, GrooveRole, FillRole, HandleRole, RippleRole } );
}

QskSliderSkinlet::~QskSliderSkinlet()
{
}

QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable,
    const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
    const auto slider = static_cast< const QskSlider* >( skinnable );

    if ( subControl == QskSlider::Panel )
    {
        return panelRect( slider, contentsRect );
    }

    if ( subControl == QskSlider::Groove )
    {
        return grooveRect( slider, contentsRect );
    }

    if ( subControl == QskSlider::Fill )
    {
        return fillRect( slider, contentsRect );
    }

    if ( subControl == QskSlider::Handle )
    {
        return handleRect( slider, contentsRect );
    }

    if ( subControl == QskSlider::Scale )
    {
        return scaleRect( slider, contentsRect );
    }

    if ( subControl == QskSlider::Ripple )
    {
        return rippleRect( slider, contentsRect );
    }

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

QSGNode* QskSliderSkinlet::updateSubNode(
    const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
    const auto slider = static_cast< const QskSlider* >( skinnable );

    switch ( nodeRole )
    {
        case PanelRole:
        {
            return updateBoxNode( slider, node, QskSlider::Panel );
        }

        case GrooveRole:
        {
            return updateBoxNode( slider, node, QskSlider::Groove );
        }

        case FillRole:
        {
            return updateBoxNode( slider, node, QskSlider::Fill );
        }

        case HandleRole:
        {
            return updateBoxNode( slider, node, QskSlider::Handle );
        }

        case RippleRole:
        {
            return updateBoxNode( slider, node, QskSlider::Ripple );
        }
    }

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

QRectF QskSliderSkinlet::panelRect(
    const QskSlider* slider, const QRectF& contentsRect ) const
{
    auto r = contentsRect;

    const qreal size = slider->metric( QskSlider::Panel | QskAspect::Size ); // 0: no hint
    if ( size > 0 && size < r.height() )
    {
        const auto alignment = slider->alignmentHint( QskSlider::Panel );

        if ( slider->orientation() == Qt::Horizontal )
            r = qskAlignedRectF( r, r.width(), size, alignment & Qt::AlignVertical_Mask );
        else
            r = qskAlignedRectF( r, size, r.height(), alignment & Qt::AlignHorizontal_Mask );
    }

    return r;
}

QRectF QskSliderSkinlet::innerRect( const QskSlider* slider,
    const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
    auto r = qskInnerPanelRect( slider, contentsRect );

    QskSkinHintStatus status;

    const qreal extent = slider->metric( subControl | QskAspect::Size, &status );

    if ( slider->orientation() == Qt::Horizontal )
    {
        if ( status.isValid() && ( extent < r.height() ) )
        {
            r.setTop( r.center().y() - 0.5 * extent );
            r.setHeight( extent );
        }
    }
    else
    {
        if ( status.isValid() && ( extent < r.width() ) )
        {
            r.setLeft( r.center().x() - 0.5 * extent );
            r.setWidth( extent );
        }
    }

    return r;
}

QRectF QskSliderSkinlet::grooveRect(
    const QskSlider* slider, const QRectF& rect ) const
{
    return innerRect( slider, rect, QskSlider::Groove );
}

QRectF QskSliderSkinlet::scaleRect(
    const QskSlider* slider, const QRectF& rect ) const
{
    return innerRect( slider, rect, QskSlider::Groove );
}

QRectF QskSliderSkinlet::fillRect(
    const QskSlider* slider, const QRectF& contentsRect ) const
{
    const auto r = qskInnerPanelRect( slider, contentsRect );
    const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );

    auto fillRect = innerRect( slider, contentsRect, QskSlider::Fill );
    if ( slider->orientation() == Qt::Horizontal )
    {
        fillRect.setLeft( r.left() );
        fillRect.setRight( r.left() + pos * r.width() );
    }
    else
    {
        fillRect.setBottom( r.bottom() );
        fillRect.setTop( r.bottom() - pos * r.height() );
    }

    return fillRect;
}

QRectF QskSliderSkinlet::handleRect(
    const QskSlider* slider, const QRectF& contentsRect ) const
{
    using Q = QskSlider;

    auto handleSize = slider->strutSizeHint( Q::Handle );
    const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );

    const auto r = qskInnerPanelRect( slider, contentsRect );
    auto center = r.center();

    if ( slider->orientation() == Qt::Horizontal )
    {
        if ( handleSize.height() < 0.0 )
            handleSize.setHeight( r.height() );

        if ( handleSize.width() < 0.0 )
            handleSize.setWidth( handleSize.height() );

        center.setX( r.left() + pos * r.width() );
    }
    else
    {
        if ( handleSize.width() < 0.0 )
            handleSize.setWidth( r.width() );

        if ( handleSize.height() < 0.0 )
            handleSize.setHeight( handleSize.width() );

        center.setY( r.bottom() - pos * r.height() );
    }

    QRectF handleRect( 0, 0, handleSize.width(), handleSize.height() );
    handleRect.moveCenter( center );

    handleRect = handleRect.marginsRemoved( slider->marginHint( Q::Handle ) );

    return handleRect;
}

QRectF QskSliderSkinlet::rippleRect(
    const QskSlider* slider, const QRectF& rect ) const
{
    const auto rippleSize = slider->strutSizeHint( QskSlider::Ripple );
    const auto handleSize = slider->strutSizeHint( QskSlider::Handle );

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

    auto r = handleRect( slider, rect );
    r = r.marginsAdded( { w, h, w, h } );

    return r;
}

QSizeF QskSliderSkinlet::sizeHint( const QskSkinnable* skinnable,
    Qt::SizeHint which, const QSizeF& ) const
{
    if ( which != Qt::PreferredSize )
        return QSizeF();

    const auto slider = static_cast< const QskSlider* >( skinnable );

    // strutSizeHint( ... ) ???
    const qreal m1 = slider->metric( QskSlider::Panel | QskAspect::Size );
    const qreal m2 = 4 * m1;

    return ( slider->orientation() == Qt::Horizontal )
        ? QSizeF( m2, m1 ) : QSizeF( m1, m2 );
}

#include "moc_QskSliderSkinlet.cpp"
