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

#include "QskPushButton.h"
#include "QskAnimationHint.h"
#include "QskAspect.h"
#include "QskBoxShapeMetrics.h"
#include "QskGraphic.h"
#include "QskGraphicProvider.h"
#include "QskSkinManager.h"
#include "QskSkin.h"
#include "QskSkinlet.h"
#include "QskTextOptions.h"
#include "QskEvent.h"

QSK_SUBCONTROL( QskPushButton, Panel )
QSK_SUBCONTROL( QskPushButton, Splash )
QSK_SUBCONTROL( QskPushButton, Text )
QSK_SUBCONTROL( QskPushButton, Icon )

class QskPushButton::PrivateData
{
  public:
    PrivateData( const QString& txt )
        : text( txt )
        , isCheckable( false )
        , isIconSourceDirty( false )
        , emphasis( NoEmphasis )
    {
    }

    void ensureIcon( const QskPushButton* button )
    {
        if ( isIconSourceDirty )
        {
            if ( !iconSource.isEmpty() )
                icon = button->loadIcon( iconSource );

            isIconSourceDirty = false;
        }
    }

    QString text;

    QUrl iconSource;
    QskGraphic icon;

    bool isCheckable : 1;
    bool isIconSourceDirty : 1;
    int emphasis : 4;
};

QskPushButton::QskPushButton( QQuickItem* parent )
    : QskPushButton( QString(), parent )
{
}

QskPushButton::QskPushButton( const QString& text, QQuickItem* parent )
    : Inherited( parent )
    , m_data( new PrivateData( text ) )
{
    initSizePolicy( QskSizePolicy::Minimum, QskSizePolicy::Fixed );
}

QskPushButton::~QskPushButton()
{
}

void QskPushButton::setCheckable( bool on )
{
    if ( on != m_data->isCheckable )
    {
        m_data->isCheckable = on;
        Q_EMIT checkableChanged( on );
    }
}

bool QskPushButton::isCheckable() const
{
    return m_data->isCheckable;
}

void QskPushButton::setEmphasis( Emphasis emphasis )
{
    if ( emphasis != m_data->emphasis )
    {
        m_data->emphasis = emphasis;

        resetImplicitSize();
        update();

        Q_EMIT emphasisChanged( emphasis );
    }
}

QskPushButton::Emphasis QskPushButton::emphasis() const
{
    return static_cast< Emphasis >( m_data->emphasis );
}

void QskPushButton::setShape( const QskBoxShapeMetrics& shape )
{
    if ( setBoxShapeHint( Panel, shape ) )
        Q_EMIT shapeChanged();
}

void QskPushButton::resetShape()
{
    if ( resetBoxShapeHint( Panel ) )
        Q_EMIT shapeChanged();
}

QskBoxShapeMetrics QskPushButton::shape() const
{
    return boxShapeHint( Panel );
}

void QskPushButton::setText( const QString& text )
{
    if ( text != m_data->text )
    {
        m_data->text = text;

        resetImplicitSize();
        update();

        Q_EMIT textChanged();
    }
}

QString QskPushButton::text() const
{
    return m_data->text;
}

void QskPushButton::setTextOptions( const QskTextOptions& textOptions )
{
    if ( setTextOptionsHint( Text, textOptions ) )
        Q_EMIT textOptionsChanged();
}

QskTextOptions QskPushButton::textOptions() const
{
    return textOptionsHint( Text );
}

void QskPushButton::resetTextOptions()
{
    if ( resetTextOptionsHint( Text ) )
        Q_EMIT textOptionsChanged();
}

QFont QskPushButton::font() const
{
    return effectiveFont( Text );
}

void QskPushButton::resetIconStrutSize()
{
    if ( resetStrutSizeHint( Icon ) )
        Q_EMIT iconStrutSizeChanged();
}

void QskPushButton::setIconStrutSize( const QSizeF& size )
{
    auto newSize = size;
    if ( newSize.width() < 0.0 )
        newSize.setWidth( -1.0 );

    if ( newSize.height() < 0.0 )
        newSize.setHeight( -1.0 );

    if ( setStrutSizeHint( Icon, newSize ) )
        Q_EMIT iconStrutSizeChanged();
}

QSizeF QskPushButton::iconStrutSize() const
{
    return strutSizeHint( Icon );
}

void QskPushButton::setIconSource( const QUrl& url )
{
    if ( m_data->iconSource == url )
        return;

    m_data->iconSource = url;
    m_data->icon.reset();

    m_data->isIconSourceDirty = true;

    resetImplicitSize();
    polish();
    update();

    Q_EMIT iconSourceChanged();
}

void QskPushButton::setIconSource( const QString& source )
{
    setIconSource( QUrl( source ) );
}

QUrl QskPushButton::iconSource() const
{
    return m_data->iconSource;
}

void QskPushButton::setIcon( const QskGraphic& icon )
{
    if ( icon != m_data->icon )
    {
        m_data->icon = icon;

        if ( !m_data->iconSource.isEmpty() )
        {
            m_data->iconSource = QString();
            m_data->isIconSourceDirty = false;

            Q_EMIT iconSourceChanged();
        }

        Q_EMIT iconChanged();

        resetImplicitSize();
        polish();
        update();
    }
}

QskGraphic QskPushButton::icon() const
{
    m_data->ensureIcon( this );
    return m_data->icon;
}

bool QskPushButton::hasIcon() const
{
    return !( icon().isEmpty() && iconSource().isEmpty() );
}

void QskPushButton::updateResources()
{
    m_data->ensureIcon( this );
}

QskAspect::Variation QskPushButton::effectiveVariation() const
{
    switch( m_data->emphasis )
    {
        case VeryLowEmphasis:
            return QskAspect::Tiny;

        case LowEmphasis:
            return QskAspect::Small;

        case HighEmphasis:
            return QskAspect::Large;

        case VeryHighEmphasis:
            return QskAspect::Huge;

        default:
            return QskAspect::NoVariation;
    }
}

QRectF QskPushButton::layoutRectForSize( const QSizeF& size ) const
{
    return subControlContentsRect( size, Panel );
}

void QskPushButton::changeEvent( QEvent* event )
{
    switch ( event->type() )
    {
        case QEvent::StyleChange:
        {
            if ( !m_data->iconSource.isEmpty() &&
                qskSkinManager->skin()->hasGraphicProvider() )
            {
                // we might need to reload from a different skin
                m_data->isIconSourceDirty = true;
            }
            break;
        }
        case QEvent::LocaleChange:
        {
            if ( !m_data->text.isEmpty() )
            {
                // maybe QLocale::textDirection() has changed
                update();
            }

            break;
        }
        default:
            break;
    }

    Inherited::changeEvent( event );
}

void QskPushButton::mousePressEvent( QMouseEvent* event )
{
    Inherited::mousePressEvent( event );

    using A = QskAspect;

    const auto hint = animationHint( Splash | A::Color );

    if( hint.isValid() )
    {
        setPositionHint( Splash, qskMousePosition( event ).x() );
        startTransition( Splash | A::Metric | A::Size, hint, 0.0, 1.0 );
    }
}

QskGraphic QskPushButton::loadIcon( const QUrl& url ) const
{
    return Qsk::loadGraphic( url );
}

#include "moc_QskPushButton.cpp"
