#include "lgi/common/Lgi.h"
#include "lgi/common/Palette.h"

/////////////////////////////////////////////////////////////////////////////
// Mem ops
void Add64(ulong *DestH, ulong *DestL, ulong SrcH, ulong SrcL)
{
	*DestH += SrcH + (*DestL & SrcL & 0x80000000) ? 1 : 0;
	*DestL += SrcL;
}

void MemSet(void *d, int c, uint s)
{
	if (d && s > 0) memset(d, c, s);
}

void MemCpy(void *d, void *s, uint l)
{
	if (d && s && l > 0) memcpy(d, s, l);
}

void MemAnd(void *d, void *s, uint l)
{
	uchar *D = (uchar*) d;
	uchar *S = (uchar*) s;
	if (D && S)
	{
		for (; l > 0; l--)
		{
			*D++ &= *S++;
		}
	}
}

void MemXor(void *d, void *s, uint l)
{
	uchar *D = (uchar*) d;
	uchar *S = (uchar*) s;
	if (D && S)
	{
		for (; l > 0; l--)
		{
			*D++ ^= *S++;
		}
	}
}

void MemOr(void *d, void *s, uint l)
{
	uchar *D = (uchar*) d;
	uchar *S = (uchar*) s;
	if (D && S)
	{
		for (; l > 0; l--)
		{
			*D++ |= *S++;
		}
	}
}

//////////////////////////////////////////////////////////////////////
bool LFindBounds(LSurface *pDC, LRect *rc)
{
	if (!pDC || ! rc)
		return false;

	LAssert(pDC->GetColourSpace() == System32BitColourSpace);

	// Move top border down to image
	while (rc->y1 < rc->y2)
	{
		System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y1];
		if (!p)
			return false;
		p += rc->x1;
		System32BitPixel *e = p + rc->X();
		bool IsTrans = true;
		while (p < e)
		{
			if (p->a != 0)
			{
				IsTrans = false;
				break;
			}			
			p++;
		}
		if (IsTrans)
			rc->y1++;
		else
			break;
	}
	
	// Move bottom border up to image
	while (rc->y2 >= rc->y1)
	{
		System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y2];
		if (!p)
			return false;
		p += rc->x1;
		System32BitPixel *e = p + rc->X();
		bool IsTrans = true;
		while (p < e)
		{
			if (p->a != 0)
			{
				IsTrans = false;
				break;
			}
			p++;
		}
		if (IsTrans)
			rc->y2--;
		else
			break;
	}

	// Do the left and right edges too
	int x1 = rc->x2;
	int x2 = rc->x1;	
	for (int y=rc->y1; y<=rc->y2; y++)
	{
		System32BitPixel *p = (System32BitPixel*)(*pDC)[y];
		if (!p)
			return false;
		System32BitPixel *px1 = p + rc->x1;
		System32BitPixel *px2 = p + rc->x2;
		while (px1 < px2 && px1->a == 0)
			px1++;
		x1 = MIN(x1, (int) (px1 - p));

		while (px2 >= px1 && px2->a == 0)
			px2--;
		x2 = MAX(x2, (int) (px2 - p));
	}
	rc->x1 = x1;
	rc->x2 = x2;
	
	if (rc->Valid())
		return true;

	// No data?
	rc->ZOff(-1, -1);
	return false;
}

//////////////////////////////////////////////////////////////////////
// Drawing functions
void LDrawBox(LSurface *pDC, LRect &r, bool Sunken, bool Fill)
{
	if (Fill)
	{
		pDC->Colour(LColour(L_MED));
		pDC->Rectangle(r.x1+1, r.y1+1, r.x2-1, r.y2-1);
	}

	pDC->Colour((Sunken) ? LColour(L_LIGHT) : LColour(L_LOW));
	pDC->Line(r.x2, r.y2, r.x2, r.y1);
	pDC->Line(r.x2, r.y2, r.x1, r.y2);

	pDC->Colour((Sunken) ? LColour(L_LOW) : LColour(L_LIGHT));
	pDC->Line(r.x1, r.y1, r.x1, r.y2);
	pDC->Line(r.x1, r.y1, r.x2, r.y1);
}

void LWideBorder(LSurface *pDC, LRect &r, LEdge Type)
{
	if (!pDC) return;
	COLOUR Old = pDC->Colour();
	LColour VLow = LColour(L_SHADOW);
	LColour Low = LColour(L_LOW);
	LColour High = LColour(L_HIGH);
	LColour VHigh = LColour(L_LIGHT);
	
	switch (Type)
	{
		case EdgeXpSunken:
		{
			// XP theme
			pDC->Colour(Low);
			pDC->Line(r.x1, r.y1, r.x2-1, r.y1);
			pDC->Line(r.x1, r.y1, r.x1, r.y2-1);

			pDC->Colour(VLow);
			pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1);
			pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2);

			pDC->Colour(High);
			pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1);
			pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1);

			pDC->Colour(VHigh);
			pDC->Line(r.x2, r.y2, r.x2, r.y1);
			pDC->Line(r.x2, r.y2, r.x1, r.y2);
			break;
		}
		case EdgeXpRaised:
		{
			pDC->Colour(VHigh);
			pDC->Line(r.x1, r.y1, r.x2-1, r.y1);
			pDC->Line(r.x1, r.y1, r.x1, r.y2-1);

			pDC->Colour(High);
			pDC->Line(r.x1+1, r.y1+1, r.x2-1, r.y1+1);
			pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-1);

			pDC->Colour(Low);
			pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1);
			pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1);

			pDC->Colour(VLow);
			pDC->Line(r.x2, r.y2, r.x2, r.y1);
			pDC->Line(r.x2, r.y2, r.x1, r.y2);
			break;
		}
		case EdgeXpChisel:
		{
			pDC->Colour(Low);
			pDC->Line(r.x1, r.y1, r.x2-1, r.y1);
			pDC->Line(r.x1, r.y1, r.x1, r.y2-1);

			pDC->Colour(VHigh);
			pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1);
			pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2);

			pDC->Colour(Low);
			pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1);
			pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1);

			pDC->Colour(VHigh);
			pDC->Line(r.x2, r.y2, r.x2, r.y1);
			pDC->Line(r.x2, r.y2, r.x1, r.y2);
			break;
		}
		case EdgeWin7Sunken:
		case EdgeWin7FocusSunken:
		{
			bool Focus = Type == EdgeWin7FocusSunken;
			
			// Win7 theme
			LColour Med(L_MED), Ws(L_WORKSPACE);
			LColour Mixer = Med.GetGray() >= 128 ? LColour::Black : LColour::White;
			LColour TopLeft, RightBottom;
			if (Focus)
			{
				LColour Focus(L_FOCUS_SEL_BACK);
				TopLeft = Med.Mix(Focus, 0.4f);
				RightBottom = Ws.Mix(Focus, 0.2f);
			}
			else
			{
				TopLeft = Med.Mix(Mixer, 0.4f);
				RightBottom = Ws.Mix(Mixer, 0.2f);
			}
			
			// Top corners
			pDC->Colour(Med);
			pDC->Set(r.x1, r.y1);
			pDC->Set(r.x2, r.y1);
			pDC->Colour(Ws.Mix(TopLeft));
			pDC->Set(r.x1+1, r.y1+1);
			pDC->Set(r.x2-1, r.y1+1);
			pDC->Set(r.x1+1, r.y2-1);

			// Edges
			pDC->Colour(TopLeft);
			pDC->Line(r.x1, r.y1+1, r.x1, r.y2-1); // left
			pDC->Line(r.x1+1, r.y1, r.x2-1, r.y1); // top

			// Bottom edge
			pDC->Colour(RightBottom);
			pDC->Line(r.x1+1, r.y2, r.x2-1, r.y2); // bottom
			pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1); // right

			// Inner workspace rect
			pDC->Colour(Ws);
			pDC->Line(r.x1+2, r.y1+1, r.x2-2, r.y1+1); // top
			pDC->Line(r.x1+1, r.y1+2, r.x1+1, r.y2-2); // left
			pDC->Line(r.x2-1, r.y1+2, r.x2-1, r.y2-2); // right
			pDC->Line(r.x1+2, r.y2-1, r.x2-1, r.y2-1); // bottom
			break;
		}
		default:
		{
			return;
		}	
	}

	r.Inset(2, 2);
	pDC->Colour(Old);
}

void LThinBorder(LSurface *pDC, LRect &r, LEdge Type)
{
	if (!pDC) return;
	COLOUR Old = pDC->Colour();

	switch (Type)
	{
		case EdgeXpSunken:
		case EdgeWin7FocusSunken:
		case EdgeWin7Sunken:
		{
			pDC->Colour(LColour(L_LIGHT));
			pDC->Line(r.x2, r.y2, r.x2, r.y1);
			pDC->Line(r.x2, r.y2, r.x1, r.y2);

			pDC->Colour(LColour(L_LOW));
			pDC->Line(r.x1, r.y1, r.x1, r.y2);
			pDC->Line(r.x1, r.y1, r.x2, r.y1);

			r.Inset(1, 1);
			break;
		}
		case EdgeXpRaised:
		{
			pDC->Colour(LColour(L_LOW));
			pDC->Line(r.x2, r.y2, r.x2, r.y1);
			pDC->Line(r.x2, r.y2, r.x1, r.y2);

			pDC->Colour(LColour(L_LIGHT));
			pDC->Line(r.x1, r.y1, r.x1, r.y2);
			pDC->Line(r.x1, r.y1, r.x2, r.y1);

			r.Inset(1, 1);
			break;
		}
		default:
		{
			LAssert(0);
			break;
		}
	}
	
	pDC->Colour(Old);
}

void LFlatBorder(LSurface *pDC, LRect &r, int Width)
{
	pDC->Colour(LColour(L_MED));
	if (Width < 1 || r.X() < (2 * Width) || r.Y() < (2 * Width))
	{
		pDC->Rectangle(&r);
		r.ZOff(-1, -1);
	}
	else
	{
		pDC->Rectangle(r.x1, r.y1, r.x2, r.y1+Width-1);
		pDC->Rectangle(r.x1, r.y2-Width+1, r.x2, r.y2);
		pDC->Rectangle(r.x1, r.y1+Width, r.x1+Width-1, r.y2-Width);
		pDC->Rectangle(r.x2-Width+1, r.y1+Width, r.x2, r.y2-Width);
		r.Inset(Width, Width);
	}
}

void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray<LColourStop> &Stops)
{
	int CurStop = 0;
	LColourStop *This = Stops.Length() > CurStop ? &Stops[CurStop] : 0;
	CurStop++;
	LColourStop *Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0;

	int Limit = Vert ? r.Y() : r.X();
	for (int n=0; n<Limit; n++)
	{
		LColour c(0, 32);
		float p = (float)n/Limit;
		if (This)
		{
			DoStop:
			if (!Next || p <= This->Pos)
			{
				// Just this
				c = This->Colour;
			}
			else if (p > This->Pos && p < Next->Pos)
			{
				// Interpolate between this and next
				float d = Next->Pos - This->Pos;
				float t = (Next->Pos - p) / d;
				float n = (p - This->Pos) / d;
				uint8_t r = (uint8_t) ((This->Colour.r() * t) + (Next->Colour.r() * n));
				uint8_t g = (uint8_t) ((This->Colour.g() * t) + (Next->Colour.g() * n));
				uint8_t b = (uint8_t) ((This->Colour.b() * t) + (Next->Colour.b() * n));
				uint8_t a = (uint8_t) ((This->Colour.a() * t) + (Next->Colour.a() * n));
				c.Rgb(r, g, b, a);
			}
			else if (p >= Next->Pos)
			{
				// Get next colour stops
				This = Stops.Length() > CurStop ? &Stops[CurStop] : 0;
				CurStop++;
				Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0;
				goto DoStop;
			}

			pDC->Colour(c);
			if (Vert)
				pDC->Line(r.x1, r.y1 + n, r.x2, r.y1 + n);
			else
				pDC->Line(r.x1 + n, r.y1, r.x1 + n, r.y2);
		}
	}
}

//////////////////////////////////////////////////////////////////////////////////
// Other Gdc Stuff
LSurface *ConvertDC(LSurface *pDC, int Bits)
{
	LSurface *pNew = new LMemDC;
	if (pNew && pNew->Create(pDC->X(), pDC->Y(), GBitsToColourSpace(Bits)))
	{
		pNew->Blt(0, 0, pDC);
		DeleteObj(pDC);
		return pNew;
	}
	return pDC;
}

LColour GdcMixColour(LColour c1, LColour c2, float HowMuchC1)
{
	float HowMuchC2 = 1.0f - HowMuchC1;

	uint8_t r = (uint8_t) ((c1.r()*HowMuchC1) + (c2.r()*HowMuchC2));
	uint8_t g = (uint8_t) ((c1.g()*HowMuchC1) + (c2.g()*HowMuchC2));
	uint8_t b = (uint8_t) ((c1.b()*HowMuchC1) + (c2.b()*HowMuchC2));
	uint8_t a = (uint8_t) ((c1.a()*HowMuchC1) + (c2.a()*HowMuchC2));
	
	return LColour(r, g, b, a);
}

COLOUR CBit(int DstBits, COLOUR c, int SrcBits, GPalette *Pal)
{
	if (SrcBits == DstBits)
	{
		return c;
	}
	else
	{
		switch (SrcBits)
		{
			case 8:
			{
				GdcRGB Grey, *p = 0;
				if (!Pal || !(p = (*Pal)[c]))
				{
					Grey.r = Grey.g = Grey.b = c & 0xFF;
					p = &Grey;
				}

				switch (DstBits)
				{
					case 16:
					{
						return Rgb16(p->r, p->g, p->b);
					}
					case 24:
					{
						return Rgb24(p->r, p->g, p->b);
					}
					case 32:
					{
						return Rgb32(p->r, p->g, p->b);
					}
				}
				break;
			}
			case 15:
			{
				int R = R15(c);
				int G = G15(c);
				int B = B15(c);

				R = R | (R >> 5);
				G = G | (G >> 5);
				B = B | (B >> 5);

				switch (DstBits)
				{
					case 8:
					{
						if (Pal)
						{
							return Pal->MatchRgb(Rgb24(R, G, B));
						}
						break;
					}
					case 16:
					{
						return Rgb16(R, G, B);
					}
					case 24:
					{
						return Rgb24(R, G, B);
					}
					case 32:
					{
						return Rgb32(R, G, B);
					}
				}
				break;
			}
			case 16:
			{
				/*
				int R = ((c>>8)&0xF8) | ((c>>13)&0x7);
				int G = ((c>>3)&0xFC) | ((c>>9)&0x3);
				int B = ((c<<3)&0xF8) | ((c>>2)&0x7);
				*/
				int R = (c >> 8) & 0xF8;
				int G = (c >> 3) & 0xFC;
				int B = (c << 3) & 0xF8;

				switch (DstBits)
				{
					case 8:
					{
						if (Pal)
						{
							return Pal->MatchRgb(Rgb24(R, G, B));
						}
						break;
					}
					case 15:
					{
						return Rgb16To15(c);
					}
					case 24:
					{
						return Rgb24(R, G, B);
					}
					case 32:
					{
						return Rgb32(R, G, B);
					}
				}
				break;
			}

			case 24:
			{
				switch (DstBits)
				{
					case 8:
					{
						if (Pal)
						{
							return Pal->MatchRgb(c);
						}
						break;
					}
					case 15:
					{
						return Rgb24To15(c);
					}
					case 16:
					{
						if (LGetOs() == LGI_OS_WIN32 ||
							LGetOs() == LGI_OS_WIN64)
						{
							return Rgb24To16(c);
						}

						int r = MAX(R24(c) - 1, 0) >> 3;
						int g = MAX(G24(c) - 1, 0) >> 2;
						int b = MAX(B24(c) - 1, 0) >> 3;

						return (r << 11) | (g << 5) | (b);
					}
					case 24:
					case 48:
					{
						return c;
					}
					case 32:
					case 64:
					{
						return Rgb24To32(c);
					}
					default:
						LAssert(0);
						break;
				}
				break;
			}

			case 32:
			{
				switch (DstBits)
				{
					case 8:
					{
						if (Pal)
						{
							return Pal->MatchRgb(Rgb32To24(c));
						}
						break;
					}
					case 15:
					{
						return Rgb32To15(c);
					}
					case 16:
					{
						return Rgb32To16(c);
					}
					case 24:
					{
						return Rgb32To24(c);
					}
				}
				break;
			}
		}
	}

	return c;
}

/////////////////////////////////////////////////////////////////////////////
const char *LColourSpaceToString(LColourSpace cs)
{
	#define CS_STR_BUF 4
	static int Cur = 0;
	static char Buf[CS_STR_BUF][16];
	static const char *CompTypes[] =
	{
		"N", // None
		"I", // Index
		"R", // Red
		"G", // Green
		"B", // Blue
		"A", // Alpha
		"X", // Pad
		"H", // Hue
		"S", // Sat
		"L", // Lum
		"C", // Cyan
		"M", // Magenta
		"Y", // Yellow
		"B", // Black
		"?",
		"?",
	};

	char *start = Buf[Cur++], *s = start;
	int total = 0;
	bool first = true;
	if (Cur >= CS_STR_BUF)
		Cur = 0;
	
	*s++ = 'C';
	*s++ = 's';
// printf("Converting Cs to String: 0x%x\n", cs);
	for (int i=3; i>=0; i--)
	{
		int c = (((uint32_t)cs) >> (i << 3)) & 0xff;
// printf("    c[%i] = 0x%x\n", i, c);
		if (c)
		{
			LComponentType type = (LComponentType)(c >> 4);
			int size = c & 0xf;
			if (first)
			{
				*s++ = CompTypes[type][0];
				first = false;
			}
			else
			{
				*s++ = tolower(CompTypes[type][0]);
			}
			
			total += size ? size : 16;
		}
	}

	s += sprintf_s(s, 4, "%i", total);	
	return start;
}

int GColourSpaceChannels(LColourSpace Cs)
{
	int Channels = 0;
	
	while (Cs)
	{
		uint8_t c = Cs & 0xff;
		if (!c)
			break;
		
		Channels++;
		((int&)Cs) >>= 8;
	}
	
	return Channels;
}

bool GColourSpaceHasAlpha(LColourSpace Cs)
{
	while (Cs)
	{
		uint8_t c = Cs & 0xff;
		LComponentType type = (LComponentType)(c >> 4);
		if (type == CtAlpha)
			return true;
		
		((int&)Cs) >>= 8;
	}
	
	return false;
}

int GColourSpaceToBits(LColourSpace ColourSpace)
{
	uint32_t c = ColourSpace;
	int bits = 0;
	while (c)
	{
		if (c & 0xf0)
		{
			int n = c & 0xf;
			bits += n ? n : 16;
		}
		c >>= 8;
	}
	return bits;
}

LColourSpace GStringToColourSpace(const char *c)
{
	if (!c)
		return CsNone;

	if (!_strnicmp(c, "Cs", 2))
	{
		// "Cs" style colour space
		c += 2;
		
		const char *n = c;
		while (*n && !IsDigit(*n))
			n++;
		int Depth = ::atoi(n);
		
		if (!_strnicmp(c, "Index", 5))
		{
			if (Depth == 8)
				return CsIndex8;
		}
		else
		{
			LArray<LComponentType> Comp;
			while (*c)
			{
				switch (tolower(*c))
				{
					case 'r':
						Comp.Add(CtRed);
						break;
					case 'g':
						Comp.Add(CtGreen);
						break;
					case 'b':
						Comp.Add(CtBlue);
						break;
					case 'a':
						Comp.Add(CtAlpha);
						break;
					case 'x':
						Comp.Add(CtPad);
						break;
					case 'c':
						Comp.Add(CtCyan);
						break;
					case 'm':
						Comp.Add(CtMagenta);
						break;
					case 'y':
						Comp.Add(CtYellow);
						break;
					case 'k':
						Comp.Add(CtBlack);
						break;
					case 'h':
						Comp.Add(CtHue);
						break;
					case 'l':
						Comp.Add(CtLuminance);
						break;
					case 's':
						Comp.Add(CtSaturation);
						break;
				}
				c++;
			}
			
			LColourSpaceBits a;
			ZeroObj(a);
			if (Comp.Length() == 3)
			{
				a[0].Type(Comp[0]);
				a[1].Type(Comp[1]);
				a[2].Type(Comp[2]);
				if (Depth == 24)
				{
					a[0].Size(8);
					a[1].Size(8);
					a[2].Size(8);
				}
				else if (Depth == 16)
				{
					a[0].Size(5);
					a[1].Size(6);
					a[2].Size(5);
				}
				else if (Depth == 15)
				{
					a[0].Size(5);
					a[0].Size(5);
					a[0].Size(5);
				}
				else if (Depth == 48)
				{
					a[0].Size(0);
					a[1].Size(0);
					a[2].Size(0);
				}
				else return CsNone;
			}
			else if (Comp.Length() == 4)
			{
				a[0].Type(Comp[0]);
				a[1].Type(Comp[1]);
				a[2].Type(Comp[2]);
				a[3].Type(Comp[3]);
				if (Depth == 32)
				{
					a[0].Size(8);
					a[1].Size(8);
					a[2].Size(8);
					a[3].Size(8);
				}
				else if (Depth == 64)
				{
					a[0].Size(0);
					a[1].Size(0);
					a[2].Size(0);
					a[3].Size(0);
				}
				else return CsNone;
			}
			
			LColourSpace Cs = (LColourSpace)a.All;
			return Cs;
		}
	}
	else if (!_strnicmp(c, "System", 6))
	{
		// "System" colour space
		c += 6;
		int Depth = ::atoi(c);
		if (Depth)
			return GBitsToColourSpace(Depth);
	}
	
	return CsNone;
}

LColourSpace GBitsToColourSpace(int Bits)
{
	switch (Bits)
	{
		case 8:
			return CsIndex8;
		case 15:
			return System15BitColourSpace;
		case 16:
			return System16BitColourSpace;
		case 24:
			return System24BitColourSpace;
		case 32:
			return System32BitColourSpace;
		default:
			LAssert(!"Unknown colour space.");
			break;
	}
	
	return CsNone;
}

bool GColourSpaceTest()
{
	union {
		uint8_t b4[4];
		uint32_t u32;
		LBgrx32 bgrx32;
		LXrgb32 xrgb32;
		LRgba32 rgba32;
	};
	
	b4[0] = 1;
	b4[1] = 2;
	b4[2] = 3;
	b4[3] = 4;
	#if 0 // def LGI_SDL
	printf("LeastSigBit=%i, LeastSigByte=%i, u32=%08x\n", LeastSigBit, LeastSigByte, u32);
	printf("bgrx32=%i,%i,%i,%i\n", bgrx32.b, bgrx32.g, bgrx32.r, bgrx32.pad);
	printf("xrgb32=%i,%i,%i,%i\n", xrgb32.pad, xrgb32.r, xrgb32.g, xrgb32.b);
	#endif
	if (bgrx32.b != 1 ||
		bgrx32.pad != 4 ||
		xrgb32.pad != 1 ||
		xrgb32.b != 4)
	{
		LAssert(!"32bit colour space byte ordering wrong. Flip the value of LEAST_SIG_BYTE_FIRST");
		return false;
	}
	
	union {
		uint16 u16;
		LRgb16 rgb16;
	};
	
	rgba32.r = 0xff;
	rgba32.g = 0;
	rgba32.b = 0;
	rgba32.a = 0;
	u16 = 0xf800;
	#if 0 // def LGI_SDL
	printf("rgba32=%i,%i,%i,%i or %08x\n", rgba32.r, rgba32.g, rgba32.b, rgba32.a, u32);
	printf("rgb16=%i,%i,%i or %04x\n", rgb16.r, rgb16.g, rgb16.b, u16);
	#endif
	if (rgb16.r != 0x1f ||
		rgb16.b != 0x0)
	{
		LAssert(!"16bit colour space bit ordering wrong. Flip the value of LEAST_SIG_BIT_FIRST");
		return false;
	}
	
	return true;
}

////////////////////////////////////////////////////////////////////////
LSurface *LInlineBmp::Create(uint32_t TransparentPx)
{
	LSurface *pDC = new LMemDC;
	if (pDC->Create(X, Y, System32BitColourSpace, LSurface::SurfaceRequireExactCs))
	{
		LBmpMem Src, Dst;
		
		Src.Base = (uint8_t*)Data;
		Src.Line = X * Bits >> 3;
		Src.x = X;
		Src.y = Y;
		switch (Bits)
		{
			case 8: Src.Cs = CsIndex8; break;
			case 15: Src.Cs = CsRgb15; break;
			case 16: Src.Cs = CsRgb16; break;
			case 24: Src.Cs = CsRgb24; break;
			case 32: Src.Cs = CsRgba32; break;
			default: Src.Cs = CsNone; break;
		}
		
		Dst.Base = (*pDC)[0];
		Dst.Line = pDC->GetRowStep();
		Dst.x = pDC->X();
		Dst.y = pDC->Y();
		Dst.Cs = pDC->GetColourSpace();
		
		LRopUniversal(&Dst, &Src, false);
		
		if (TransparentPx != 0xffffffff)
		{
			for (int y=0; y<Y; y++)
			{
				LPointer s;
				s.u8 = (uint8_t*)Data + (y * Src.Line);
				uint32_t *d = (uint32_t*)(*pDC)[y];
				
				switch (Bits >> 3)
				{
					case 1:
					{
						for (int x=0; x<X; x++)
						{
							if (s.u8[x] == TransparentPx)
								d[x] = 0;
						}
						break;
					}
					case 2:
					{
						for (int x=0; x<X; x++)
						{
							if (s.u16[x] == TransparentPx)
								d[x] = 0;
						}
						break;
					}
					case 3:
					{
						REG uint8_t r = R24(TransparentPx);
						REG uint8_t g = G24(TransparentPx);
						REG uint8_t b = B24(TransparentPx);
						REG LRgb24 *px = (LRgb24*) s.u8;
						REG LRgb24 *e = px + X;
						while (px < e)
						{
							if (px->r == r &&
								px->g == g &&
								px->b == b)
							{
								*d = 0;
							}
							d++;
						}
						break;
					}
					case 4:
					{
						for (int x=0; x<X; x++)
						{
							if (s.u32[x] == TransparentPx)
								d[x] = 0;
						}
						break;
					}
				}
			}
		}
	}
	else
	{
		DeleteObj(pDC);
	}

	return pDC;
}

/////////////////////////////////////////////////////////////////////////////////////////////
#include "lgi/common/Rops.h"

bool LRopRgb(uint8_t *d, LColourSpace DstCs, uint8_t *s, LColourSpace SrcCs, int x, bool Composite)
{
	// This is just a huge switch statement that takes care of all possible combinations
	// of src and dst RGB colour space formats. The 'GRopsCases.cpp' code is generated
	// automatically by 'generate_rops_cases.py' in the same folder as this code.
	#define JointCs(dst, src) (((uint64)(dst) << 32) | (src))
	switch (JointCs(DstCs, SrcCs))
	{
		#include "../../src/common/Gdc2/RopsCases.cpp"
		default:
		{
			LColourSpaceBits Src, Dst;
			Src.All = SrcCs;
			Dst.All = DstCs;
			
			LgiTrace("%s:%i - Unsupported ROP: %s -> %s\n", _FL, LColourSpaceToString(SrcCs), LColourSpaceToString(DstCs));
			LAssert(!"Unsupported pixel conversion.");
			return false;
		}
	}
	return true;
}

/// Universal bit blt method
bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite)
{
	if (!Dst || !Src)
		return false;

	// Work out conversion area...
	int Cx = MIN(Dst->x, Src->x);
	int Cy = MIN(Dst->y, Src->y);

	// Size of src and dst pixels:
	int SrcBits = GColourSpaceToBits(Src->Cs);
	int DstBits = GColourSpaceToBits(Dst->Cs);

	if (Dst->Cs == Src->Cs && !Composite)
	{
		// No conversion idiom
		uint8_t *d = Dst->Base;
		uint8_t *s = Src->Base;
		int BytesPerPx = (SrcBits + 7) / 8;
		int Bytes = BytesPerPx * Cx;
		for (int y=0; y<Cy; y++)
		{
			memcpy(d, s, Bytes);
			d += Dst->Line;
			s += Src->Line;
		}
		return true;
	}

	if (SrcBits > 8 && DstBits > 8)
	{
		uint8_t *d = Dst->Base;
		uint8_t *s = Src->Base;

		for (int y=0; y<Cy; y++)
		{
			if (!LRopRgb(d, Dst->Cs, s, Src->Cs, Cx, Composite))
				return false;
			d += Dst->Line;
			s += Src->Line;
		}
		
		return true;
	}
	else
	{
	}

	// LAssert(!"Unsupported pixel conversion.");
	return false;
}

int LScreenDpi()
{
	#if LGI_COCOA
	
		static int Dpi = 0;
		if (!Dpi)
		{
			NSScreen *screen = [NSScreen mainScreen];
			NSDictionary *description = [screen deviceDescription];
			NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
			CGSize displayPhysicalSize = CGDisplayScreenSize(
						[[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
			Dpi = (int) ((displayPixelSize.width / displayPhysicalSize.width) * 25.4f);
		}
		return Dpi;

	#else

		return 96; // A reasonable default.
	
	#endif
}

bool LMemDC::SwapRedAndBlue()
{
	switch (GetColourSpace())
	{
		#define ROP(cs)								\
			case Cs##cs:							\
			{										\
				for (int y=0; y<Y(); y++)			\
				{									\
					auto p = (L##cs*)((*this)[y]);	\
					if (!p)							\
						break;						\
					auto e = p + X();				\
					while (p < e)					\
					{								\
						LSwap(p->r, p->b);			\
						p++;						\
					}								\
					break;							\
				}									\
				break;								\
			}

		ROP(Rgb24)
		ROP(Bgr24)

		ROP(Rgbx32)
		ROP(Bgrx32)
		ROP(Xrgb32)
		ROP(Xbgr32)

		ROP(Argb32)
		ROP(Abgr32)
		ROP(Rgba32)
		ROP(Bgra32)

		default:
			return false;
	}

	return true;
}

