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

#include "QskBoxShapeMetrics.h"

#include <qhashfunctions.h>
#include <qvariant.h>

#include <algorithm>

static void qskRegisterBoxShapeMetrics()
{
    qRegisterMetaType< QskBoxShapeMetrics >();

#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
    QMetaType::registerEqualsComparator< QskBoxShapeMetrics >();
#endif

    QMetaType::registerConverter< int, QskBoxShapeMetrics >(
        []( int radius ) { return QskBoxShapeMetrics( radius ); } );

    QMetaType::registerConverter< qreal, QskBoxShapeMetrics >(
        []( qreal radius ) { return QskBoxShapeMetrics( radius ); } );
}

Q_CONSTRUCTOR_FUNCTION( qskRegisterBoxShapeMetrics )

static inline QSizeF qskInterpolatedSize(
    const QSizeF& from, const QSizeF& to, qreal ratio )
{
    return from + ( to - from ) * ratio;
}

static inline qreal qskAbsoluted( qreal length, qreal percentage )
{
    // 100% means -> 0.5 of length
    percentage = qBound( 0.0, percentage, 100.0 );
    return percentage / 100.0 * 0.5 * length;
}

static inline void qskSetRadius( qreal rx, qreal ry, QSizeF& radius )
{
    radius.rwidth() = ( rx > 0.0 ) ? rx : 0.0;
    radius.rheight() = ( ry > 0.0 ) ? ry : 0.0;
}

void QskBoxShapeMetrics::setRadius(
    qreal topLeftX, qreal topLeftY,
    qreal topRightX, qreal topRightY,
    qreal bottomLeftX, qreal bottomLeftY,
    qreal bottomRightX, qreal bottomRightY ) noexcept
{
    qskSetRadius( topLeftX, topLeftY, m_radii[ Qt::TopLeftCorner ] );
    qskSetRadius( topRightX, topRightY, m_radii[ Qt::TopRightCorner ] );
    qskSetRadius( bottomLeftX, bottomLeftY, m_radii[ Qt::BottomLeftCorner ] );
    qskSetRadius( bottomRightX, bottomRightY, m_radii[ Qt::BottomRightCorner ] );
}

void QskBoxShapeMetrics::setRadius( Qt::Corner corner, qreal radiusX, qreal radiusY ) noexcept
{
    if ( ( corner >= Qt::TopLeftCorner ) && ( corner <= Qt::BottomRightCorner ) )
        qskSetRadius( radiusX, radiusY, m_radii[ corner ] );
}

void QskBoxShapeMetrics::setTopLeft( const QSizeF& radius ) noexcept
{
    setRadius( Qt::TopLeftCorner, radius );
}

void QskBoxShapeMetrics::setTopRight( const QSizeF& radius ) noexcept
{
    setRadius( Qt::TopRightCorner, radius );
}

void QskBoxShapeMetrics::setBottomLeft( const QSizeF& radius ) noexcept
{
    setRadius( Qt::BottomLeftCorner, radius );
}

void QskBoxShapeMetrics::setBottomRight( const QSizeF& radius ) noexcept
{
    setRadius( Qt::BottomRightCorner, radius );
}

QskBoxShapeMetrics QskBoxShapeMetrics::toAbsolute( const QSizeF& size ) const noexcept
{
    if ( m_sizeMode != Qt::RelativeSize )
        return *this;

    if ( size.isEmpty() )
        return QskBoxShapeMetrics();

    QskBoxShapeMetrics shape = *this;
    shape.m_sizeMode = Qt::AbsoluteSize;

    for ( int i = 0; i < 4; i++ )
    {
        auto& radius = shape.m_radii[ i ];

        if ( radius.isEmpty() )
        {
            radius.rheight() = radius.rwidth() = 0.0;
            continue;
        }

        const qreal rx = qskAbsoluted( size.width(), radius.width() );
        const qreal ry = qskAbsoluted( size.height(), radius.height() );

        switch ( m_scalingMode )
        {
            case Symmetric:
            {
                radius.rheight() = radius.rwidth() = std::min( rx, ry );
                break;
            }
            case SymmetricByMaximum:
            {
                radius.rheight() = radius.rwidth() = std::max( rx, ry );
                break;
            }
            default:
            {
                const auto ratio = radius.height() / radius.width();

                if ( ratio >= 1.0 )
                {
                    radius.rwidth() = ry / ratio;
                    radius.rheight() = ry;
                }
                else
                {
                    radius.rwidth() = rx;
                    radius.rheight() = rx * ratio;
                }
            }
        }
    }

    return shape;
}

QskBoxShapeMetrics QskBoxShapeMetrics::interpolated(
    const QskBoxShapeMetrics& to, qreal ratio ) const noexcept
{
    // what about m_aspectRatioMode != to.m_aspectRatioMode ???

    if ( ( *this == to ) || ( m_sizeMode != to.m_sizeMode ) )
        return to;

    return QskBoxShapeMetrics(
        qskInterpolatedSize( m_radii[ 0 ], to.m_radii[ 0 ], ratio ),
        qskInterpolatedSize( m_radii[ 1 ], to.m_radii[ 1 ], ratio ),
        qskInterpolatedSize( m_radii[ 2 ], to.m_radii[ 2 ], ratio ),
        qskInterpolatedSize( m_radii[ 3 ], to.m_radii[ 3 ], ratio ),
        to.m_sizeMode, to.m_scalingMode );
}

QVariant QskBoxShapeMetrics::interpolate(
    const QskBoxShapeMetrics& from, const QskBoxShapeMetrics& to, qreal progress ) noexcept
{
    return QVariant::fromValue( from.interpolated( to, progress ) );
}

QskHashValue QskBoxShapeMetrics::hash( QskHashValue seed ) const noexcept
{
    auto hash = qHash( static_cast< int >( m_sizeMode ), seed );
    return qHashBits( m_radii, sizeof( m_radii ), hash );
}

#ifndef QT_NO_DEBUG_STREAM

#include <qdebug.h>

QDebug operator<<( QDebug debug, const QskBoxShapeMetrics& metrics )
{
    QDebugStateSaver saver( debug );
    debug.nospace();

    debug << "BoxShape" << '(';
    debug << metrics.sizeMode();

    for ( int i = Qt::TopLeftCorner; i <= Qt::BottomRightCorner; i++ )
    {
        const QSizeF r = metrics.radius( static_cast< Qt::Corner >( i ) );
        debug << "(" << r.width() << ',' << r.height() << ")";
    }

    debug << ')';

    return debug;
}

#endif

#include "moc_QskBoxShapeMetrics.cpp"
