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

#include "QskAspect.h"

#include <QDebug>
#include <QMetaEnum>
#include <QtAlgorithms>
#include <QVector>

#include <limits>
#include <bitset>
#include <string>
#include <unordered_map>

static_assert( sizeof( QskAspect::Aspect ) == sizeof( quint64 ),
    "QskAspect::Aspect has to match quint64" );

namespace
{
    using namespace QskAspect;
    using namespace std;

    struct StateInfo
    {
        State state;
        QByteArray name;
    };

    struct AspectRegistry
    {
        QVector< QByteArray > subControlNames;
        unordered_map< const QMetaObject*, QVector< Subcontrol > > subControlTable;
        unordered_map< const QMetaObject*, QVector< StateInfo > > stateTable;
    };
}

Q_GLOBAL_STATIC( AspectRegistry, qskAspectRegistry )

QskAspect::State QskAspect::registerState(
    const QMetaObject* metaObject, State state, const char* name )
{
    StateInfo info;
    info.state = state;
    info.name = name;

    auto& stateTable = qskAspectRegistry->stateTable;
    stateTable[ metaObject ] += info;

    return state;
}

QskAspect::Subcontrol QskAspect::nextSubcontrol(
    const QMetaObject* metaObject, const char* name )
{
    auto& names = qskAspectRegistry->subControlNames;
    auto& hashTable = qskAspectRegistry->subControlTable;

    Q_ASSERT_X( names.size() <= LastSubcontrol,
        "QskAspect", "There are no free subcontrol aspects; please modify your"
        " application to declare fewer aspects, or increase the mask size of"
        " QskAspect::Subcontrol in QskAspect.h." );

    names += name;

    // 0 is QskAspect::Control, so we have to start with 1
    const auto subControl = static_cast< Subcontrol >( names.size() );
    hashTable[ metaObject ] += subControl;

    return subControl;
}

QByteArray QskAspect::subControlName( Subcontrol subControl )
{
    const auto& names = qskAspectRegistry->subControlNames;

    const int index = subControl;
    if ( index > 0 && index < names.size() )
        return names[ index - 1 ];

    return QByteArray();
}

QVector< QByteArray > QskAspect::subControlNames( const QMetaObject* metaObject )
{
    const auto& names = qskAspectRegistry->subControlNames;

    if ( metaObject == nullptr )
        return names;

    const auto subControls = QskAspect::subControls( metaObject );

    QVector< QByteArray > subControlNames;
    subControlNames.reserve( subControls.size() );

    for ( auto subControl : subControls )
        subControlNames += names[ subControl ];

    return subControlNames;
}

QVector< QskAspect::Subcontrol > QskAspect::subControls( const QMetaObject* metaObject )
{
    if ( metaObject )
    {
        const auto& hashTable = qskAspectRegistry->subControlTable;

        auto it = hashTable.find( metaObject );
        if ( it != hashTable.end() )
            return it->second;
    }

    return QVector< Subcontrol >();
}

#ifndef QT_NO_DEBUG_STREAM

static QByteArray qskEnumString( const char* name, int value )
{
    const QMetaObject& mo = QskAspect::staticMetaObject;

    const QMetaEnum metaEnum = mo.enumerator( mo.indexOfEnumerator( name ) );
    const char* key = metaEnum.valueToKey( value );

    return key ? QByteArray( key ) : QString::number( value ).toLatin1();
}

static QByteArray qskStateKey( const QMetaObject* metaObject, quint16 state )
{
    const auto& stateTable = qskAspectRegistry->stateTable;

    for ( auto mo = metaObject; mo != nullptr; mo = mo->superClass() )
    {
        const auto it = stateTable.find( mo );
        if ( it != stateTable.end() )
        {
            for ( const auto& info : it->second )
            {
                if ( info.state == state )
                    return info.name;
            }
        }
    }

    return QByteArray();
}

static QByteArray qskStateString(
    const QMetaObject* metaObject, QskAspect::State state )
{
    if ( state == 0 )
    {
        return "NoState";
    }

    if ( metaObject == nullptr )
    {
        const std::bitset< 16 > stateBits( state );
        return stateBits.to_string().c_str();
    }

    // not the fastest implementation, but as it is for debugging only

    QByteArray stateString;

    bool first = true;

    for ( int i = 0; i < 16; i++ )
    {
        const quint16 mask = 1 << i;

        if ( state & mask )
        {
            if ( first )
                first = false;
            else
                stateString += ", ";

            const auto key = qskStateKey( metaObject, mask );
            if ( key.isEmpty() )
            {
                const std::bitset< 16 > stateBits( state );
                stateString += stateBits.to_string().c_str();
            }
            else
            {
                stateString += key;
            }
        }
    }

    return stateString;
}

static inline QDebug qskDebugEnum(
    QDebug debug, const char* name, int value )
{
    qt_QMetaEnum_debugOperator( debug, value,
        &QskAspect::staticMetaObject, name );
    return debug;
}

QDebug operator<<( QDebug debug, const QskAspect::Type& type )
{
    return qskDebugEnum( debug, "Type", type );
}

QDebug operator<<( QDebug debug, const QskAspect::FlagPrimitive& primitive )
{
    return qskDebugEnum( debug, "FlagPrimitive", primitive );
}

QDebug operator<<( QDebug debug, const QskAspect::ColorPrimitive& primitive )
{
    return qskDebugEnum( debug, "ColorPrimitive", primitive );
}

QDebug operator<<( QDebug debug, const QskAspect::MetricPrimitive& primitive )
{
    return qskDebugEnum( debug, "MetricPrimitive", primitive );
}

QDebug operator<<( QDebug debug, const QskAspect::Subcontrol& subControl )
{
    QDebugStateSaver saver( debug );

    debug.nospace();
    debug << "QskAspect::Subcontrol" << '(';
    debug << subControlName( subControl );
    debug << ')';

    return debug;
}

QDebug operator<<( QDebug debug, const QskAspect::Placement& placement )
{
    qskDebugEnum( debug, "Placement", placement );
    return debug;
}

QDebug operator<<( QDebug debug, const QskAspect::State& state )
{
    qskDebugState( debug, nullptr, state );
    return debug;
}

QDebug operator<<( QDebug debug, const QskAspect::Aspect& aspect )
{
    qskDebugAspect( debug, nullptr, aspect );
    return debug;
}

void qskDebugState( QDebug debug, const QMetaObject* metaObject, QskAspect::State state )
{
    QDebugStateSaver saver(debug);

    debug.resetFormat();
    debug.noquote();
    debug.nospace();

    debug << "QskAspect::State( " << qskStateString( metaObject, state ) << " )";
}

void qskDebugAspect( QDebug debug, const QMetaObject* metaObject, QskAspect::Aspect aspect )
{
    using namespace QskAspect;

    QDebugStateSaver saver(debug);

    debug.resetFormat();
    debug.noquote();
    debug.nospace();

    debug << "QskAspect( ";

    const auto subControlName = QskAspect::subControlName( aspect.subControl() );
    if ( subControlName.isEmpty() )
        debug << QByteArrayLiteral( "NoSubcontrol" );
    else
        debug << subControlName;

    debug << ", " << qskEnumString( "Type", aspect.type() );
    if ( aspect.isAnimator() )
        debug << "(A)";

    switch( aspect.type() )
    {
        case Color:
        {
            if ( aspect.colorPrimitive() != 0 )
                debug << ", " << qskEnumString( "ColorPrimitive", aspect.colorPrimitive() );
            break;
        }
        case Metric:
        {
            if ( aspect.metricPrimitive() != 0 )
                debug << ", " << qskEnumString( "MetricPrimitive", aspect.metricPrimitive() );
            break;
        }
        default:
        {
            if ( aspect.flagPrimitive() != 0 )
                debug << ", " << qskEnumString( "FlagPrimitive", aspect.flagPrimitive() );
        }
    }

    if ( aspect.placement() != QskAspect::Preserved )
        debug << ", " << qskEnumString( "Placement", aspect.placement() );

    if ( aspect.state() )
        debug << ", " << qskStateString( metaObject, aspect.state() );

    debug << " )";
}

#endif

const char* QskAspect::Aspect::toPrintable() const
{
    QString tmp;

    QDebug debug( &tmp );
    debug << *this;

    // we should find a better impementation
    static QByteArray bytes[10];
    static int counter = 0;

    counter = ( counter + 1 ) % 10;

    bytes[counter] = tmp.toUtf8();
    return bytes[counter].constData();
}

QskAspect::State QskAspect::Aspect::topState() const
{
    if ( m_bits.states == NoState )
        return NoState;

    /*
        Before Qt 5.8 qCountLeadingZeroBits does not use
        _BitScanReverse - we can live with this.
     */

    const auto n = qCountLeadingZeroBits( static_cast< quint16 >( m_bits.states ) );
    return static_cast< QskAspect::State >( 1 << ( 15 - n ) );
}

#include "moc_QskAspect.cpp"
