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

#include "QskIntervalF.h"
#include "QskFunctions.h"

#include <qvariant.h>
#include <qrect.h>
#include <algorithm>

static void qskRegisterIntervalF()
{
    qRegisterMetaType< QskIntervalF >();

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

Q_CONSTRUCTOR_FUNCTION( qskRegisterIntervalF )

static inline QskIntervalF qskInterpolated(
    const QskIntervalF& intv1, const QskIntervalF& intv2, qreal progress )
{
    const qreal lowerBound = intv1.lowerBound()
        + progress * ( intv2.lowerBound() - intv1.lowerBound() );

    const qreal upperBound = intv1.upperBound()
        + progress * ( intv2.upperBound() - intv1.upperBound() );

    return QskIntervalF( lowerBound, upperBound );
}

QskIntervalF QskIntervalF::interpolated(
    const QskIntervalF& to, qreal progress ) const noexcept
{
    return qskInterpolated( *this, to, progress );
}

QVariant QskIntervalF::interpolate(
    const QskIntervalF& intv1, const QskIntervalF& intv2, qreal progress ) noexcept
{
    return QVariant::fromValue( qskInterpolated( intv1, intv2, progress ) );
}

void QskIntervalF::unite( const QskIntervalF& other ) noexcept
{
    if ( isValid() )
    {
        if ( other.isValid() )
        {
            if ( other.m_lowerBound < m_lowerBound )
            {
                m_lowerBound = other.m_lowerBound;
            }
            else if ( other.m_upperBound > m_upperBound )
            {
                m_upperBound = other.m_upperBound;
            }
        }
    }
    else
    {
        if ( other.isValid() )
        {
            m_lowerBound = other.m_lowerBound;
            m_upperBound = other.m_upperBound;
        }
    }
}

QskIntervalF QskIntervalF::united( const QskIntervalF& other ) const noexcept
{
    if ( isValid() )
    {
        if ( other.isValid() )
        {
            const auto min = std::min( m_lowerBound, other.m_lowerBound );
            const auto max = std::max( m_upperBound, other.m_upperBound );

            return QskIntervalF( min, max );
        }
    }
    else
    {
        if ( other.isValid() )
            return other;
    }

    return *this;
}

QskIntervalF QskIntervalF::intersected( const QskIntervalF& other ) const noexcept
{
    if ( !isValid() )
        return *this;

    if ( !other.isValid() )
        return other;

    if ( m_lowerBound <= other.m_lowerBound )
    {
        if ( m_upperBound < other.m_lowerBound )
            return QskIntervalF();

        const qreal max = std::min( m_upperBound, other.m_upperBound );
        return QskIntervalF( other.m_lowerBound, max );
    }
    else
    {
        if ( other.m_upperBound < m_lowerBound )
            return QskIntervalF();

        const qreal max = std::min( m_upperBound, other.m_upperBound );
        return QskIntervalF( m_lowerBound, max );
    }
}

bool QskIntervalF::intersects( const QskIntervalF& other ) const noexcept
{
    if ( !isValid() || !other.isValid() )
        return false;

    if ( m_lowerBound <= other.m_lowerBound )
        return m_upperBound >= other.m_lowerBound;
    else
        return m_lowerBound <= other.m_upperBound;
}

void QskIntervalF::extend( qreal value ) noexcept
{
    if ( !isValid() )
    {
        m_lowerBound = value;
        m_upperBound = value;
    }
    else if ( value < m_lowerBound )
    {
        m_lowerBound = value;
    }
    else if ( value >= m_upperBound )
    {
        m_upperBound = value;
    }
}

QskIntervalF QskIntervalF::extended( qreal value ) const noexcept
{
    if ( !isValid() )
        return QskIntervalF( value, value );

    const qreal lower = std::min( value, m_lowerBound );
    const qreal upper = std::max( value, m_upperBound );

    return QskIntervalF( lower, upper );
}

void QskIntervalF::spanFromLowerBound( qreal value ) noexcept
{
    if ( !isValid() )
    {
        m_lowerBound = value;
        m_upperBound = value;
    }
    else
    {
        m_lowerBound = value;
        if ( m_lowerBound > m_upperBound )
            m_upperBound = m_lowerBound;
    }
}

void QskIntervalF::spanFromUpperBound( qreal value ) noexcept
{
    if ( !isValid() )
    {
        m_lowerBound = value;
        m_upperBound = value;
    }
    else
    {
        m_upperBound = value;
        if ( m_lowerBound > m_upperBound )
            m_lowerBound = m_upperBound;
    }
}

QskIntervalF QskIntervalF::fuzzyAligned( qreal stepSize ) const
{
    auto v1 = m_lowerBound;
    auto v2 = m_upperBound;

    const auto max = std::numeric_limits< qreal >::max();

    if ( -max + stepSize <= v1 )
    {
        const auto v = qskFuzzyFloor( v1, stepSize );
        if ( !qskFuzzyCompare( v1, v ) )
            v1 = v;
    }

    if ( max - stepSize >= v2 )
    {
        const auto v = qskFuzzyCeil( v2, stepSize );
        if ( !qskFuzzyCompare( v2, v ) )
            v2 = v;
    }

    return QskIntervalF( v1, v2 );
}

bool QskIntervalF::fuzzyContains( qreal value ) const
{
    if ( !isValid() )
        return false;

    if ( ( value < m_lowerBound ) && !qskFuzzyCompare( value, m_lowerBound ) )
        return false;

    if ( ( value > m_upperBound ) && !qskFuzzyCompare( value, m_upperBound ) )
        return false;

    return true;
}

bool QskIntervalF::fuzzyContains( const QskIntervalF& interval ) const
{
    if ( !isValid() || !interval.isValid() )
        return false;

    if ( ( interval.m_lowerBound < m_lowerBound )
        && !qskFuzzyCompare( interval.m_lowerBound, m_lowerBound ) )
    {
        return false;
    }

    if ( ( interval.m_upperBound > m_upperBound )
        && !qskFuzzyCompare( interval.m_upperBound, m_upperBound ) )
    {
        return false;
    }

    return true;
}

bool QskIntervalF::fuzzyIsBoundary( qreal value ) const
{
    return qskFuzzyCompare( value, m_lowerBound )
        || qskFuzzyCompare( value, m_upperBound );
}

QRectF QskIntervalF::toRect( const QskIntervalF& intervalX,
    const QskIntervalF& intervalY ) noexcept
{
    return QRectF( intervalX.lowerBound(), intervalY.lowerBound(),
        intervalX.length(), intervalY.length() ).normalized();
}

#ifndef QT_NO_DEBUG_STREAM

#include <qdebug.h>

QDebug operator<<( QDebug debug, const QskIntervalF& interval )
{
    debug.nospace() << "QskIntervalF("
        << interval.lowerBound() << "," << interval.upperBound() << ")";

    return debug.space();
}

#endif

#include "moc_QskIntervalF.cpp"
