/******************************************************************************
 * QSkinny - Copyright (C) 2016 Uwe Rathmann
 * This file may be used under the terms of the QSkinny License, Version 1.0
 *****************************************************************************/

#include "QskDialogButtonBox.h"
#include "QskDialogButton.h"
#include "QskLinearBox.h"
#include "QskSkin.h"

#include <QKeyEvent>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformdialoghelper.h>

QSK_SUBCONTROL( QskDialogButtonBox, Panel )

static void qskSendEventTo( QObject* object, QEvent::Type type )
{
    QEvent event( type );
    QCoreApplication::sendEvent( object, &event );
}

static inline QskDialog::ButtonRole qskButtonRole(
    QskDialog::StandardButton standardButton )
{
    QPlatformDialogHelper::StandardButton sb =
        static_cast< QPlatformDialogHelper::StandardButton >( standardButton );

    QPlatformDialogHelper::ButtonRole role = QPlatformDialogHelper::buttonRole( sb );

    return static_cast< QskDialog::ButtonRole >( role );
}

static void qskAddToLayout( const QList< QskPushButton* >& buttonList,
    bool reverse, QskLinearBox* layoutBox )
{
    if ( reverse )
    {
        for ( int i = buttonList.count() - 1; i >= 0; i-- )
            layoutBox->addItem( buttonList[i] );
    }
    else
    {
        for ( int i = 0; i < buttonList.count(); i++ )
            layoutBox->addItem( buttonList[i] );
    }
}

class QskDialogButtonBox::PrivateData
{
public:
    PrivateData():
        layoutBox( nullptr ),
        centeredButtons( false ),
        dirtyLayout( false ),
        clickedButton( QskDialog::NoButton )
    {
    }

    QskLinearBox* layoutBox;

    QList< QskPushButton* > buttonLists[QskDialog::NButtonRoles];

    bool centeredButtons : 1;
    bool dirtyLayout : 1;

    QskDialog::StandardButton clickedButton;
};

QskDialogButtonBox::QskDialogButtonBox( QQuickItem* parent ):
    QskDialogButtonBox( Qt::Horizontal, parent )
{
}

QskDialogButtonBox::QskDialogButtonBox( Qt::Orientation orientation, QQuickItem* parent ):
    Inherited( parent ),
    m_data( new PrivateData() )
{
    setPolishOnResize( true );
    setOrientation( orientation );
}

QskDialogButtonBox::~QskDialogButtonBox()
{
}

void QskDialogButtonBox::setOrientation( Qt::Orientation orientation )
{
    if ( m_data->layoutBox && m_data->layoutBox->orientation() == orientation )
        return;

    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        for ( QskPushButton* button : qskAsConst( m_data->buttonLists[i] ) )
        {
            // avoid that buttons get deleted
            // together with the layout

            button->setParent( nullptr );
        }
    }

    delete m_data->layoutBox;

    m_data->layoutBox = new QskLinearBox( orientation, this );
    m_data->layoutBox->setObjectName( QStringLiteral( "DialogButtonBoxLayout" ) );

    if ( orientation == Qt::Horizontal )
        setSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Fixed );
    else
        setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Preferred );

    invalidateLayout();

    Q_EMIT orientationChanged();
}

Qt::Orientation QskDialogButtonBox::orientation() const
{
    return m_data->layoutBox->orientation();
}

QskAspect::Subcontrol QskDialogButtonBox::effectiveSubcontrol(
    QskAspect::Subcontrol subControl ) const
{
    if ( subControl == QskBox::Panel )
        return QskDialogButtonBox::Panel;

    return Inherited::effectiveSubcontrol( subControl );
}

QSizeF QskDialogButtonBox::contentsSizeHint() const
{
    if ( m_data->dirtyLayout )
    {
        const_cast< QskDialogButtonBox* >( this )->rearrangeButtons();
        m_data->dirtyLayout = false;
    }

    return outerBoxSize( Panel, m_data->layoutBox->contentsSizeHint() );
}

void QskDialogButtonBox::invalidateLayout()
{
    m_data->dirtyLayout = true;

    resetImplicitSize();
    polish();
}

void QskDialogButtonBox::updateLayout()
{
    if ( m_data->dirtyLayout )
    {
        rearrangeButtons();
        m_data->dirtyLayout = false;

        if ( parentItem() )
            qskSendEventTo( parentItem(), QEvent::LayoutRequest );
    }

    m_data->layoutBox->setGeometry( layoutRect() );
}

void QskDialogButtonBox::rearrangeButtons()
{
    // Result differs from the widget counter parts. Needs more
    // investigation - TODO ...

    auto layoutBox = m_data->layoutBox;

    const bool isActive = layoutBox->isActive();
    layoutBox->setActive( false );

    layoutBox->clear();

    const int* currentLayout = effectiveSkin()->dialogButtonLayout( orientation() );

    if ( m_data->centeredButtons )
        layoutBox->addStretch( 10 );

    while ( *currentLayout != QPlatformDialogHelper::EOL )
    {
        const int role = ( *currentLayout & ~QPlatformDialogHelper::Reverse );
        const bool reverse = ( *currentLayout & QPlatformDialogHelper::Reverse );

        switch ( role )
        {
            case QPlatformDialogHelper::Stretch:
            {
                if ( !m_data->centeredButtons )
                    layoutBox->addStretch( 1 );

                break;
            }
            case QPlatformDialogHelper::AcceptRole:
            {
                const QList< QskPushButton* >& buttons = m_data->buttonLists[role];

                if ( !buttons.isEmpty() )
                    layoutBox->addItem( buttons.first() );

                break;
            }
            case QPlatformDialogHelper::AlternateRole:
            {
                const QList< QskPushButton* >& buttons =
                    m_data->buttonLists[QskDialog::AcceptRole];

                if ( buttons.size() > 1 )
                    qskAddToLayout( buttons.mid( 1 ), reverse, layoutBox );

                break;
            }
            case QPlatformDialogHelper::DestructiveRole:
            case QPlatformDialogHelper::RejectRole:
            case QPlatformDialogHelper::ActionRole:
            case QPlatformDialogHelper::HelpRole:
            case QPlatformDialogHelper::YesRole:
            case QPlatformDialogHelper::NoRole:
            case QPlatformDialogHelper::ApplyRole:
            case QPlatformDialogHelper::ResetRole:
            {
                const QList< QskPushButton* > buttons = m_data->buttonLists[role];

                if ( !buttons.isEmpty() )
                    qskAddToLayout( buttons, reverse, layoutBox );

                break;
            }
        }

        ++currentLayout;
    }

    if ( m_data->centeredButtons )
        layoutBox->addStretch( 1 );

    layoutBox->setActive( isActive );

    // organizing the tab chain ???
}

void QskDialogButtonBox::setCenteredButtons( bool centered )
{
    if ( centered != m_data->centeredButtons )
    {
        m_data->centeredButtons = centered;
        invalidateLayout();

        Q_EMIT centeredButtonsChanged();
    }
}

bool QskDialogButtonBox::centeredButtons() const
{
    return m_data->centeredButtons;
}

void QskDialogButtonBox::addButton( QskPushButton* button, QskDialog::ButtonRole role )
{
    if ( role < 0 || role >= QskDialog::NButtonRoles )
        return;

    connect( button, &QskPushButton::clicked, this, &QskDialogButtonBox::onButtonClicked );

    m_data->buttonLists[role] += button;
    invalidateLayout();
}

void QskDialogButtonBox::addButton( QskDialog::StandardButton standardButton )
{
    QskPushButton* button = createButton( standardButton );
    if ( button )
        addButton( button, qskButtonRole( standardButton ) );
}

void QskDialogButtonBox::removeButton( QskPushButton* button )
{
    // ChildRemove Events !!!

    if ( button == nullptr )
        return;

    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        QList< QskPushButton* >& buttons = m_data->buttonLists[i];
        if ( buttons.removeOne( button ) )
        {
            disconnect( button, &QskPushButton::clicked,
                this, &QskDialogButtonBox::onButtonClicked );

            invalidateLayout();
            return;
        }
    }
}

QskPushButton* QskDialogButtonBox::createButton(
    QskDialog::StandardButton standardButton ) const
{
    return new QskDialogButton( standardButton );
}

void QskDialogButtonBox::clear()
{
    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        for ( const QQuickItem* button : qskAsConst( m_data->buttonLists[i] ) )
            delete button;
    }

    invalidateLayout();
}

void QskDialogButtonBox::setStandardButtons(
    QskDialog::StandardButtons standardButton )
{
    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        for ( const QQuickItem* button : qskAsConst( m_data->buttonLists[i] ) )
            delete button;
    }

    for ( int i = QskDialog::Ok; i <= QskDialog::RestoreDefaults; i <<= 1 )
    {
        const QskDialog::StandardButton id = static_cast< QskDialog::StandardButton >( i );
        if ( id & standardButton )
            addButton( id );
    }

    invalidateLayout();
}

QList< QskPushButton* > QskDialogButtonBox::buttons() const
{
    QList< QskPushButton* > buttons;

    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
        buttons += m_data->buttonLists[i];

    return buttons;
}

QskDialog::ButtonRole QskDialogButtonBox::buttonRole( const QskPushButton* button ) const
{
    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        const QList< QskPushButton* >& buttons = m_data->buttonLists[i];

        for ( const QskPushButton* btn : buttons )
        {
            if ( button == btn )
                return static_cast< QskDialog::ButtonRole >( i );
        }
    }

    return QskDialog::InvalidRole;
}

QskDialog::StandardButton QskDialogButtonBox::defaultButtonCandidate() const
{
    // not the fastest code ever, but usually we always
    // have a AcceptRole or YesRole button

    static const QVector< QskDialog::ButtonRole > fallBackRoles =
    {
        QskDialog::AcceptRole, QskDialog::YesRole,
        QskDialog::RejectRole, QskDialog::NoRole, QskDialog::DestructiveRole,
        QskDialog::ActionRole, QskDialog::ResetRole,
        QskDialog::ApplyRole, QskDialog::HelpRole
    };

    for ( auto role : fallBackRoles )
    {
        QskDialog::StandardButton defaultButton =
            standardButton( buttonFromRole( role ) );

        if ( defaultButton != QskDialog::NoButton )
            return defaultButton;
    }

    return QskDialog::NoButton;
}

void QskDialogButtonBox::onButtonClicked()
{
    QskPushButton* button = qobject_cast< QskPushButton* >( sender() );
    if ( button == nullptr )
        return;

    switch( buttonRole( button ) )
    {
        case QskDialog::AcceptRole:
        case QskDialog::YesRole:
        {
            m_data->clickedButton = standardButton( button );

            Q_EMIT clicked( button );
            Q_EMIT accepted();
            break;
        }
        case QskDialog::RejectRole:
        case QskDialog::NoRole:
        case QskDialog::DestructiveRole:
        {
            m_data->clickedButton = standardButton( button );

            Q_EMIT clicked( button );
            Q_EMIT rejected();
            break;
        }
        default:
        {
            m_data->clickedButton = QskDialog::NoButton;
            Q_EMIT clicked( button );

            break;
        }
    }
}

QskDialog::StandardButtons QskDialogButtonBox::standardButtons() const
{
    QskDialog::StandardButtons standardButtons;

    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        const QList< QskPushButton* >& buttons = m_data->buttonLists[i];

        for ( const QskPushButton* btn : buttons )
            standardButtons |= this->standardButton( btn );
    }

    return standardButtons;
}

QskDialog::StandardButton QskDialogButtonBox::standardButton( const QskPushButton* button ) const
{
    if ( button )
    {
        if ( auto dialogButton = qobject_cast< const QskDialogButton* >( button ) )
            return dialogButton->standardButton();

        const QVariant value = button->property( "standardButton" );
        if ( value.canConvert< int >() )
            return static_cast< QskDialog::StandardButton >( value.toInt() );
    }

    return QskDialog::NoButton;
}

QskPushButton* QskDialogButtonBox::button(
    QskDialog::StandardButton standardButton ) const
{
    for ( int i = 0; i < QskDialog::NButtonRoles; i++ )
    {
        const QList< QskPushButton* >& buttons = m_data->buttonLists[i];
        for ( QskPushButton* btn : buttons )
        {
            if ( this->standardButton( btn ) == standardButton )
                return btn;
        }
    }

    return nullptr;
}

QskPushButton* QskDialogButtonBox::buttonFromRole( QskDialog::ButtonRole role ) const
{
    if ( role < 0 || role >= QskDialog::NButtonRoles )
        return nullptr;

    const auto& buttonList = m_data->buttonLists[role];
    return buttonList.isEmpty() ? nullptr : buttonList.first();
}

QskDialog::StandardButton QskDialogButtonBox::clickedButton() const
{
    return m_data->clickedButton;
}

bool QskDialogButtonBox::event( QEvent* event )
{
    if ( event->type() == QEvent::LayoutRequest )
        resetImplicitSize();

    return Inherited::event( event );
}

bool QskDialogButtonBox::isDefaultButtonKeyEvent( const QKeyEvent* event )
{
    if ( event->modifiers() & Qt::KeypadModifier && event->key() == Qt::Key_Enter )
    {
        return ( event->modifiers() & Qt::KeypadModifier )
            && ( event->key() == Qt::Key_Enter );
    }
    else
    {
        return ( event->key() == Qt::Key_Enter ) ||
            ( event->key() == Qt::Key_Return );
    }
}

QString QskDialogButtonBox::buttonText( QskDialog::StandardButton standardButton )
{
    const QPlatformTheme* theme = QGuiApplicationPrivate::platformTheme();
    QString text = theme->standardButtonText( standardButton );

#if QT_VERSION < QT_VERSION_CHECK( 5, 7, 0 )
    text.remove( '&' );
#else
    text = QPlatformTheme::removeMnemonics( text );
#endif

    return text;
}

#include "moc_QskDialogButtonBox.cpp"
