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

#include "QskRadioBox.h"
#include "QskEvent.h"
#include "QskAnimationHint.h"
#include "QskSkinlet.h"

QSK_SUBCONTROL( QskRadioBox, Panel )
QSK_SUBCONTROL( QskRadioBox, Button )
QSK_SUBCONTROL( QskRadioBox, CheckIndicatorPanel )
QSK_SUBCONTROL( QskRadioBox, CheckIndicator )
QSK_SUBCONTROL( QskRadioBox, Text )
QSK_SUBCONTROL( QskRadioBox, Ripple )

QSK_STATE( QskRadioBox, Selected, QskAspect::FirstUserState << 1 )
QSK_STATE( QskRadioBox, Pressed, QskAspect::FirstUserState << 2 )

class QskRadioBox::PrivateData
{
  public:
    QStringList options;

    int selectedIndex = -1;
    int hoveredIndex = -1;
    int focusedIndex = -1;
    int pressedIndex = -1;
};

QskRadioBox::QskRadioBox( QQuickItem* parent )
    : Inherited( parent )
    , m_data( new PrivateData() )
{
    initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed );

    setFocusPolicy( Qt::StrongFocus );
    setAcceptedMouseButtons( Qt::LeftButton );

    setPositionHint( Ripple, -1 );

    setAcceptHoverEvents( true );
}

QskRadioBox::QskRadioBox( const QStringList& options, QQuickItem* parent )
    : QskRadioBox( parent )
{
    setOptions( options );
}

QskRadioBox::QskRadioBox( const QStringList& items,
        int selectedIndex, QQuickItem* parent )
    : QskRadioBox( items, parent )
{
    if( selectedIndex >= 0 && selectedIndex < items.count() )
        m_data->selectedIndex = selectedIndex;
}

QskRadioBox::~QskRadioBox()
{
}

QRectF QskRadioBox::focusIndicatorRect() const
{
    if ( m_data->focusedIndex < 0 )
        return QRectF();

    const auto rect = contentsRect();

    auto skinlet = effectiveSkinlet();

    const auto panelRect = skinlet->sampleRect( this,
        rect, QskRadioBox::CheckIndicatorPanel, m_data->focusedIndex );

    auto y = panelRect.y();
    auto h = panelRect.height();

    const auto textRect = skinlet->sampleRect( this,
        rect, QskRadioBox::Text, m_data->focusedIndex );

    if( textRect.height() > 0.0 )
    {
        y = qMin( y, textRect.y() );
        h = qMax( h, textRect.height() );
    }

    return QRectF( rect.x(), y, rect.width(), h );
}

int QskRadioBox::selectedIndex() const
{
    return m_data->selectedIndex;
}

QStringList QskRadioBox::options() const
{
    return m_data->options;
}

QString QskRadioBox::optionAt( int index ) const
{
    return m_data->options.value( index );
}

int QskRadioBox::pressedIndex() const
{
    return m_data->pressedIndex;
}

void QskRadioBox::setSelectedIndex( int index )
{
    if( index == m_data->selectedIndex || index >= m_data->options.count() )
        return;

    if( index < 0 )
        m_data->selectedIndex = -1;
    else
        m_data->selectedIndex = index;

    Q_EMIT selectedIndexChanged( m_data->selectedIndex );
}

void QskRadioBox::setOptions( const QStringList& options )
{
    if( m_data->options == options )
        return;

    m_data->options = options;

    Q_EMIT optionsChanged( options );
    setSelectedIndex( m_data->selectedIndex );

    if( m_data->focusedIndex > options.size() )
        setFocusedIndex( options.size() - 1 );
}

void QskRadioBox::keyPressEvent( QKeyEvent* event )
{
    switch ( event->key() )
    {
        case Qt::Key_Up:
        case Qt::Key_Left:
        {
            m_data->selectedIndex = qMax( m_data->selectedIndex - 1, 0 );
            setFocusedIndex( m_data->selectedIndex );
            update();

            return;
        }
        case Qt::Key_Down:
        case Qt::Key_Right:
        {
            m_data->selectedIndex = qMin( m_data->selectedIndex + 1,
                m_data->options.size() - 1 );

            setFocusedIndex( m_data->selectedIndex );
            update();

            return;
        }
        case Qt::Key_Select:
        case Qt::Key_Return:
        case Qt::Key_Space:
        {
            m_data->selectedIndex = m_data->focusedIndex;
            update();

            return;
        }
    }

    const auto currentTabIndex = m_data->focusedIndex;
    const auto nextTabIndex = currentTabIndex + qskFocusChainIncrement( event );

    if( nextTabIndex >= m_data->options.size() || nextTabIndex < 0 )
    {
        Inherited::keyPressEvent( event );
        setFocusedIndex( -1 );
    }
    else
    {
        setFocusedIndex( nextTabIndex );

        const auto aspect = Ripple | QskAspect::Metric | QskAspect::Position;
        const auto hint = animationHint( aspect | skinStates() );

        if( hint.isValid() )
            startTransition( aspect, hint, currentTabIndex, nextTabIndex );
    }

    update();
}

void QskRadioBox::keyReleaseEvent( QKeyEvent* )
{
}

void QskRadioBox::mousePressEvent( QMouseEvent* event )
{
    auto indexAtPosition = indexAt( qskMousePosition( event ) );

    m_data->pressedIndex = indexAtPosition;

    setFocusedIndex( indexAtPosition );
    update();
}

void QskRadioBox::mouseUngrabEvent()
{
    if ( m_data->pressedIndex >= 0 )
    {
        m_data->pressedIndex = -1;
        update();
    }
}

void QskRadioBox::mouseReleaseEvent( QMouseEvent* event )
{
    const auto index = indexAt( qskMousePosition( event ) );
    if( index == m_data->pressedIndex )
        setSelectedIndex( index );

    m_data->pressedIndex = -1;
    update();
}

void QskRadioBox::hoverEnterEvent( QHoverEvent* event )
{
    const auto index = indexAt( qskHoverPosition( event ) );
    setHoveredIndex( index );
}

void QskRadioBox::hoverMoveEvent( QHoverEvent* event )
{
    const auto index = indexAt( qskHoverPosition( event ) );
    setHoveredIndex( index );
}

void QskRadioBox::hoverLeaveEvent( QHoverEvent* )
{
    setHoveredIndex( -1 );
}

void QskRadioBox::focusInEvent( QFocusEvent* event )
{
    if( event->reason() == Qt::TabFocusReason )
    {
        setFocusedIndex( 0 );
    }
    else if( event->reason() == Qt::BacktabFocusReason )
    {
        setFocusedIndex( m_data->options.size() - 1 );
    }

    update();
    Inherited::focusInEvent( event );
}

void QskRadioBox::focusOutEvent( QFocusEvent* event )
{
    setFocusedIndex( -1 );
    update();

    Inherited::focusOutEvent( event );
}

int QskRadioBox::indexAt( const QPointF& pos ) const
{
    return effectiveSkinlet()->sampleIndexAt( this,
        contentsRect(), QskRadioBox::Button, pos );
}

void QskRadioBox::setHoveredIndex( int index )
{
    if ( m_data->hoveredIndex == index )
        return;

    m_data->hoveredIndex = index;
    setPositionHint( Ripple | Hovered, index );

    update();
}

void QskRadioBox::setFocusedIndex( int index )
{
    if ( m_data->focusedIndex == index )
        return;

    m_data->focusedIndex = index;
    setPositionHint( Ripple, index );

    update();

    Q_EMIT focusIndicatorRectChanged();
}

#include "moc_QskRadioBox.cpp"
