/**
	\file
	\author Matthew Allen
	\date 25/3/97
	\brief Graphics file filters
*/

#if WINDOWS
	#include <winsock2.h>
	#include <Windows.h>
	#include "lgi/common/Lgi.h"
	#ifdef _MSC_VER
		#include <IImgCtx.h>
	#endif
#else
	#define BI_RGB			0L
	#define BI_RLE8			1L
	#define BI_RLE4			2L
	#define BI_BITFIELDS	3L
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "lgi/common/Lgi.h"
#include "lgi/common/LgiString.h"
#include "lgi/common/Variant.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/Palette.h"

int FindHeader(int Offset, const char *Str, LStream *f)
{
	int i = 0;
	
	if (Offset >= 0)
	{
		f->SetPos(Offset);
	}
	
	while (true)
	{
		char c;
		
		if (!f->Read(&c, 1))
			break;

		if (Str[i] == c || Str[i] == '?')
		{
			i++;
			if (!Str[i])
			{
				return true;
			}
		}
		else
		{
			i = 0;
		}
	}

	return false;
}

/// Windows and OS/2 BMP file filter
class GdcBmp : public LFilter
{
	int ActualBits;
	int ScanSize;

public:
	int GetCapabilites() override { return FILTER_CAP_READ | FILTER_CAP_WRITE; }
	Format GetFormat() override { return FmtBmp; }

	/// Reads a BMP file
	IoStatus ReadImage(LSurface *Out, LStream *In) override;
	
	/// Writes a Windows BMP file
	IoStatus WriteImage(LStream *Out, LSurface *In) override;

	bool GetVariant(const char *n, LVariant &v, const char *a) override
	{
		if (!stricmp(n, LGI_FILTER_TYPE))
		{
			v = "Windows or OS/2 Bitmap";
		}
		else if (!stricmp(n, LGI_FILTER_EXTENSIONS))
		{
			v = "BMP";
		}
		else return false;

		return true;
	}
};

class GdcBmpFactory : public GFilterFactory
{
	bool CheckFile(const char *File, int Access, const uchar *Hint)
	{
		if (File)
		{
			const char *Ext = LGetExtension((char*)File);
			if (Ext && !_stricmp(Ext, "bmp"))
				return true;
		}
		
		if (Hint)
		{
			if (Hint[0] == 'B' &&
				Hint[1] == 'M')
				return true;
		}
		
		return false;
	}

	LFilter *NewObject()
	{
		return new GdcBmp;
	}

} BmpFactory;

#define BMP_ID			0x4D42

#ifdef WIN32
#pragma pack(push, before_pack)
#pragma pack(1)
#endif

class BMP_FILE {
public:
	char		Type[2];
	uint32_t		Size;
	uint16		Reserved1;
	uint16		Reserved2;
	uint32_t		OffsetToData;
};

class BMP_WININFO {
public:
	// Win2 hdr (12 bytes)
	int32		Size;
	int32		Sx;
	int32		Sy;
	uint16		Planes;
	uint16		Bits;
	
	// Win3 hdr (40 bytes)
	uint32_t		Compression;
	uint32_t		DataSize;
	int32		XPels;
	int32		YPels;
	uint32_t		ColoursUsed;
	uint32_t		ColourImportant;

	// Win4 hdr (108 bytes)
	uint32_t RedMask;       /* Mask identifying bits of red component */
	uint32_t GreenMask;     /* Mask identifying bits of green component */
	uint32_t BlueMask;      /* Mask identifying bits of blue component */
	uint32_t AlphaMask;     /* Mask identifying bits of alpha component */
	uint32_t CSType;        /* Color space type */
	uint32_t RedX;          /* X coordinate of red endpoint */
	uint32_t RedY;          /* Y coordinate of red endpoint */
	uint32_t RedZ;          /* Z coordinate of red endpoint */
	uint32_t GreenX;        /* X coordinate of green endpoint */
	uint32_t GreenY;        /* Y coordinate of green endpoint */
	uint32_t GreenZ;        /* Z coordinate of green endpoint */
	uint32_t BlueX;         /* X coordinate of blue endpoint */
	uint32_t BlueY;         /* Y coordinate of blue endpoint */
	uint32_t BlueZ;         /* Z coordinate of blue endpoint */
	uint32_t GammaRed;      /* Gamma red coordinate scale value */
	uint32_t GammaGreen;    /* Gamma green coordinate scale value */
	uint32_t GammaBlue;     /* Gamma blue coordinate scale value */

	bool Read(LStream &f)
	{
		ZeroObj(*this);
		int64 Start = f.GetPos();
		
		#define Rd(var) if (f.Read(&var, sizeof(var)) != sizeof(var)) \
			{ LgiTrace("Bmp.Read(%i) failed\n", (int)sizeof(var)); return false; }
		Rd(Size); // 4
		Rd(Sx); // 4
		Rd(Sy); // 4
		Rd(Planes); // 2
		Rd(Bits); // 2
		// = 16
		
		if (Size >= 40)
		{
			Rd(Compression); // 4
			Rd(DataSize); // 4
			Rd(XPels); // 4
			Rd(YPels); // 4
			Rd(ColoursUsed); // 4
			Rd(ColourImportant); // 4
			// = 24 + 16 = 40
		}
		
		if (Size >= 52)
		{
			Rd(RedMask); // 4
			Rd(GreenMask); // 4
			Rd(BlueMask); // 4
			// 12 + 40 = 52
		}

		if (Size >= 56)
		{
			Rd(AlphaMask); // 4
			// = 4 + 52 = 56
		}
		
		if (Size >= 108)
		{
			Rd(CSType); // 4
			Rd(RedX); // 4
			Rd(RedY); // 4
			Rd(RedZ); // 4
			Rd(GreenX); // 4
			Rd(GreenY); // 4
			Rd(GreenZ); // 4
			Rd(BlueX); // 4
			Rd(BlueY); // 4
			Rd(BlueZ); // 4
			Rd(GammaRed); // 4
			Rd(GammaGreen); // 4
			Rd(GammaBlue); // 4
			// = 52 + 56 = 108
		}

		int64 End = f.GetPos();
		int64 Bytes = End - Start;
		return Bytes >= 12;
	}
};

#ifdef WIN32
#pragma pack(pop, before_pack)
#endif

static int CountSetBits(uint32_t b)
{
	int Count = 0;
	for (int i=0; i<sizeof(b)<<3; i++)
	{
		if ((b & (1 << i)) != 0)
			Count++;
	}
	return Count;
}

struct MaskComp
{
	LComponentType Type;
	uint32_t Mask;
	int Bits;
	
	void Set(LComponentType t, int mask)
	{
		Type = t;
		Mask = mask;
		Bits = CountSetBits(Mask);
	}
};

int UpSort(MaskComp *a, MaskComp *b)
{
	return b->Mask < a->Mask ? 1 : -1;
}

int DownSort(MaskComp *a, MaskComp *b)
{
	return b->Mask > a->Mask ? 1 : -1;
}

LFilter::IoStatus GdcBmp::ReadImage(LSurface *pDC, LStream *In)
{
	if (!pDC || !In)
		return LFilter::IoError;

	ActualBits = 0;
	ScanSize = 0;
	
	BMP_FILE File;
	BMP_WININFO Info;
	LRect Cr;
	
	Read(In, &File.Type, sizeof(File.Type));
	Read(In, &File.Size, sizeof(File.Size));
	Read(In, &File.Reserved1, sizeof(File.Reserved1));
	Read(In, &File.Reserved2, sizeof(File.Reserved2));
	Read(In, &File.OffsetToData, sizeof(File.OffsetToData));

	if (!Info.Read(*In))
	{
		LgiTrace("%s:%i - BmpHdr read failed.\n", _FL);
		return LFilter::IoError;
	}

	if (File.Type[0] != 'B' || File.Type[1] != 'M')
	{
		LgiTrace("%s:%i - Bmp file id failed: '%.2s'.\n", _FL, File.Type);
		return LFilter::IoUnsupportedFormat;
	}

	ActualBits = Info.Bits;
	ScanSize = BMPWIDTH(Info.Sx * Info.Bits);
	int MemBits = MAX(Info.Bits, 8);
	if (!pDC->Create(Info.Sx, Info.Sy, GBitsToColourSpace(MemBits), ScanSize))
	{
		LgiTrace("%s:%i - MemDC(%i,%i,%i) failed.\n", _FL, Info.Sx, Info.Sy, MAX(Info.Bits, 8));
		return LFilter::IoError;
	}

	if (pDC->GetBits() <= 8)
	{
		int Colours = 1 << ActualBits;
		GPalette *Palette = new GPalette;
		if (Palette)
		{
			Palette->SetSize(Colours);
			GdcRGB *Start = (*Palette)[0];
			if (Start)
			{
				In->Read(Start, sizeof(GdcRGB)*Colours);
				Palette->SwapRAndB();
			}

			Palette->Update();
			pDC->Palette(Palette);
		}
	}

	LBmpMem *pMem = GetSurface(pDC);
	In->SetPos(File.OffsetToData);

	LFilter::IoStatus Status = IoSuccess;
	if (Info.Compression == BI_RLE8)
	{
		// 8 bit RLE compressed image
		int64 Remaining = In->GetSize() - In->GetPos();
		uchar *Data = new uchar[Remaining];
		if (Data)
		{
			if (In->Read(Data, (int)Remaining) == Remaining)
			{
				int x=0, y=pDC->Y()-1;
				uchar *p = Data;
				bool Done = false;

				while (!Done && p < Data + Remaining)
				{
					uchar Length = *p++;
					uchar Colour = *p++;

					if (Length == 0)
					{
						switch (Colour)
						{
							case 0:
							{
								x = 0;
								y--;
								break;
							}
							case 1:
							{
								Done = true;
								break;
							}
							case 2:
							{
								x += *p++;
								y -= *p++;
								break;
							}
							default:
							{
								// absolute mode
								uchar *Pixel = (*pDC)[y];
								if (Pixel && y >= 0 && y < pDC->Y())
								{
									int Len = MIN(Colour, pDC->X() - x);
									if (Len > 0)
									{
										memcpy(Pixel + x, p, Len);
										x += Colour;
										p += Colour;
									}
								}
								else
								{
									p += Colour;
								}
								if ((NativeInt) p & 1) p++;
								break;
							}
						}
					}
					else
					{
						// encoded mode
						uchar *Pixel = (*pDC)[y];
						if (Pixel && y >= 0 && y < pDC->Y())
						{
							int Len = MIN(Length, pDC->X() - x);
							if (Len > 0)
							{
								memset(Pixel + x, Colour, Len);
								x += Length;
							}
						}
					}
				}
			}
		}
	}
	else if (Info.Compression == BI_RLE4)
	{
		// 4 bit RLE compressed image

		// not implemented
		LgiTrace("%s:%i - BI_RLE4 not implemented.\n", _FL);
	}
	else
	{
		// Progress
		if (Meter)
		{
			Meter->SetDescription("scanlines");
			Meter->SetRange(LRange(0, pMem->y));
		}
		
		LColourSpace SrcCs = CsNone;

		switch (ActualBits)
		{
			case 8:
				SrcCs = CsIndex8;
				break;
			case 15:
				SrcCs = CsRgb15;
				break;
			case 16:
				SrcCs = CsRgb16;
				break;
			case 24:
				SrcCs = CsBgr24;
				break;
			case 32:
				SrcCs = CsBgra32;
				break;
		}

		#if 1
		if (Info.Compression == BI_BITFIELDS)
		{
			// Should we try and create a colour space from these fields?
			LArray<MaskComp> Comps;
			Comps.New().Set(CtRed, Info.RedMask);
			Comps.New().Set(CtGreen, Info.GreenMask);
			Comps.New().Set(CtBlue, Info.BlueMask);
			
			if (Info.AlphaMask)
				Comps.New().Set(CtAlpha, Info.AlphaMask);
			Comps.Sort(ActualBits == 16 ? DownSort : UpSort);

			LColourSpaceBits Cs;
			Cs.All = 0;
			for (int i=0; i<Comps.Length(); i++)
			{
				MaskComp &c = Comps[i];
				Cs.All <<= 8;
				Cs.All |= (c.Type << 4) | c.Bits;
			}
			
			SrcCs = (LColourSpace) Cs.All;
		}
		#endif

		// straight
		switch (ActualBits)
		{
			case 1:
			{
				uchar *Buffer = new uchar[ScanSize];
				if (Buffer)
				{
					for (int y=pMem->y-1; y>=0; y--)
					{
						if (In->Read(Buffer, ScanSize) == ScanSize)
						{
							uchar Mask = 0x80;
							uchar *d = Buffer;
							for (int x=0; x<pDC->X(); x++)
							{
								pDC->Colour((*d & Mask) ? 1 : 0);
								pDC->Set(x, y);

								Mask >>= 1;
								if (!Mask)
								{
									Mask = 0x80;
									d++;
								}
							}
						}
						else
						{
						    Status = IoError;
							break;
						}

						if (Meter) Meter->Value(pMem->y-1-y);
					}

					DeleteArray(Buffer);
				}
				break;
			}
			case 4:
			{
				uchar *Buffer = new uchar[ScanSize];
				if (Buffer)
				{
					for (int y=pMem->y-1; y>=0; y--)
					{
						if (In->Read(Buffer, ScanSize) != ScanSize)
						{
						    Status = IoError;
						    break;
						}

						uchar *d = Buffer;
						for (int x=0; x<pDC->X(); x++)
						{
							if (x & 1)
							{
								pDC->Colour(*d & 0xf);
								d++;
							}
							else
							{
								pDC->Colour(*d >> 4);
							}
							pDC->Set(x, y);
						}

						if (Meter) Meter->Value(pMem->y-1-y);
					}

					DeleteArray(Buffer);
				}
				break;
			}
			default:
			{
				LColourSpace DstCs = pDC->GetColourSpace();
				for (int i=pMem->y-1; i>=0; i--)
				{
					uint8_t *Ptr = pMem->Base + (pMem->Line * i);
					ssize_t r = In->Read(Ptr, ScanSize);
					if (r != ScanSize)
					{
						Status = IoError;
						LgiTrace("%s:%i - Bmp read err, wanted %i, got %i.\n", _FL, ScanSize, r);
						break;
					}
					
					if (DstCs != SrcCs)
					{
						if (!LRopRgb(Ptr, DstCs, Ptr, SrcCs, pMem->x, false))
						{
							Status = IoUnsupportedFormat;
							LgiTrace("%s:%i - Bmp had unsupported bit depth.\n", _FL);
							break;
						}
						
					}

					if (Meter) Meter->Value(pMem->y-1-i);
				}
				break;
			}
		}
	}

	Cr.ZOff(pDC->X()-1, pDC->Y()-1);
	pDC->ClipRgn(&Cr);
	pDC->Update(GDC_BITS_CHANGE|GDC_PAL_CHANGE);
	
	return Status;
}

template<typename T>
ssize_t SwapWrite(LStream *Out, T v)
{
	#if 0 // __ORDER_BIG_ENDIAN__
	uint8_t *s = (uint8_t*)&v;
	uint8_t *e = s + sizeof(v) - 1;
	while (s < e)
	{
		uint8_t tmp = *s;
		*s++ = *e;
		*e-- = tmp;
	}
	#endif
	return Out->Write(&v, sizeof(v));
}

LFilter::IoStatus GdcBmp::WriteImage(LStream *Out, LSurface *pDC)
{
    LFilter::IoStatus Status = IoError;
	
	if (!pDC || !Out)
		return LFilter::IoError;

	BMP_FILE File;
	BMP_WININFO Info;
	LBmpMem *pMem = GetSurface(pDC);
	int Colours = (pMem->Cs == CsIndex8) ? 1 << 8 : 0;
	int UsedBits = GColourSpaceToBits(pMem->Cs);

	GPalette *Palette = pDC->Palette();
	if (pMem->Cs == CsIndex8 && Palette)
	{
		int Size = Palette->GetSize();
		int Bits = 8;
		for (int c=256; c; c>>=1, Bits--)
		{
			if (c & Size)
			{
				Colours = c;
				UsedBits = Bits;
				break;
			}
		}
	}

	if (!pMem ||
		!pMem->x ||
		!pMem->y)
	{
		return LFilter::IoError;
	}

	Out->SetSize(0);

	#define Wr(var) SwapWrite(Out, var)

	Info.Compression = UsedBits == 16 || UsedBits == 32 ? BI_BITFIELDS : BI_RGB;
	int BitFieldSize = Info.Compression == BI_BITFIELDS ? 16 : 0;
	Info.Size = 40 + BitFieldSize;

	File.Type[0] = 'B';
	File.Type[1] = 'M';
	File.OffsetToData = 14 + Info.Size;
	File.Size = File.OffsetToData + (int)(ABS(pMem->Line) * pMem->y);
	File.Reserved1 = 0;
	File.Reserved2 = 0;

	Info.Sx = pMem->x;
	Info.Sy = pMem->y;
	Info.Planes = 1;
	Info.Bits = UsedBits;
	Info.DataSize = 0;
	Info.XPels = 3000;
	Info.YPels = 3000;
	Info.ColoursUsed = Colours;
	Info.ColourImportant = Colours;

	bool Written =	Out->Write(File.Type, 2) &&
					Wr(File.Size) &&
					Wr(File.Reserved1) &&
					Wr(File.Reserved2) &&
					Wr(File.OffsetToData);

	Written =		Wr(Info.Size) &&
					Wr(Info.Sx) &&
					Wr(Info.Sy) &&
					Wr(Info.Planes) &&
					Wr(Info.Bits) &&
					Wr(Info.Compression) &&
					Wr(Info.DataSize) &&
					Wr(Info.XPels) &&
					Wr(Info.YPels) &&
					Wr(Info.ColoursUsed) &&
					Wr(Info.ColourImportant);

	if (Written)
	{
		if (pMem->Cs == CsIndex8)
		{
			int Done = 0;

			if (Palette)
			{
				GdcRGB *Start = (*Palette)[0];
				if (Start)
				{
					Palette->SwapRAndB();
					Out->Write(Start, sizeof(GdcRGB)*Palette->GetSize());
					Palette->SwapRAndB();
					Done += Palette->GetSize();
				}
			}

			char Grey[4];
			Grey[3] = 0;

			while (Done < Colours)
			{
				int C = Done * 256 / Colours;

				Grey[0] = C;
				Grey[1] = C;
				Grey[2] = C;

				Out->Write(Grey, 4);
				Done++;
			}
		}

		// Progress
		if (Meter)
		{
			Meter->SetDescription("scanlines");
			Meter->SetRange(LRange(0, pMem->y));
		}

		int Bytes = BMPWIDTH(pMem->x * UsedBits);
		Status = LFilter::IoSuccess;
		switch (UsedBits)
		{
			case 1:
			{
				uchar *Buffer = new uchar[Bytes];
				if (Buffer)
				{
					for (int i=pMem->y-1; i>=0; i--)
					{
						uchar *Src = pMem->Base + (i * pMem->Line);
						
						// assemble the bits
						memset(Buffer, 0, Bytes);
						for (int x=0; x<pMem->x; x++)
						{
							Buffer[x>>3] |= (Src[x] & 1) << (x & 3);
						}

						// write the bits
						if (Out->Write(Buffer, Bytes) != Bytes)
						{
						    Status = IoError;
						    break;
						}
						
						// update status
						if (Meter) Meter->Value(pMem->y-1-i);
					}

					DeleteArray(Buffer);
				}
				break;
			}
			case 4:
			{
				uchar *Buffer = new uchar[Bytes];
				if (Buffer)
				{
					for (int i=pMem->y-1; i>=0; i--)
					{
						uchar *Src = pMem->Base + (i * pMem->Line);

						// assemble the nibbles
						for (int x=0; x<pMem->x; x+=2)
						{
							Buffer[x>>1] = (Src[x]<<4) | (Src[x+1]&0x0f);
						}

						// write the line
						if (Out->Write(Buffer, Bytes) != Bytes)
						{
						    Status = IoError;
						    break;
						}
						
						// update status
						if (Meter) Meter->Value(pMem->y-1-i);
					}

					DeleteArray(Buffer);
				}
				break;
			}
			default:
			{
				if (UsedBits == 16)
				{
					System16BitPixel px[8];
					ZeroObj(px);
					px[0].r = 0x1f;
					px[2].g = 0x3f;
					px[4].b = 0x1f;
					Out->Write(px, sizeof(px));
				}
				else if (UsedBits == 32)
				{
					System32BitPixel px[4];
					ZeroObj(px);
					px[0].r = 0xff;
					px[1].g = 0xff;
					px[2].b = 0xff;
					px[3].a = 0xff;
					Out->Write(px, sizeof(px));
				}

				for (int i=pMem->y-1; i>=0; i--)
				{
					ssize_t w = Out->Write(pMem->Base + (i * pMem->Line), Bytes);
					if (w != Bytes)
					{
						Status = IoError;
						break;
					}

					if (Meter) Meter->Value(pMem->y-1-i);
				}
				break;
			}
		}
	}

	Out->Close();
	
	return Status;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// ICO file format
class GdcIco : public LFilter
{
	int TruncSize(int i)
	{
		if (i >= 64) return 64;
		if (i >= 32) return 32;
		return 16;
	}

public:
	GdcIco();

	Format GetFormat() override { return FmtIco; }
	int GetCapabilites() override { return FILTER_CAP_READ | FILTER_CAP_WRITE; }
	int GetImages() override { return 1; }
	IoStatus ReadImage(LSurface *pDC, LStream *In) override;
	IoStatus WriteImage(LStream *Out, LSurface *pDC) override;
	bool GetVariant(const char *n, LVariant &v, const char *a) override;
};

class GdcIcoFactory : public GFilterFactory
{
	bool CheckFile(const char *File, int Access, const uchar *Hint)
	{
		return (File) ? stristr(File, ".ico") != 0 : false;
	}

	LFilter *NewObject()
	{
		return new GdcIco;
	}

} IcoFactory;

GdcIco::GdcIco()
{
}

bool GdcIco::GetVariant(const char *n, LVariant &v, const char *a)
{
	if (!stricmp(n, LGI_FILTER_TYPE))
	{
		v = "Icon file";
	}
	else if (!stricmp(n, LGI_FILTER_EXTENSIONS))
	{
		v = "ICO";
	}
	else return false;

	return true;
}

LFilter::IoStatus GdcIco::ReadImage(LSurface *pDC, LStream *In)
{
	LFilter::IoStatus Status = IoError;
	int MyBits = 0;

	int16 Reserved_1;
	int16 Type;
	int16 Count;

	Read(In, &Reserved_1, sizeof(Reserved_1));
	Read(In, &Type, sizeof(Type));
	Read(In, &Count, sizeof(Count));

	for (int Image = 0; Image < Count; Image++)
	{
		int8	Width;
		int8	Height;
		int8	ColorCount;
		int8	Reserved_2;
		int16	Planes;
		int16	BitCount;
		int32	BytesInRes;
		int32	ImageOffset;
		
		Read(In, &Width, sizeof(Width));
		Read(In, &Height, sizeof(Height));
		Read(In, &ColorCount, sizeof(ColorCount));
		Read(In, &Reserved_2, sizeof(Reserved_2));
		Read(In, &Planes, sizeof(Planes));
		Read(In, &BitCount, sizeof(BitCount));
		Read(In, &BytesInRes, sizeof(BytesInRes));
		Read(In, &ImageOffset, sizeof(ImageOffset));
		
		int64 BytesLeft = BytesInRes;

		int64 CurrentPos = In->GetPos();
		In->SetPos(ImageOffset);

		BMP_WININFO	Header;
		GdcRGB *Colours;
		uchar *XorBytes = 0;
		uchar *AndBytes = 0;

		int64 StartHdrPos = In->GetPos();
		if (!Header.Read(*In))
			return LFilter::IoError;
		BytesLeft -= In->GetPos() - StartHdrPos;

		if (!Header.Sx) Header.Sx = Width;
		if (!Header.Sy) Header.Sy = Height;
		if (!Header.Bits)
		{
			if (BitCount) Header.Bits = BitCount;
			else if (ColorCount)
			{
				for (int i=1; i<=8; i++)
				{
					if (1 << i >= ColorCount)
					{
						BitCount = Header.Bits = i;
						break;
					}
				}
			}
		}

		Colours = new GdcRGB[ColorCount];
		if (Colours)
		{
			In->Read(Colours, sizeof(GdcRGB) * ColorCount);
			BytesLeft -= sizeof(GdcRGB) * ColorCount;
		}

		Header.Sy >>= 1;
		
		int XorSize = BMPWIDTH(Header.Sx * Header.Bits) * Height;
		int AndSize = BMPWIDTH(Header.Sx) * Height;
		
		if (BytesLeft >= XorSize)
		{
			XorBytes = new uchar[XorSize];
			if (XorBytes)
			{
				In->Read(XorBytes, XorSize);
				BytesLeft -= XorSize;
			}
		}
		
		if (BytesLeft >= AndSize)
		{
			AndBytes = new uchar[AndSize];
			if (AndBytes)
			{
				In->Read(AndBytes, AndSize);
				BytesLeft -= AndSize;
			}
		}

		/*
		LgiTrace("BytesInRes=%i, Xor=%i, And=%i, Pal=%i, BytesLeft=%i\n",
			BytesInRes,
			XorSize,
			AndSize,
			sizeof(GdcRGB) * Cols,
			BytesLeft);
		*/

		if (Colours &&
			XorBytes &&
			(Header.Bits > MyBits || Width > pDC->X() || Height > pDC->Y()) &&
			pDC->Create(Width, Height, GBitsToColourSpace(MAX(8, Header.Bits)) ))
		{
			MyBits = Header.Bits;
			pDC->Colour(0, 24);
			pDC->Rectangle();

			GPalette *Pal = new GPalette;
			if (Pal)
			{
				Pal->SetSize(ColorCount);
				memcpy((*Pal)[0], Colours, sizeof(GdcRGB) * ColorCount);
				Pal->SwapRAndB();
				pDC->Palette(Pal, true);
			}
			
			if (AndBytes)
			{
				pDC->HasAlpha(true);
			}			

			LSurface *pAlpha = pDC->AlphaDC();
			int XorLine = XorSize / Height;
			int AndLine = AndSize / Height;
			for (int y=0; y<Height; y++)
			{
				uchar *Dest = (*pDC)[y];
				uchar *Alpha = pAlpha ? (*pAlpha)[y] : 0;
				uchar *Src = XorBytes + ((Height-y-1) * XorLine);
				uchar *Mask = AndBytes ? AndBytes + ((Height-y-1) * AndLine) : 0;

				switch (Header.Bits)
				{
					case 1:
					{
						uchar m = 0x80;
						for (int x=0; x<Width; x++)
						{
							if (Alpha)
							{
								Alpha[x] = (Mask[x>>3] & (0x80 >> (x&7))) ? 0 : 0xff;
							}

							if (Src[x/8] & m)
							{
								Dest[x] = 1;
							}
							else
							{
								Dest[x] = 0;
							}
							m >>= 1;
							if (!m) m = 0x80;
						}
						Status = IoSuccess;
						break;
					}
					case 4:
					{
						for (int x=0; x<Width; x++)
						{
							if (Alpha)
							{
								Alpha[x] = (Mask[x>>3] & (0x80 >> (x&7))) ? 0 : 0xff;
							}

							if (x & 1)
							{
								Dest[x] = Src[x>>1] & 0xf;
							}
							else
							{
								Dest[x] = Src[x>>1] >> 4;
							}
						}
						Status = IoSuccess;
						break;
					}
					case 8:
					{
						for (int x=0; x<Width; x++)
						{
							if (Alpha)
							{
								Alpha[x] = (Mask[x>>3] & (0x80 >> (x&7))) ? 0 : 0xff;
							}

							Dest[x] = Src[x];
						}

						Status = IoSuccess;
						break;
					}
				}
			}
		}
		else
		{
			LgiTrace("%s:%i - Header size error: %i != %i + %i, Img: %ix%i @ %i bits\n",
				_FL,
				Header.DataSize, XorSize, AndSize,
				Header.Sx, Header.Sy, Header.Bits);
		}

		DeleteArray(Colours);
		DeleteArray(XorBytes);
		DeleteArray(AndBytes);
		In->SetPos(CurrentPos);
	}

	return Status;
}

LFilter::IoStatus GdcIco::WriteImage(LStream *Out, LSurface *pDC)
{
	LFilter::IoStatus Status = IoError;

	if (!pDC || pDC->GetBits() > 8 || !Out)
		return LFilter::IoError;

	Out->SetSize(0);

	int ActualBits = pDC->GetBits();
	GPalette *Pal = pDC->Palette();
	if (Pal)
	{
		if (Pal->GetSize() <= 2)
		{
			ActualBits = 1;
		}
		else if (Pal->GetSize() <= 16)
		{
			ActualBits = 4;
		}
	}

	// write file header
	int Colours = 1 << ActualBits;
	int16 Reserved_1 = 0;
	int16 Type = 1;
	int16 Count = 1;

	Write(Out, &Reserved_1, sizeof(Reserved_1));
	Write(Out, &Type, sizeof(Type));
	Write(Out, &Count, sizeof(Count));

	// write directory list
	int8	Width = TruncSize(pDC->X());
	int8	Height = TruncSize(pDC->Y());
	int8	ColorCount = Colours;
	int8	Reserved_2 = 0;
	int16	Planes = 0;
	int16	BitCount = 0;

	int Line = (ActualBits * Width) / 8;
	int MaskLine = BMPWIDTH(Width/8);
	int32	BytesInRes = sizeof(BMP_WININFO) +
						(sizeof(GdcRGB) * Colours) +
						(Line * Height) +
						(MaskLine * Height);
	
	Write(Out, &Width, sizeof(Width));
	Write(Out, &Height, sizeof(Height));
	Write(Out, &ColorCount, sizeof(ColorCount));
	Write(Out, &Reserved_2, sizeof(Reserved_2));
	Write(Out, &Planes, sizeof(Planes));
	Write(Out, &BitCount, sizeof(BitCount));
	Write(Out, &BytesInRes, sizeof(BytesInRes));

	int32	ImageOffset = (int32)(Out->GetPos() + sizeof(ImageOffset));
	Write(Out, &ImageOffset, sizeof(ImageOffset));

	// Write icon itself
	BMP_WININFO	Header;
	LAssert(sizeof(Header) == 40);
	Header.Size = sizeof(Header);
	Header.Sx = Width;
	Header.Sy = Height * 2;
	Header.Planes = 1;
	Header.Bits = ActualBits;
	Header.Compression = 0; // BI_RGB;
	Header.DataSize = (Line * Height) + (MaskLine * Height);
	Header.XPels = 0;
	Header.YPels = 0;
	Header.ColoursUsed = 0;
	Header.ColourImportant = 0;
	Out->Write(&Header, sizeof(Header));

	// Write palette
	if (Pal)
	{
		Pal->SwapRAndB();
		Out->Write((*Pal)[0], sizeof(GdcRGB) * (int)(1 << ActualBits));
		Pal->SwapRAndB();
	}

	// Get background colour
	COLOUR Back = 0xffffffff;
	if (Props)
	{
		LVariant v;
		if (Props->GetValue(LGI_FILTER_BACKGROUND, v))
			Back = v.CastInt32();
	}

	// Write "Colour" bits
	int y;
	for (y=Height-1; y>=0; y--)
	{
		uchar *Src = (*pDC)[y];

		// xor bits (colour)
		switch (ActualBits)
		{
			case 4:
			{
				uchar Dest = 0;
				for (int x=0; x<Width; x++)
				{
					if (x&1)
					{
						Dest |=	(Back == Src[x]) ? 0 : Src[x] & 0xf;
						Write(Out, &Dest, sizeof(Dest));
						Dest = 0;
					}
					else
					{
						Dest |=	(Back == Src[x]) ? 0 : (Src[x] & 0xf) << 4;
					}
				}

				Status = IoSuccess;
				break;
			}
			case 8:
			{
				for (int x=0; x<Width; x++)
				{
					uchar Dest = (Back == Src[x]) ? 0 : Src[x];
					Write(Out, &Dest, sizeof(Dest));
				}
				Status = IoSuccess;
				break;
			}
		}
	}

	// Write "And" bits (mask)
	for (y=Height-1; y>=0; y--)
	{
		uchar *Src = (*pDC)[y];
		uchar Dest = 0;
		uchar Mask = 0x80;
		
		int x;
		for (x = 0; x < Width; x++)
		{
			if (Src[x] == Back) Dest |= Mask;
			Mask >>= 1;
			if (!Mask)
			{
				Write(Out, &Dest, sizeof(Dest));
				Dest = 0;
				Mask = 0x80;
			}
		}

		x = Width / 8;
		while (x < MaskLine)
		{
			uchar c = 0;
			Write(Out, &c, sizeof(c));
			x++;
		}

		Status = IoSuccess;
	}

	return Status;
}

////////////////////////////////////////////////////////////////////
GFilterFactory *GFilterFactory::First = 0;
GFilterFactory::GFilterFactory()
{
	// add this filter to the global list
	Next = First;
	First = this;
}

GFilterFactory::~GFilterFactory()
{
	// delete from the global list
	if (First == this)
	{
		First = First->Next;
	}
	else
	{
		GFilterFactory *i = First;
		while (i->Next && i->Next != this)
		{
			i = i->Next;
		}
		if (i->Next == this)
		{
			i->Next = Next;
		}
		else
		{
			// we aren't in the list
			// thats bad
			LAssert(0);
		}
	}
}

LFilter *GFilterFactory::New(const char *File, int Access, const uchar *Hint)
{
	GFilterFactory *i = First;
	while (i)
	{
		if (i->CheckFile(File, Access, Hint))
		{
			return i->NewObject();
		}
		i = i->Next;
	}
	return 0;
}

LFilter *GFilterFactory::NewAt(int n)
{
	int Status = 0;
	GFilterFactory *i = First;
	while (i)
	{
		if (Status++ == n)
		{
			return i->NewObject();
		}

		i = i->Next;
	}

	return 0;
}

int GFilterFactory::GetItems()
{
	int Status = 0;
	GFilterFactory *i = First;
	while (i)
	{
		Status++;
		i = i->Next;
	}
	return Status;
}

//////////////////////////////////////////////////////////////////////////

/// Legacy wrapper that calls the new method
LSurface *LoadDC(const char *Name, bool UseOSLoader)
{
    return GdcD->Load(Name, UseOSLoader);
}

LSurface *GdcDevice::Load(const char *Name, bool UseOSLoader)
{
	if (!LFileExists(Name))
	{
		// Loading non-file resource...
		#if WINNATIVE
		LAutoWString WName(Utf8ToWide(Name));
		// a resource... lock and load gentlemen
		HRSRC hRsrc = FindResource(NULL, WName, RT_BITMAP);
		if (hRsrc)
		{
			int Size = SizeofResource(NULL, hRsrc);
			HGLOBAL hMem = LoadResource(NULL, hRsrc);
			if (hMem)
			{
				uchar *Ptr = (uchar*) LockResource(hMem);
				if (Ptr)
				{
					LClipBoard Clip(NULL);
					LSurface *pDC = Clip.ConvertFromPtr(Ptr);
					GlobalUnlock(hMem);
					return pDC;
				}
			}
		}
		#endif
		return NULL;
	}

	LFile File;
	if (!File.Open(Name, O_READ))
	{
		LgiTrace("%s:%i - Couldn't open '%s' for reading.\n", _FL, Name);
		return NULL;
	}

	return Load(&File, Name, UseOSLoader);
}

LSurface *GdcDevice::Load(LStream *In, const char *Name, bool UseOSLoader)
{
	if (!In)
		return NULL;

	LFilter::IoStatus FilterStatus = LFilter::IoError;

	int64 Size = In->GetSize();
	if (Size <= 0)
	{
		return NULL;
	}

	LAutoPtr<uchar, true> Hint(new uchar[16]);
	memset(Hint, 0, 16);
	if (In->Read(Hint, 16) == 0)
	{
		Hint.Reset(0);
	}

	int64 SeekResult = In->SetPos(0);
	if (SeekResult != 0)
	{
		LgiTrace("%s:%i - Seek failed after reading hint.\n", _FL);
		return NULL;
	}

	LXmlTag Props;
	LAutoPtr<LFilter> Filter(GFilterFactory::New(Name, FILTER_CAP_READ, Hint));
	LAutoPtr<LSurface> pDC;
	if (Filter &&
		pDC.Reset(new LMemDC))
	{
		Filter->Props = &Props;

		FilterStatus = Filter->ReadImage(pDC, In);
		if (FilterStatus != LFilter::IoSuccess)
		{
			pDC.Reset();
			
			LVariant m;
			Props.GetValue(LGI_FILTER_ERROR, m);			
			LgiTrace("%s:%i - Filter couldn't cope with '%s' (msg=%s).\n", _FL, Name, m.Str());
		}
	}

	if (UseOSLoader && !pDC)
	{
		#if LGI_SDL
		
		#elif defined(__GTK_H__)
		
		#elif defined MAC && !LGI_COCOA
		
		CGImageRef Img = NULL;
		if (LFileExists(Name))
		{
			CFURLRef FileUrl = CFURLCreateFromFileSystemRepresentation(0, (const UInt8*)Name, strlen(Name), false);
			if (!FileUrl)
				LgiTrace("%s:%i - CFURLCreateFromFileSystemRepresentation failed.\n", _FL);
			else
			{
				CGImageSourceRef Src = CGImageSourceCreateWithURL(FileUrl, 0);
				if (!Src)
					LgiTrace("%s:%i - CGImageSourceCreateWithURL failed.\n", _FL);
				else
				{
					Img = CGImageSourceCreateImageAtIndex(Src, 0, 0);
					if (!Img)
						LgiTrace("%s:%i - CGImageSourceCreateImageAtIndex failed.\n", _FL);
					
					CFRelease(Src);
				}

				CFRelease(FileUrl);
			}
		}
		else
		{
			GMemStream ms(In, 0, -1);
			CFDataRef data = CFDataCreate(NULL, (const UInt8 *)ms.GetBasePtr(), ms.GetSize());
			CGImageSourceRef Src = CGImageSourceCreateWithData(data, NULL);
			if (!Src)
				LgiTrace("%s:%i - CGImageSourceCreateWithURL failed.\n", _FL);
			else
			{
				Img = CGImageSourceCreateImageAtIndex(Src, 0, 0);
				if (!Img)
					LgiTrace("%s:%i - CGImageSourceCreateImageAtIndex failed.\n", _FL);
				
				CFRelease(Src);
			}
		}

		if (Img)
		{
			size_t x = CGImageGetWidth(Img);
			size_t y = CGImageGetHeight(Img);
			// size_t bits = CGImageGetBitsPerPixel(Img);
			
			if (pDC.Reset(new LMemDC) &&
				pDC->Create(x, y, System32BitColourSpace))
			{
				pDC->Colour(0);
				pDC->Rectangle();
				
				CGRect r = {{0, 0}, {(CGFloat)x, (CGFloat)y}};
				CGContextDrawImage(pDC->Handle(), r, Img);
			}
			else
			{
				LgiTrace("%s:%i - pMemDC->Create failed.\n", _FL);
				pDC.Reset();
			}
			
			CGImageRelease(Img);
		}

		#elif WINNATIVE && defined(_MSC_VER)
		
		/*
		char *Ext = LGetExtension((char*)Name);
		if (Ext && stricmp(Ext, "gif") && stricmp(Ext, "png"))
		*/
		{
			IImgCtx *Ctx = 0;
			HRESULT hr = CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_INPROC_SERVER, IID_IImgCtx, (LPVOID*)&Ctx);
			if (SUCCEEDED(hr))
			{
				LVariant Fn = Name;
				hr = Ctx->Load(Fn.WStr(), 0);
				if (SUCCEEDED(hr))
				{
					SIZE  Size = { -1, -1 };
					ULONG State = 0;
					int64 Start = LCurrentTime();
					while (LCurrentTime() - Start < 3000) // just in case it gets stuck....
					{
						hr = Ctx->GetStateInfo(&State, &Size, false);
						if (SUCCEEDED(hr))
						{
							if ((State & IMGLOAD_COMPLETE) || (State & IMGLOAD_STOPPED) || (State & IMGLOAD_ERROR))
							{
								break;
							}
							else
							{
								LSleep(10);
							}
						}
						else break;
					}

					if (Size.cx > 0 &&
						Size.cy > 0 &&
						pDC.Reset(new LMemDC))
					{
						if (pDC->Create(Size.cx, Size.cy, System24BitColourSpace))
						{
							HDC hDC = pDC->StartDC();
							if (hDC)
							{
								RECT rc = { 0, 0, pDC->X(), pDC->Y() };
								Ctx->Draw(hDC, &rc);
								pDC->EndDC();
							}

							if (pDC->GetBits() == 32)
							{
								// Make opaque
								int Op = pDC->Op(GDC_OR);
								pDC->Colour(Rgba32(0, 0, 0, 255), 32);
								pDC->Rectangle();
								pDC->Op(GDC_SET);
							}
						}
						else pDC.Reset();
					}
				}

				Ctx->Release();
			}
		}

		#endif

		if (pDC)
			return pDC.Release();
	}

	if (FilterStatus == LFilter::IoComponentMissing)
	{
		const char *c = Filter->GetComponentName();
		LAssert(c != NULL);
		if (c)
		{
			auto t = LString(c).SplitDelimit(",");
			for (int i=0; i<t.Length(); i++)
				NeedsCapability(t[i]);
		}
	}

	return pDC.Release();
}

bool WriteDC(const char *Name, LSurface *pDC)
{
    return GdcD->Save(Name, pDC);
}

bool GdcDevice::Save(LStream *Out, LSurface *In, const char *FileType)
{
	if (!Out || !In || !FileType)
		return false;

	LAutoPtr<LFilter> F(GFilterFactory::New(FileType, FILTER_CAP_WRITE, 0));
	if (!F)
	{
		LgiTrace("%s:%i - No filter for '%s'\n", _FL, FileType);
		return false;
	}

	return F->WriteImage(Out, In) == LFilter::IoSuccess;
}

bool GdcDevice::Save(const char *Name, LSurface *pDC)
{
	if (!Name || !pDC)
		return false;

	LFile File;
	if (!File.Open(Name, O_WRITE))
	{
		LgiTrace("%s:%i - Can't open '%s'\n", _FL, Name);
		return false;
	}

	return Save(&File, pDC, Name);
}
