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

#include "QskGraphicIO.h"
#include "QskGraphic.h"
#include "QskPainterCommand.h"

#include <qbuffer.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qvector.h>

#include <cstring>

static const char qskMagicNumber[] = "QSKG";

/*
    To avoid crashes ( fonts ), when svg2qvg was running with a different Qt
    version, than the one of the application we hardcode
    the datastream format.
 */
static const int qskDataStreamVersion = QDataStream::Qt_5_15;

static inline void qskWritePathData(
    const QPainterPath& path, QDataStream& s )
{
    s << path;
}

static inline void qskReadPathData(
    QDataStream& s, QVector< QskPainterCommand >& commands )
{
    QPainterPath path;
    s >> path;

    commands += QskPainterCommand( path );
}

static inline void qskWritePixmapData(
    const QskPainterCommand::PixmapData& data, QDataStream& s )
{
    s << data.rect << data.pixmap << data.subRect;
}

static inline void qskReadPixmapData(
    QDataStream& s, QVector< QskPainterCommand >& commands )
{
    QskPainterCommand::PixmapData data;

    s >> data.rect;
    s >> data.pixmap;
    s >> data.subRect;

    commands += QskPainterCommand( data.rect, data.pixmap, data.subRect );
}

static inline void qskWriteImageData(
    const QskPainterCommand::ImageData& data, QDataStream& s )
{
    s << data.rect << data.image << data.subRect;
}

static inline void qskReadImageData(
    QDataStream& s, QVector< QskPainterCommand >& commands )
{
    QskPainterCommand::ImageData data;

    s >> data.rect;
    s >> data.image;
    s >> data.subRect;

    quint8 flags;
    s >> flags;
    data.flags = static_cast< Qt::ImageConversionFlags >( flags );

    commands += QskPainterCommand( data.rect, data.image, data.subRect, data.flags );
}

static inline void qskWriteStateData(
    const QskPainterCommand::StateData& data, QDataStream& s )
{
    s << static_cast< quint16 >( data.flags );

    if ( data.flags & QPaintEngine::DirtyPen )
        s << data.pen;

    if ( data.flags & QPaintEngine::DirtyBrush )
        s << data.brush;

    if ( data.flags & QPaintEngine::DirtyBrushOrigin )
        s << data.brushOrigin;

    if ( data.flags & QPaintEngine::DirtyFont )
        s << data.font;

    if ( data.flags & QPaintEngine::DirtyBackground )
    {
        s << static_cast< quint8 >( data.backgroundMode );
        s << data.backgroundBrush;
    }

    if ( data.flags & QPaintEngine::DirtyTransform )
        s << data.transform;

    if ( data.flags & QPaintEngine::DirtyClipEnabled )
        s << data.isClipEnabled;

    if ( data.flags & QPaintEngine::DirtyClipRegion )
    {
        s << data.clipRegion;
        s << static_cast< quint8 >( data.clipOperation );
    }

    if ( data.flags & QPaintEngine::DirtyClipPath )
    {
        s << data.clipPath;
        s << static_cast< quint8 >( data.clipOperation );
    }

    if ( data.flags & QPaintEngine::DirtyHints )
        s << static_cast< qint32 >( data.renderHints );

    if ( data.flags & QPaintEngine::DirtyCompositionMode )
        s << static_cast< qint16 >( data.compositionMode );

    if ( data.flags & QPaintEngine::DirtyOpacity )
        s << data.opacity;
}

static inline void qskReadStateData(
    QDataStream& s, QVector< QskPainterCommand >& commands )
{
    QskPainterCommand::StateData data;

    quint16 flags;
    s >> flags;

    data.flags = static_cast< QPaintEngine::DirtyFlags >( flags );

    if ( data.flags & QPaintEngine::DirtyPen )
        s >> data.pen;

    if ( data.flags & QPaintEngine::DirtyBrush )
        s >> data.brush;

    if ( data.flags & QPaintEngine::DirtyBrushOrigin )
        s >> data.brushOrigin;

    if ( data.flags & QPaintEngine::DirtyFont )
        s >> data.font;

    if ( data.flags & QPaintEngine::DirtyBackground )
    {
        quint8 bgMode = data.backgroundMode;
        s >> bgMode >> data.backgroundBrush;
        data.backgroundMode = static_cast< Qt::BGMode >( bgMode );
    }

    if ( data.flags & QPaintEngine::DirtyTransform )
        s >> data.transform;

    if ( data.flags & QPaintEngine::DirtyClipEnabled )
        s >> data.isClipEnabled;

    if ( data.flags & QPaintEngine::DirtyClipRegion )
    {
        s >> data.clipRegion;

        quint8 clipOperation;
        s >> clipOperation;
        data.clipOperation = static_cast< Qt::ClipOperation >( clipOperation );
    }

    if ( data.flags & QPaintEngine::DirtyClipPath )
    {
        s >> data.clipPath;

        quint8 clipOperation;
        s >> clipOperation;
        data.clipOperation = static_cast< Qt::ClipOperation >( clipOperation );
    }

    if ( data.flags & QPaintEngine::DirtyHints )
    {
        qint32 renderHints;
        s >> renderHints;
        data.renderHints = static_cast< QPainter::RenderHints >( renderHints );
    }

    if ( data.flags & QPaintEngine::DirtyCompositionMode )
    {
        qint16 compositionMode;
        s >> compositionMode;
        data.compositionMode = static_cast< QPainter::CompositionMode >( compositionMode );
    }

    if ( data.flags & QPaintEngine::DirtyOpacity )
        s >> data.opacity;

    commands += QskPainterCommand( data );
}

QskGraphic QskGraphicIO::read( const QString& fileName )
{
    QFile file( fileName );
    if ( file.open( QIODevice::ReadOnly ) == false )
    {
        qWarning( "QskGraphicIO::read can't open %s", qPrintable( fileName ) );
        return QskGraphic();
    }

    return read( &file );
}

QskGraphic QskGraphicIO::read( const QByteArray& data )
{
    QBuffer buffer;
    buffer.setData( data );

    return read( &buffer );
}

QskGraphic QskGraphicIO::read( QIODevice* dev )
{
    if ( dev == nullptr )
        return QskGraphic();

    QDataStream stream( dev );
#if 1
    stream.setVersion( qskDataStreamVersion );
#endif
    stream.setByteOrder( QDataStream::BigEndian );

    char magicNumber[ 4 ];
    stream.readRawData( magicNumber, 4 );
    if ( memcmp( magicNumber, qskMagicNumber, 4 ) != 0 )
    {
        qWarning( "QskGraphicIO::read: bad magic number" );
        return QskGraphic();
    }

    QRectF viewBox;
    stream >> viewBox;

    quint32 numCommands;
    stream >> numCommands;

    QVector< QskPainterCommand > commands;
    commands.reserve( numCommands );

    for ( uint i = 0; i < numCommands; i++ )
    {
        quint8 type;
        stream >> type;

        switch ( type )
        {
            case QskPainterCommand::Path:
            {
                qskReadPathData( stream, commands );
                break;
            }
            case QskPainterCommand::Pixmap:
            {
                qskReadPixmapData( stream, commands );
                break;
            }
            case QskPainterCommand::Image:
            {
                qskReadImageData( stream, commands );
                break;
            }
            case QskPainterCommand::State:
            {
                qskReadStateData( stream, commands );
                break;
            }
            default:
                return QskGraphic();
        }
    }

    QskGraphic graphic;
    graphic.setViewBox( viewBox );
    graphic.setCommands( commands );

    return graphic;
}

bool QskGraphicIO::write( const QskGraphic& graphic, const QString& fileName )
{
    QFile file( fileName );
    if ( file.open( QIODevice::WriteOnly | QIODevice::Truncate ) == false )
    {
        qWarning( "QskGraphicIO::write can't open %s", qPrintable( fileName ) );
        return false;
    }

    return write( graphic, &file );
}

bool QskGraphicIO::write( const QskGraphic& graphic, QByteArray& data )
{
    QBuffer buffer( &data );
    return write( graphic, &buffer );
}

bool QskGraphicIO::write( const QskGraphic& graphic, QIODevice* dev )
{
    if ( dev == nullptr )
        return false;

    QDataStream stream( dev );
#if 1
    stream.setVersion( qskDataStreamVersion );
#endif
    stream.setByteOrder( QDataStream::BigEndian );
    stream.writeRawData( qskMagicNumber, 4 );

    stream << graphic.viewBox();

    const auto numCommands = graphic.commands().size();
    const QskPainterCommand* cmds = graphic.commands().constData();

    stream << static_cast< quint32 >( numCommands );

    for ( int i = 0; i < numCommands; i++ )
    {
        stream << static_cast< quint8 >( cmds[ i ].type() );

        switch ( cmds[ i ].type() )
        {
            case QskPainterCommand::Path:
            {
                qskWritePathData( *cmds[ i ].path(), stream );
                break;
            }
            case QskPainterCommand::Pixmap:
            {
                qskWritePixmapData( *cmds[ i ].pixmapData(), stream );
                break;
            }
            case QskPainterCommand::Image:
            {
                qskWriteImageData( *cmds[ i ].imageData(), stream );
                break;
            }
            case QskPainterCommand::State:
            {
                qskWriteStateData( *cmds[ i ].stateData(), stream );
                break;
            }
            default:
            {
                // cleanup ???
                return false;
            }
        }
    }

    return true;
}
