/* This file is part of the KDE project
   Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
   Copyright (C) 2004 Ignacio Castaño <castano@ludicon.com>
   Copyright (C) 2010 Troy Unrau <troy@kde.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

#include "ras.h"

#include <QtGui/QImage>
#include <QtCore/QDataStream>

#include <kdebug.h>

namespace { // Private.
	// format info from http://www.fileformat.info/format/sunraster/egff.htm

	// Header format of saved files.
	quint32 rasMagicBigEndian = 0x59a66a95;
	// quint32 rasMagicLittleEndian = 0x956aa659; # used to support wrong encoded files
	
	enum RASType {
		RAS_TYPE_OLD			= 0x0,
		RAS_TYPE_STANDARD		= 0x1,
		RAS_TYPE_BYTE_ENCODED		= 0x2,
		RAS_TYPE_RGB_FORMAT		= 0x3,
		RAS_TYPE_TIFF_FORMAT		= 0x4,
		RAS_TYPE_IFF_FORMAT		= 0x5,
		RAS_TYPE_EXPERIMENTAL		= 0xFFFF
	};
	
	enum RASColorMapType {
		RAS_COLOR_MAP_TYPE_NONE		= 0x0,
		RAS_COLOR_MAP_TYPE_RGB		= 0x1,
		RAS_COLOR_MAP_TYPE_RAW		= 0x2
	};
	
	struct RasHeader {
	  quint32 MagicNumber;
	  quint32 Width;
	  quint32 Height;
	  quint32 Depth;
	  quint32 Length;
	  quint32 Type;
	  quint32 ColorMapType;
	  quint32 ColorMapLength;
	  enum { SIZE = 32 }; // 8 fields of four bytes each
	};

	static QDataStream & operator>> ( QDataStream & s, RasHeader & head )
	{
		s >> head.MagicNumber;
		s >> head.Width;
		s >> head.Height;
		s >> head.Depth;
		s >> head.Length;
		s >> head.Type;
		s >> head.ColorMapType;
		s >> head.ColorMapLength;
		/*qDebug() << "MagicNumber: " << head.MagicNumber
		         << "Width: " << head.Width
		         << "Height: " << head.Height
		         << "Depth: " << head.Depth
		         << "Length: " << head.Length
		         << "Type: " << head.Type
		         << "ColorMapType: " << head.ColorMapType
		         << "ColorMapLength: " << head.ColorMapLength;*/
		return s;
	}

	static bool IsSupported( const RasHeader & head )
	{
	        // check magic number
		if ( head.MagicNumber != rasMagicBigEndian) {
		  return false;
		}
		// check for an appropriate depth
		// we support 8bit+palette, 24bit and 32bit ONLY!
		// TODO: add support for 1bit
		if ( ! ((head.Depth == 8 && head.ColorMapType == 1)
		         || head.Depth == 24 || head.Depth == 32) ){
		  return false;
		}
		// the Type field adds support for RLE(BGR), RGB and other encodings
		// we support Type 1: Normal(BGR) and Type 3: Normal(RGB) ONLY!
		// TODO: add support for Type 2: RLE(BGR) & Type 4,5: TIFF/IFF
		if ( ! (head.Type == 1 || head.Type == 3) ){
		  return false;
		}
		// Old files didn't have Length set - reject them for now
		// TODO: add length recalculation to support old files
		if ( !head.Length ) {
		  return false;
		}
		return true;
	}

	static bool LoadRAS( QDataStream & s, const RasHeader & ras, QImage &img )
	{
	    s.device()->seek(RasHeader::SIZE);
	    // Read palette if needed.
            QVector<quint8> palette(ras.ColorMapLength);
	    if ( ras.ColorMapType == 1 ) {
		for (quint32 i = 0; i < ras.ColorMapLength; ++i) { s >> palette[i]; }
	    }
	    
	    // each line must be a factor of 16 bits, so they may contain padding
	    // this will be 1 if padding required, 0 otherwise
	    int paddingrequired = (ras.Width*(ras.Depth/8) % 2);
	    
	    // qDebug() << "paddingrequired: " << paddingrequired;
	    // don't trust ras.Length
            QVector<quint8> input(ras.Length);
	    
	    int i = 0;
	    while ( ! s.atEnd()) {
		s >> input[i];
		// I guess we need to find out if we're at the end of a line
		if ( paddingrequired && i != 0 && !(i % (ras.Width*(ras.Depth/8))) ) {
		  s >> input[i];
		}
		i++;
	      }
	    
	    // Allocate image
	    img = QImage(ras.Width, ras.Height, QImage::Format_ARGB32);

            // Reconstruct image from RGB palette if we have a palette
	    // TODO: make generic so it works with 24bit or 32bit palettes
	    if ( ras.ColorMapType == 1 && ras.Depth == 8) {
		quint8 red, green, blue;
		for ( quint32 y = 0; y < ras.Height; y++ ){
		  for ( quint32 x = 0; x < ras.Width; x++ ) {
		    red = palette[(int)input[y*ras.Width + x]];
		    green = palette[(int)input[y*ras.Width + x] + (ras.ColorMapLength/3)];
		    blue = palette[(int)input[y*ras.Width + x] + 2*(ras.ColorMapLength/3)];
		    img.setPixel(x, y, qRgb(red, green, blue));
		  }
		}
		
	    }
	    
	    if ( ras.ColorMapType == 0 && ras.Depth == 24 && (ras.Type == 1 || ras.Type == 2)) {
		quint8 red, green, blue;
		for ( quint32 y = 0; y < ras.Height; y++ ){
		  for ( quint32 x = 0; x < ras.Width; x++ ) {
		    red = input[y*3*ras.Width + x*3 + 2];
		    green = input[y*3*ras.Width + x*3 + 1];
		    blue = input[y*3*ras.Width + x*3];
		    img.setPixel(x, y, qRgb(red, green, blue));
		  }
		}
	    }

	    if ( ras.ColorMapType == 0 && ras.Depth == 24 && ras.Type == 3) {
		quint8 red, green, blue;
		for ( quint32 y = 0; y < ras.Height; y++ ){
		  for ( quint32 x = 0; x < ras.Width; x++ ) {
		    red = input[y*3*ras.Width + x*3];
		    green = input[y*3*ras.Width + x*3 + 1];
		    blue = input[y*3*ras.Width + x*3 + 2];
		    img.setPixel(x, y, qRgb(red, green, blue));
		  }
		}
	    }

	    if ( ras.ColorMapType == 0 && ras.Depth == 32 && (ras.Type == 1 || ras.Type == 2)) {
		quint8 red, green, blue;
		for ( quint32 y = 0; y < ras.Height; y++ ){
		  for ( quint32 x = 0; x < ras.Width; x++ ) {
		    red = input[y*4*ras.Width + x*4 + 3];
		    green = input[y*4*ras.Width + x*4 + 2];
		    blue = input[y*4*ras.Width + x*4 + 1];
		    img.setPixel(x, y, qRgb(red, green, blue));
		  }
		}
	    }
	    
	    if ( ras.ColorMapType == 0 && ras.Depth == 32 && ras.Type == 3 ) {
		quint8 red, green, blue;
		for ( quint32 y = 0; y < ras.Height; y++ ){
		  for ( quint32 x = 0; x < ras.Width; x++ ) {
		    red = input[y*4*ras.Width + x*4 + 1];
		    green = input[y*4*ras.Width + x*4 + 2];
		    blue = input[y*4*ras.Width + x*4 + 3];
		    img.setPixel(x, y, qRgb(red, green, blue));
		  }
		}
	    }
	    
            return true;
	}
} // namespace

RASHandler::RASHandler()
{
}

QByteArray RASHandler::name() const
{
    return "ras";
}

bool RASHandler::canRead() const
{
    if (canRead(device())) {
        setFormat("ras");
        return true;
    }
    return false;
}

bool RASHandler::canRead(QIODevice *device)
{
    if (!device) {
        qWarning("RASHandler::canRead() called with no device");
        return false;
    }
    
    if (device->isSequential()) {
        qWarning("Reading ras files from sequential devices not supported");
        return false;
        }
        
    qint64 oldPos = device->pos();
    QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
    int readBytes = head.size(); // this should always be 32 bytes

    device->seek(oldPos);
    
    if (readBytes < RasHeader::SIZE) {
        return false;
    }

    QDataStream stream(head);
    stream.setByteOrder(QDataStream::BigEndian);
    RasHeader ras;
    stream >> ras;
    return IsSupported(ras);   
}
    
bool RASHandler::read(QImage *outImage)
{
    QDataStream s( device() );
    s.setByteOrder( QDataStream::BigEndian );

    // Read image header.
    RasHeader ras;
    s >> ras;
    // TODO: add support for old versions of RAS where Length may be zero in header
    s.device()->seek( RasHeader::SIZE + ras.Length + ras.ColorMapLength );
    
    // Check image file format. Type 2 is RLE, which causing seeking to be silly.
    if( !s.atEnd() && ras.Type != 2) {
        kDebug(399) << "This RAS file is not valid, or an older version of the format.";
        return false;
    }

    // Check supported file types.
    if( !IsSupported(ras) ) {
        kDebug(399) << "This RAS file is not supported.";
        return false;
    }

    QImage img;
    bool result = LoadRAS(s, ras, img);

    if( result == false ) {
        kDebug(399) << "Error loading RAS file.";
        return false;
    }

    *outImage = img;
    return true;
}
/*
bool RASHandler::write(const QImage &image){
  return false;
}*/

class RASPlugin : public QImageIOPlugin
{
public:
    QStringList keys() const;
    Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
    QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
};

QStringList RASPlugin::keys() const
{
    return QStringList() << "ras" << "RAS";
}

QImageIOPlugin::Capabilities RASPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
  
    if (format == "ras" || format == "RAS")
        return Capabilities(CanRead);
//         return Capabilities(CanRead | CanWrite);
    if (!format.isEmpty())
        return 0;
    if (!device->isOpen())
        return 0;

    Capabilities cap;	
    if (device->isReadable() && RASHandler::canRead(device))
        cap |= CanRead;
    if (device->isWritable())
        cap |= CanWrite;
    return cap;
}

QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format) const
{
    QImageIOHandler *handler = new RASHandler;
    handler->setDevice(device);
    handler->setFormat(format);
    return handler;
}


Q_EXPORT_STATIC_PLUGIN(RASPlugin)
Q_EXPORT_PLUGIN2(ras, RASPlugin)
