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

#include "QskSkin.h"

#include "QskAnimationHint.h"
#include "QskAspect.h"
#include "QskColorFilter.h"
#include "QskGraphic.h"
#include "QskGraphicProviderMap.h"
#include "QskStandardSymbol.h"
#include "QskPlatform.h"
#include "QskMargins.h"
#include "QskFontRole.h"

#include "QskSkinHintTable.h"
#include "QskSkinManager.h"
#include "QskSkinTransition.h"

#include <qguiapplication.h>
#include <qpa/qplatformdialoghelper.h>
#include <qpa/qplatformtheme.h>

#include <cmath>

#include "QskBox.h"
#include "QskBoxSkinlet.h"

#include "QskCheckBox.h"
#include "QskCheckBoxSkinlet.h"

#include "QskComboBox.h"
#include "QskComboBoxSkinlet.h"

#include "QskDrawer.h"
#include "QskDrawerSkinlet.h"

#include "QskFocusIndicator.h"
#include "QskFocusIndicatorSkinlet.h"

#include "QskGraphicLabel.h"
#include "QskGraphicLabelSkinlet.h"

#include "QskListView.h"
#include "QskListViewSkinlet.h"

#include "QskMenu.h"
#include "QskMenuSkinlet.h"

#include "QskPageIndicator.h"
#include "QskPageIndicatorSkinlet.h"

#include "QskPopup.h"
#include "QskPopupSkinlet.h"

#include "QskProgressBar.h"
#include "QskProgressBarSkinlet.h"

#include "QskProgressRing.h"
#include "QskProgressRingSkinlet.h"

#include "QskRadioBox.h"
#include "QskRadioBoxSkinlet.h"

#include "QskPushButton.h"
#include "QskPushButtonSkinlet.h"

#include "QskScrollView.h"
#include "QskScrollViewSkinlet.h"

#include "QskSegmentedBar.h"
#include "QskSegmentedBarSkinlet.h"

#include "QskSeparator.h"
#include "QskSeparatorSkinlet.h"

#include "QskSlider.h"
#include "QskSliderSkinlet.h"

#include "QskSpinBox.h"
#include "QskSpinBoxSkinlet.h"

#include "QskSubWindow.h"
#include "QskSubWindowSkinlet.h"

#include "QskSubWindowArea.h"
#include "QskSubWindowAreaSkinlet.h"

#include "QskSwitchButton.h"
#include "QskSwitchButtonSkinlet.h"

#include "QskTabButton.h"
#include "QskTabButtonSkinlet.h"

#include "QskTabView.h"
#include "QskTabViewSkinlet.h"

#include "QskTextLabel.h"
#include "QskTextLabelSkinlet.h"

#include "QskTextInput.h"
#include "QskTextInputSkinlet.h"

#include "QskStatusIndicator.h"
#include "QskStatusIndicatorSkinlet.h"

#include <qhash.h>

static inline QskSkinlet* qskNewSkinlet( const QMetaObject* metaObject, QskSkin* skin )
{
    const QByteArray signature = metaObject->className() + QByteArrayLiteral( "(QskSkin*)" );

    QskSkinlet* skinlet = nullptr;

    const int index = metaObject->indexOfConstructor( signature.constData() );
    if ( index >= 0 )
    {
        void* param[] = { &skinlet, &skin };
        metaObject->static_metacall( QMetaObject::CreateInstance, index, param );
    }

    return skinlet;
}

// also used in QskSkinTransition.cpp TODO ...

QFont qskResolvedFont( const QHash< QskFontRole, QFont >& fontTable,
    const QskFontRole& fontRole )
{
    auto it = fontTable.constFind( fontRole );
    if ( it != fontTable.constEnd() )
        return it.value();

    it = fontTable.constFind( QskFontRole() );
    if ( it != fontTable.constEnd() )
        return it.value();

    return QGuiApplication::font();
}

namespace
{
    class SkinletData
    {
      public:
        SkinletData( const QMetaObject* metaObject )
            : metaObject( metaObject )
            , skinlet( nullptr )
        {
        }

        ~SkinletData()
        {
            delete skinlet;
        }

        const QMetaObject* metaObject;
        QskSkinlet* skinlet; // mutable ???
    };
}

class QskSkin::PrivateData
{
  public:
    QHash< const QMetaObject*, SkinletData > skinletMap;

    QskSkinHintTable hintTable;

    QHash< QskFontRole, QFont > fonts;
    QHash< int, QskColorFilter > graphicFilters;

    QskGraphicProviderMap graphicProviders;

    int colorScheme = -1; // uninitialized
};

QskSkin::QskSkin( QObject* parent )
    : QObject( parent )
    , m_data( new PrivateData() )
{
    declareSkinlet< QskControl, QskSkinlet >();

    declareSkinlet< QskBox, QskBoxSkinlet >();
    declareSkinlet< QskCheckBox, QskCheckBoxSkinlet >();
    declareSkinlet< QskComboBox, QskComboBoxSkinlet >();
    declareSkinlet< QskDrawer, QskDrawerSkinlet >();
    declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >();
    declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >();
    declareSkinlet< QskListView, QskListViewSkinlet >();
    declareSkinlet< QskPageIndicator, QskPageIndicatorSkinlet >();
    declareSkinlet< QskPopup, QskPopupSkinlet >();
    declareSkinlet< QskMenu, QskMenuSkinlet >();
    declareSkinlet< QskPushButton, QskPushButtonSkinlet >();
    declareSkinlet< QskScrollView, QskScrollViewSkinlet >();
    declareSkinlet< QskSegmentedBar, QskSegmentedBarSkinlet >();
    declareSkinlet< QskSeparator, QskSeparatorSkinlet >();
    declareSkinlet< QskSlider, QskSliderSkinlet >();
    declareSkinlet< QskSpinBox, QskSpinBoxSkinlet >();
    declareSkinlet< QskStatusIndicator, QskStatusIndicatorSkinlet >();
    declareSkinlet< QskSubWindow, QskSubWindowSkinlet >();
    declareSkinlet< QskSubWindowArea, QskSubWindowAreaSkinlet >();
    declareSkinlet< QskSwitchButton, QskSwitchButtonSkinlet >();
    declareSkinlet< QskTabButton, QskTabButtonSkinlet >();
    declareSkinlet< QskTabView, QskTabViewSkinlet >();
    declareSkinlet< QskTextLabel, QskTextLabelSkinlet >();
    declareSkinlet< QskTextInput, QskTextInputSkinlet >();
    declareSkinlet< QskProgressBar, QskProgressBarSkinlet >();
    declareSkinlet< QskProgressRing, QskProgressRingSkinlet >();
    declareSkinlet< QskRadioBox, QskRadioBoxSkinlet >();

    const QFont font = QGuiApplication::font();
    setupFontTable( font.family(), font.italic() );

    const auto noMargins = QVariant::fromValue( QskMargins( 0 ) );

    {
        // some defaults
        const auto aspect = QskAspect::NoSubcontrol | QskAspect::Metric;

        setSkinHint( aspect | QskAspect::Margin, noMargins );
        setSkinHint( aspect | QskAspect::Padding, noMargins );
        setSkinHint( aspect | QskAspect::Spacing, 0 );
    }

    setSkinHint( QskControl::Background | QskAspect::Metric | QskAspect::Padding, noMargins );

    setSkinHint( QskControl::Background | QskAspect::Color,
        QVariant::fromValue( QskGradient() ) );
}

QskSkin::~QskSkin()
{
}

QskSkin::ColorScheme QskSkin::colorScheme() const
{
    if ( m_data->colorScheme < 0 )
        return QskSkin::UnknownScheme;

    return static_cast< QskSkin::ColorScheme >( m_data->colorScheme );
}

void QskSkin::setColorScheme( ColorScheme colorScheme )
{
    if ( colorScheme == m_data->colorScheme )
        return;

    m_data->colorScheme = colorScheme;

    const auto transitionHint = qskSkinManager->transitionHint();
    if ( transitionHint.isValid() )
    {
        QskSkinTransition transition;
        transition.setMask( QskSkinTransition::Color );
        transition.setSourceSkin( this );

        clearHints();
        initHints();

        transition.setTargetSkin( this );
        transition.run( transitionHint );
    }
    else
    {
        clearHints();
        initHints();
    }

    Q_EMIT colorSchemeChanged( colorScheme );
}

void QskSkin::setSkinHint( QskAspect aspect, const QVariant& skinHint )
{
    m_data->hintTable.setHint( aspect, skinHint );
}

const QVariant& QskSkin::skinHint( QskAspect aspect ) const
{
    return m_data->hintTable.hint( aspect );
}

void QskSkin::declareSkinlet( const QMetaObject* metaObject,
    const QMetaObject* skinletMetaObject )
{
    Q_ASSERT( skinletMetaObject->constructorCount() );

    const auto it = m_data->skinletMap.find( metaObject );

    if ( it != m_data->skinletMap.end() )
    {
        auto& entry = it.value();
        if ( entry.metaObject != skinletMetaObject )
        {
            entry.metaObject = skinletMetaObject;

            delete entry.skinlet;
            entry.skinlet = nullptr;
        }
    }
    else
    {
        m_data->skinletMap.insert( metaObject, skinletMetaObject );
    }
}

static inline void qskSetFont( QskSkin* skin,
    QskFontRole::Category category, QskFontRole::Emphasis emphasis,  
    QFont font, QFont::Weight weight )
{
    font.setWeight( weight );
    skin->setFont( { category, emphasis }, font );
}

static inline void qskSetNormalFont( QskSkin* skin,
    QskFontRole::Category category, QFont font, int pixelSize )
{
    font.setPixelSize( qRound( qskDpToPixels( pixelSize ) ) );
    qskSetFont( skin, category, QskFontRole::Normal, font, QFont::Normal );
}

void QskSkin::setupFontTable( const QString& family, bool italic )
{
    const QFont font( family, -1, -1, italic );

    qskSetNormalFont( this, QskFontRole::Caption, font, 10 );
    qskSetNormalFont( this, QskFontRole::Subtitle, font, 15 );
    qskSetNormalFont( this, QskFontRole::Body, font, 20 );
    qskSetNormalFont( this, QskFontRole::Title, font, 24 );
    qskSetNormalFont( this, QskFontRole::Headline, font, 32 );
    qskSetNormalFont( this, QskFontRole::Display, font, 66 );

    completeFontTable();
}

void QskSkin::completeFontTable()
{
    // varying the weight of QskFontRole::Normal
        
    for ( int i = QskFontRole::Caption; i <= QskFontRole::Display; i++ )
    {
        auto& table = m_data->fonts;

        const auto category = static_cast< QskFontRole::Category >( i );

        const auto it = table.constFind( { category, QskFontRole::Normal } );
        if ( it == table.constEnd() )
            continue;

        const auto normalFont = it.value();

        for ( int j = QskFontRole::VeryLow; j <= QskFontRole::VeryHigh; j++ )
        {
            const auto emphasis = static_cast< QskFontRole::Emphasis >( j );

            if ( emphasis == QskFontRole::Normal
                || table.contains( { category, emphasis } ) )
            {
                continue;
            }

#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
            const auto step = 10; // weight: [0-99]
#else
            const auto step = 100; // weight: [1-1000]
#endif

            int weight = normalFont.weight() + ( j - 2 ) * step;
            weight = qBound( static_cast<int>( QFont::Thin ),
                weight, static_cast<int>( QFont::Black ) );

            auto font = normalFont;
            font.setWeight( static_cast< QFont::Weight >( weight ) );

            m_data->fonts[ { category, emphasis } ] = font;
        }
    }
}

void QskSkin::setFont( const QskFontRole& fontRole, const QFont& font )
{
    m_data->fonts[ fontRole ] = font;
}

void QskSkin::resetFont( const QskFontRole& fontRole )
{
    m_data->fonts.remove( fontRole );
}

QFont QskSkin::font( const QskFontRole& fontRole ) const
{
    return qskResolvedFont( m_data->fonts, fontRole );
}

void QskSkin::setGraphicFilter( int graphicRole, const QskColorFilter& colorFilter )
{
    m_data->graphicFilters[ graphicRole ] = colorFilter;
}

void QskSkin::resetGraphicFilter( int graphicRole )
{
    m_data->graphicFilters.remove( graphicRole );
}

QskColorFilter QskSkin::graphicFilter( int graphicRole ) const
{
    return m_data->graphicFilters.value( graphicRole );
}

const QskSkinHintTable& QskSkin::hintTable() const
{
    return m_data->hintTable;
}

QskSkinHintTable& QskSkin::hintTable()
{
    return m_data->hintTable;
}

const QHash< QskFontRole, QFont >& QskSkin::fontTable() const
{
    return m_data->fonts;
}

const QHash< int, QskColorFilter >& QskSkin::graphicFilters() const
{
    return m_data->graphicFilters;
}

void QskSkin::addGraphicProvider(
    const QString& providerId, QskGraphicProvider* provider )
{
    m_data->graphicProviders.insert( providerId, provider );
}

QskGraphicProvider* QskSkin::graphicProvider( const QString& providerId ) const
{
    return m_data->graphicProviders.provider( providerId );
}

bool QskSkin::hasGraphicProvider() const
{
    return m_data->graphicProviders.size() > 0;
}

void QskSkin::clearHints()
{
    m_data->hintTable.clear();
    m_data->fonts.clear();
    m_data->graphicFilters.clear();
    m_data->graphicProviders.clear();
}

QString QskSkin::dialogButtonText( int action ) const
{
    const auto theme = qskPlatformTheme();

    auto text = theme->standardButtonText( action );
    text = QPlatformTheme::removeMnemonics( text );

    return text;
}

const int* QskSkin::dialogButtonLayout( Qt::Orientation orientation ) const
{
    // auto policy = QPlatformDialogHelper::UnknownLayout;
    auto policy = QPlatformDialogHelper::WinLayout;

    if ( const auto theme = qskPlatformTheme() )
    {
        const QVariant v = theme->themeHint( QPlatformTheme::DialogButtonBoxLayout );
        policy = static_cast< QPlatformDialogHelper::ButtonLayout >( v.toInt() );
    }

    return QPlatformDialogHelper::buttonLayout( orientation, policy );
}

const QMetaObject* QskSkin::skinletMetaObject( const QMetaObject* metaObject ) const
{
    while ( metaObject )
    {
        auto it = m_data->skinletMap.constFind( metaObject );
        if ( it != m_data->skinletMap.constEnd() )
            return it.value().metaObject;

        metaObject = metaObject->superClass();
    }

    return nullptr;
}

QskSkinlet* QskSkin::skinlet( const QMetaObject* metaObject )
{
    while ( metaObject )
    {
        auto it = m_data->skinletMap.find( metaObject );
        if ( it != m_data->skinletMap.end() )
        {
            auto& entry = it.value();

            if ( entry.skinlet == nullptr )
                entry.skinlet = qskNewSkinlet( entry.metaObject, this );

            return entry.skinlet;
        }

        metaObject = metaObject->superClass();
    }

    static QskSkinlet defaultSkinlet;
    return &defaultSkinlet;
}

#include "moc_QskSkin.cpp"
