#include "pcx.h"

namespace Upp {

static Size GetDotSize(Size pixel_size, int xpm, int ypm)
{
	if(!xpm || !ypm)
		return Size(0, 0);
	static const double DOTS_PER_METER = 60000 / 2.54;
	return Size(fround(pixel_size.cx * DOTS_PER_METER / xpm), fround(pixel_size.cy * DOTS_PER_METER / ypm));
}

int sFd(int x)
{
	return x > 0 ? x : 120;
}

bool PCXRaster::Create()
{
	Stream& stream = GetStream();
	size = Size(0, 0);
	if(!stream.IsOpen()) {
		SetError();
		return false;
	}
	ASSERT(stream.IsLoading());

	if(!stream.GetAll(&header, sizeof(PCXHeader))) {
		SetError();
		return false;
	}
	header.SwapEndian();
	if(header.manufacturer != 10) {
		SetError();
		return false;
	}

	if(header.bitsPerPixel == 8 && header.planes == 3)
		fmt.Set24le(0x0000ff, 0x00ff00, 0xff0000);
	else
	if(header.bitsPerPixel == 1 || header.bitsPerPixel == 4 || header.bitsPerPixel == 8)
		fmt.Set8();
	else {
		SetError();
		return false;
	}
	size = Size(header.maxX - header.minX + 1, header.maxY - header.minY + 1);
	info.kind = IMAGE_OPAQUE;
	info.bpp = header.bitsPerPixel;
	info.dots = Size(size.cx * 600 / sFd(header.horzDpi), size.cy * 600 / sFd(header.vertDpi));
	info.hotspot = Point(0, 0);
	info.colors = fmt.GetColorCount();
	int colors = 1 << (header.planes * header.bitsPerPixel);
	palette.Alloc(colors);
	if(colors <= 16)
		for(int i = 0; i < colors; i++) {
			palette[i].r = header.palette[i].r;
			palette[i].g = header.palette[i].g;
			palette[i].b = header.palette[i].b;
			palette[i].a = 255;
		}
	else {
		if(stream.GetLeft() < 768) {
			SetError();
			return false;
		}
		stream.SeekEnd(-768);
		for(int i = 0; i < 256; i++) {
			palette[i].r = stream.Get();
			palette[i].g = stream.Get();
			palette[i].b = stream.Get();
			palette[i].a = 255;
		}
		stream.Seek(sizeof(PCXHeader));
	}

	row_bytes = fmt.GetByteCount(size.cx);
	scanline.Alloc(row_bytes);
	buffer.Alloc(header.bytesPerLine * header.planes);
	return true;
}

Size PCXRaster::GetSize()
{
	return size;
}

Raster::Info PCXRaster::GetInfo()
{
	return info;
}

Raster::Line PCXRaster::GetLine(int line)
{
	Stream& stream = GetStream();
	byte *ptr = new byte[row_bytes];
	if(!IsError()) {
		byte *t = buffer;
		byte *e = buffer + header.bytesPerLine * header.planes;
		while(t < e) {
			byte b = stream.Get();
			if((b & 0xc0) == 0xc0) {
				int n = b & 0x3f;
				if(t + n > e)
					break;
				memset(t, stream.Get(), n);
				t += n;
			}
			else
				*t++ = b;
		}
		byte *bs = buffer;
		t = ptr;
		e = ptr + row_bytes;
		if(header.bitsPerPixel == 1)
			for(int i = 0; i < header.bytesPerLine; i++) {
				byte h[8];
				memset(h, 0, 8);
				byte *s = bs;
				for(int plane = 0; plane < header.planes; plane++) {
					byte sb = *s;
					byte bit = 1 << plane;
					for(int j = 0; j < 8; j++)
						if(sb & (0x80 >> j))
							h[j] |= bit;
					s += header.bytesPerLine;
				}
				for(int q = 0; q < 8; q++) {
					if(t >= e)
						break;
					*t++ = h[q];
				}
				bs++;
			}
		else
		if(header.bitsPerPixel == 8 && header.planes == 3) {
			for(int i = 0; i < size.cx; i++) {
				*t++ = buffer[i];
				*t++ = buffer[i + header.bytesPerLine];
				*t++ = buffer[i + 2 * header.bytesPerLine];
			}
		}
		else
		if(header.bitsPerPixel == 4) {
			for(;;) {
				if(t >= e) break;
				byte b = *bs++;
				*t++ = (b & 0xf0) >> 4;
				if(t >= e) break;
				*t++ = b & 0x0f;
			}
		}
		else
		if(header.bitsPerPixel == 8)
			memcpy(t, bs, row_bytes);
		else
			goto error;
		return Line(ptr, this, true);
	}
error:
	SetError();
	memset(ptr, 0, row_bytes);
	return Line(ptr, this, true);
}

int PCXRaster::GetPaletteCount()
{
	return fmt.GetPaletteCount();
}

RGBA *PCXRaster::GetPalette()
{
	return fmt.GetPaletteCount() ? ~palette : NULL;
}

const RasterFormat *PCXRaster::GetFormat()
{
	return &fmt;
}

}
