#include "blkhandler.h"
#include <QImageIOPlugin>
#include <QImageIOHandler>
#include <QImage>

/* BLK image format goes something like this:
 * uint32 flags
 * uint16 width
 * uint16 height
 * uint16 frames
 * <offsets>
 *   uint32 offset  // actual_offset = offset + 4
 *   uint16 width	// should always be 128
 *   uint16 height	// should always be 128
 *   	:
 *   	:
 * <blocks>
 */


/* Decode 555/565 code
 * 555:
 * 0123 4567 0123 4567
 * -rrr rr-- ---- ----
 * ---- --gg ggg- ----
 * ---- ---- ---b bbbb
 *
 * 565:
 * 0123 4567 0123 4567
 * rrrr r--- ---- ----
 * ---- -ggg ggg- ----
 * ---- ---- ---b bbbb
 */

#define DECODE_555(v)           \
	(qRgb(                      \
		  ((quint32)(v) & 0x7c00) >> 7,  \
		  ((quint32)(v) & 0x03e0) >> 2,  \
		  ((quint32)(v) & 0x001f) << 3  \
		 ))

#define DECODE_565(v)           \
	(qRgb(                      \
		  ((quint32)(v) & 0xf800) >> 8 , \
		  ((quint32)(v) & 0x07e0) >> 3 , \
		  ((quint32)(v) & 0x001f) << 3   \
		 ))

BLKImageHandler::BLKImageHandler()
{ /* noop */ }

bool BLKImageHandler::canRead() const
{
	return canRead(device()); // defer to the real canRead()
}

QByteArray BLKImageHandler::name() const
{
	return "blk";
}

bool BLKImageHandler::canRead(QIODevice *device)
{
	if (!device) {
		qWarning("BLKImageHandler::canRead() called with no device");
		return false;
	}

	// Return if we don't have enough bytes for the header.
	if (device->bytesAvailable() < 10)
		return false;

	qint32 flags;
	qint16 width, height, frames;

	// fire our <s>cigarettes</s> shit!
	device->read((char*)&flags, 4); flags = swapEndianLong(flags);
	device->read((char*)&width, 2); width = swapEndianShort(width);
	device->read((char*)&height, 2); height = swapEndianShort(height);
	device->read((char*)&frames, 2); frames = swapEndianShort(frames);

	// reset in case of incompetency
	device->reset();

	// if the number of frames doesn't match the width and height, panic
	if (width * height != frames)
		return false;

	// all is well, as far as we can tell.
	return true;
}

bool BLKImageHandler::read(QImage *out)
{
	QIODevice *dev = device();

	if (dev->isOpen() && dev->bytesAvailable() < 10)
		return false;

	qint32 flags;
	qint16 width, height, frames;
	bool is_565;

	dev->read((char*)&flags, 4); flags = swapEndianLong(flags);
	dev->read((char*)&width, 2); width = swapEndianShort(width);
	dev->read((char*)&height, 2); height = swapEndianShort(height);
	dev->read((char*)&frames, 2); frames = swapEndianShort(frames);
	
	is_565 = flags & 0x01;

	if (frames != width * height)
		return false;

	qint32 *offsets = new qint32[frames];
	qint16 iwidth, iheight;

	// read in the offsets. Add 4 to account for BLK header craziness.
	// Subtract 10 to account for the size of the header. Subtract frames * 8
	// to account for the offset data.
	for (int i = 0; i < frames; i++) {
		dev->read((char*)&offsets[i], 4);
		offsets[i] = swapEndianLong(offsets[i]) - 6 - (frames * 8);
		dev->read((char*)&iwidth, 2); iwidth = swapEndianShort(iwidth);
		if (iwidth != 128) return false;
		dev->read((char*)&iheight, 2); iheight = swapEndianShort(iheight);
		if (iheight != 128) return false;
	}

	QImage image(width*128, height*128, QImage::Format_RGB32);

	qint32 *pixels = (qint32*) image.bits();

	// And here's the reason we had to account for size of header and offset
	// data in reading the offsets.
	QByteArray buffer = dev->readAll();
	qint16 *data = new qint16[buffer.size()];
	memcpy(data, buffer.data(), buffer.size());

	// go through each of the blocks
	for (int by = 0; by < height; by++) {
		for (int bx = 0; bx < width; bx++) {
			// make a nice little pointer to the data at the offset of this
			// block.
			qint16 *block = &data[offsets[bx * height + by] / 2];
			
			// keep it all big-endian-friendly
			for (int i = 0; i < 128*128; i++)
				block[i] = swapEndianShort(block[i]);

			// go through all the data in this block
			for (int y = 0; y < 128; y++) {
				for (int x = 0; x < 128; x++) {
					// get the 555/565 data
					qint32 pixel;
					if (is_565)
						pixel = DECODE_565(block[y * 128 + x]);
					else
						pixel = DECODE_555(block[y * 128 + x]);
					// set the appropriate pixel
					// that crazy formula down there finds the right place in
					// +pixels+.
					int pv = (by * 128 + y) * (width * 128) + (bx * 128 + x);
					pixels[pv] = pixel;
				}
			}
		}
	}

	delete [] data;
	delete [] offsets;

	*out = image;
	return true;
}





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

QStringList BLKImagePlugin::keys() const
{
	return QStringList() << "blk" << "BLK";
}

QImageIOPlugin::Capabilities BLKImagePlugin::capabilities(QIODevice *device,
		const QByteArray &format) const
{
	if (format == "blk" || format == "BLK")
		return Capabilities(CanRead);
	if (!format.isEmpty())
		return 0;
	if (!device->isOpen())
		return 0;

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

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

Q_EXPORT_PLUGIN(BLKImagePlugin)

