#include "serverframebufferupdatemessage.h"
#include "serverframebufferinitmessage.h"
#include "pixelformat.h"
#include "clientglobals.h"
#include "compressioncontext.h"
#include "connectionprofiler.h"

#include <QIODevice>
#include <QRect>
#include <QBuffer>
#include <QVector>

ServerFramebufferUpdateMessage::ServerFramebufferUpdateMessage() :  ServerMessageBase(),
    m_bpp(0), m_bigEndian(0), m_redShift(0), m_redMax(0), m_greenShift(0),
    m_greenMax(0), m_blueShift(0), m_blueMax(0), m_numberOfRects(0), m_currentRectIndex(0), m_zip(0), m_fbDepth(0)
{
}

 bool ServerFramebufferUpdateMessage::loadMessage( QIODevice *device )
 {
    readUint8( device ); // discard padding...
    m_numberOfRects = readUint16( device );
    return true;
 }

QRgb ServerFramebufferUpdateMessage::readPixel( QIODevice *device ) const
{
  quint32 pixel = 0;

  switch(m_bpp)
  {
    case 1: // 8-Bit
        pixel = readUint8(device );
        break;
    case 2: // 16-Bit
        pixel = readUint16(device, !m_bigEndian );
        break;
    case 4: // 32-bit
        pixel = readUint32(device, !m_bigEndian );
        break;
    default:
        break;
  }

  int red = ((pixel >> m_redShift) & m_redMax) * 255/m_redMax;
  int green = ((pixel >> m_greenShift) & m_greenMax) * 255/m_greenMax;
  int blue = ((pixel >> m_blueShift) & m_blueMax) * 255/m_blueMax;
  return qRgb( red, green, blue );
}

void ServerFramebufferUpdateMessage::setFrameBufferPixel( uchar *framebuffer, const QRgb &pixel, int x, int y ) const
{
    int base = y * m_fbSize.width() * sizeof(QRgb) + x * sizeof(QRgb);
    qMemCopy( &framebuffer[base], &pixel, sizeof(QRgb) );
}

void ServerFramebufferUpdateMessage::fillRect( uchar *framebuffer, const QRgb &pixel, const QRect &rect ) const
{
  for( int cy = rect.top(); cy < rect.bottom() + 1; cy++ )
  {
    for( int cx = rect.left(); cx < rect.right() + 1; cx++ )
    {
      setFrameBufferPixel( framebuffer, pixel, cx, cy );
    }
  }
}

void ServerFramebufferUpdateMessage::readRawRect( QIODevice *device, uchar *framebuffer, const QRect &rect  ) const
{
    if( m_bpp == m_fbDepth / 8 )
    {
        int line = m_fbSize.width() * m_bpp;
        int pixelDataLine = rect.width() * m_bpp;
        uchar *dst = &framebuffer[rect.y() * m_fbSize.width() * m_bpp + rect.x() * m_bpp];

        for (int idx = 0; idx < rect.height(); idx++)
        {
            readBytes( device, (char*)dst, pixelDataLine );
            dst += line;
        }
    }
    else
    {
        for( int cy = rect.top(); cy < rect.bottom() + 1; cy++ )
        {
            for( int cx = rect.left(); cx < rect.right() + 1; cx++ )
            {
                setFrameBufferPixel( framebuffer, readPixel( device ), cx, cy );
            }
        }
    }
}

void ServerFramebufferUpdateMessage::readRreRect( QIODevice *device, uchar *framebuffer, const QRect &rect  ) const
{
    quint32 numberOfRects = readUint32( device );
    QRgb pixel = readPixel( device );
    fillRect( framebuffer, pixel, rect );

    for( quint32 innerRect = 0; innerRect < numberOfRects; innerRect++ )
    {
      pixel = readPixel( device );
      quint16 lx = readUint16( device );
      quint16 ly = readUint16( device );
      quint16 lw = readUint16( device );
      quint16 lh = readUint16( device );
      fillRect( framebuffer, pixel, QRect( lx, ly, lw, lh ) );
    }
}

void ServerFramebufferUpdateMessage::readCoRreRect( QIODevice *device, uchar *framebuffer, const QRect &rect  ) const
{
    quint32 numberOfRects = readUint32( device );
    QRgb pixel = readPixel( device );
    fillRect( framebuffer, pixel, rect );

    for( quint32 innerRect = 0; innerRect < numberOfRects; innerRect++ )
    {
      pixel = readPixel( device );
      quint8 lx = readUint8( device );
      quint8 ly = readUint8( device );
      quint8 lw = readUint8( device );
      quint8 lh = readUint8( device );
      fillRect( framebuffer, pixel, QRect( lx, ly, lw, lh ) );
    }
}

void ServerFramebufferUpdateMessage::readCopyRect( QIODevice *device, uchar *framebuffer, const QRect &rect ) const
{
    int src_x = readUint16(device);
    int src_y = readUint16(device);
    int dst_y = rect.y();
    int line = m_fbSize.width() * m_bpp;

    if (src_y < dst_y)
    {
        line = -line;
        src_y += (rect.height() - 1);
        dst_y += (rect.height() - 1);
    }

    uchar *dst = &framebuffer[dst_y * m_fbSize.width() * m_bpp + rect.x() * m_bpp];
    uchar *src = &framebuffer[src_y * m_fbSize.width() * m_bpp + src_x * m_bpp];

    for (int idx = 0; idx < rect.height(); idx++)
    {
        memmove(dst, src, rect.width() * m_bpp );
        dst += line;
        src += line;
    }    
}

void ServerFramebufferUpdateMessage::readZlibRect(QIODevice *device, uchar *framebuffer, const QRect &rect ) const
{
    if( m_zip == 0)
        return;

    int size = readUint32(device);
    QByteArray input(size, '\0');
    readBytes(device, input.data(), size );

    QByteArray output = m_zip->inflateBytes(input);

    if( output.isEmpty() )
    {
        qWarning("No output...");
        return;
    }

    QBuffer buffer(&output);
    buffer.open(QIODevice::ReadOnly);
    readRawRect(&buffer, framebuffer, rect );
}

void ServerFramebufferUpdateMessage::readHextileRect( QIODevice *device, uchar *framebuffer, const QRect &rect ) const
{
    QRgb bgColor = 0;
    QRgb fgColor = 0;

    for( int yIdx = rect.top(); yIdx < rect.bottom() + 1; yIdx += 16)
    {
        for( int xIdx = rect.left(); xIdx < rect.right() + 1; xIdx += 16 )
        {
            QRect tile;
            tile.setY( yIdx );
            tile.setX( xIdx );
            tile.setWidth( 16 );
            tile.setHeight( 16 );
            tile.setRight( qMin(tile.right(),rect.right() ) );
            tile.setBottom( qMin(tile.bottom(),rect.bottom() ) );

            HextileEncodings encoding = (HextileEncoding)readUint8(device);

            if( encoding.testFlag( RAW_SUB_ENCODING ) )
            {
                readRawRect( device, framebuffer, tile );
            }
            else
            {

                if( encoding.testFlag( BG_SUB_ENCODING ) )
                {
                    bgColor = readPixel(device);
                }

                if( encoding.testFlag( FG_SUB_ENCODING ) )
                {
                    fgColor = readPixel(device);
                }

                fillRect(framebuffer, bgColor, tile );

                int numSubRects = 0;
                if ( encoding.testFlag( AS_SUB_ENCODING ) )
                {
                    numSubRects = readUint8(device);

                    bool subRectsColored = encoding.testFlag(SC_SUB_ENCODING);

                    for( int idx = 0; idx < numSubRects; idx++ )
                    {
                        QRgb color = fgColor;
                        if( subRectsColored )
                            color = readPixel(device);
                        QRect subRect;
                        quint8 xY = readUint8(device);
                        quint8 wH = readUint8(device);
                        subRect.setX( tile.x() + ( ( xY & 0xF0 ) >> 4 ));
                        subRect.setY( tile.y() + ( ( xY & 0x0F ) ));
                        subRect.setWidth( ( ( wH & 0xF0 ) >> 4 ) + 1 );
                        subRect.setHeight( ( ( wH & 0x0F ) ) + 1 );
                        fillRect( framebuffer, color, subRect );
                    }
                }
            }
        }
    }
}

void ServerFramebufferUpdateMessage::setPixelFormat( const PixelFormat &props )
{
    m_bpp = props.bpp() / 8;
    m_bigEndian = props.bigEndian();
    m_redMax = props.redMax();
    m_greenMax = props.greenMax();
    m_blueMax = props.blueMax();
    m_redShift = props.redShift();
    m_greenShift = props.greenShift();
    m_blueShift = props.blueShift();
}

bool ServerFramebufferUpdateMessage::hasData() const
{
    return m_numberOfRects > 0;
}

ServerFramebufferUpdateMessage::FrameBufferUpdates ServerFramebufferUpdateMessage::readNextRectUpdate( QIODevice *device ) const
{
    FrameBufferUpdates result;
    if( m_currentRectIndex < m_numberOfRects )
    {
        quint16 x = readUint16(device);
        quint16 y = readUint16(device);
        quint16 width = readUint16(device);
        quint16 height = readUint16(device);

        result.rect = QRect( x, y, width, height  );
        result.encoding = (ClientGlobals::RfbEncoding) readUint32( device );
        m_currentRectIndex++;
    }
    else
        result.encoding = ClientGlobals::EncodingDone;
    return result;
}

void ServerFramebufferUpdateMessage::setCompressionContext( CompressionContext *ctx )
{
    m_zip = ctx;
}

void ServerFramebufferUpdateMessage::setFramebufferSize( const QSize &sz )
{
    m_fbSize = sz;
}

void ServerFramebufferUpdateMessage::setFramebufferDepth(int depth)
{
    m_fbDepth = depth;
}

