/******************************************************************************
 * Copyright (C) 2021 Edelhirsch Software GmbH
 *           SPDX-License-Identifier: BSD-3-Clause
 *****************************************************************************/

#include "LightDisplay.h"
#include "Skin.h"

#include <QskArcMetrics.h>
#include <QskEvent.h>

#include <QtMath>

QSK_SUBCONTROL( LightDisplay, Panel )
QSK_SUBCONTROL( LightDisplay, Groove )
QSK_SUBCONTROL( LightDisplay, ColdAndWarmArc )
QSK_SUBCONTROL( LightDisplay, Tickmarks )
QSK_SUBCONTROL( LightDisplay, ValueText )
QSK_SUBCONTROL( LightDisplay, LeftLabel )
QSK_SUBCONTROL( LightDisplay, RightLabel )
QSK_SUBCONTROL( LightDisplay, Knob )

QSK_STATE( LightDisplay, Pressed, ( QskAspect::FirstUserState << 1 ) )

namespace
{
    bool angleInRange( const QskArcMetrics& metrics, qreal angle )
    {
        return angle >= metrics.startAngle() && angle <= metrics.endAngle();
    }

    qreal angleDiff( qreal angle1, qreal angle2 )
    {
        // Qt wraps around at 180 degrees
        qreal diff = qAbs( angle1 ) - qAbs( angle2 );
        return qAbs( diff );
    }
}

LightDisplay::LightDisplay( QQuickItem* parent )
    : QskBoundedValueInput( parent )
{
    setAlignmentHint( LeftLabel, Qt::AlignRight );
    setAlignmentHint( ValueText, Qt::AlignRight );

    setBoundaries( 0, 100 );
}

bool LightDisplay::isPressed() const
{
    return hasSkinState( Pressed );
}

void LightDisplay::mousePressEvent( QMouseEvent* event )
{
    QRectF handleRect = subControlRect( LightDisplay::Knob );

    if ( handleRect.contains( event->pos() ) )
    {
        setSkinStateFlag( Pressed );
    }
    else
    {
        QskBoundedValueInput::mousePressEvent( event );
    }
}

void LightDisplay::mouseMoveEvent( QMouseEvent* event )
{
    if ( !isPressed() )
        return;

    const auto mousePos = qskMousePosition( event );
    const auto rect = subControlRect( ColdAndWarmArc );

    bool arcContainsMousePos = arcContainsPoint( rect, mousePos );

    if( !arcContainsMousePos )
    {
        setSkinStateFlag( Pressed, false );
        return;
    }

    const auto metrics = arcMetricsHint( ColdAndWarmArc );
    qreal angle = angleFromPoint( rect, mousePos );

    const int tolerance = 20;

    if( !angleInRange( metrics, angle ) )
    {
        // we're slightly outside the range, but don't want to give up
        // the Pressed state

        if( angleDiff( angle, metrics.startAngle() ) < tolerance )
        {
            angle = metrics.startAngle();
        }
        else if( angleDiff( angle, metrics.endAngle() ) < tolerance )
        {
            angle = metrics.endAngle();
        }
    }

    qreal ratio = ( metrics.spanAngle() - angle ) / metrics.spanAngle();
    setValueAsRatio( ratio );
}

void LightDisplay::mouseReleaseEvent( QMouseEvent* /*event*/ )
{
    setSkinStateFlag( Pressed, false );
}

qreal LightDisplay::angleFromPoint( const QRectF& rect, const QPointF& point ) const
{
    QPointF circlePos( point.x() - rect.center().x(),
        rect.center().y() - point.y() );

    const qreal atan = qAtan2( circlePos.y(), circlePos.x() );
    const qreal angle = qRadiansToDegrees( atan );
    return angle;
}

bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) const
{
    // putting this in an own function just because it might be useful
    // at other places in the future

    const auto metrics = arcMetricsHint( ColdAndWarmArc );
    const int tolerance = 20;

    // 1. check angle
    QPointF circlePos(
        point.x() - rect.center().x(),
        rect.center().y() - point.y() );

    const qreal angle = angleFromPoint( rect, point );

    const bool angleWithinRange = angleInRange( metrics, angle )
        || angleDiff( angle, metrics.startAngle() ) <= tolerance
        || angleDiff( angle, metrics.endAngle() ) <= tolerance;

    // 2. check whether point is on arc
    const qreal radiusMax = rect.width() / 2;
    const qreal arcWidth = metrics.thickness();
    const qreal radiusMin = radiusMax - arcWidth;

    const qreal polarRadius = qSqrt( qPow( circlePos.x(), 2 ) + qPow( circlePos.y(), 2 ) );
    const bool pointOnArc = ( polarRadius + tolerance ) > radiusMin
        && ( polarRadius - tolerance ) < radiusMax;

    bool ret = angleWithinRange && pointOnArc;

    return ret;
}

#include "moc_LightDisplay.cpp"
