/*hdr
**	FILE:			LMemDC.cpp
**	AUTHOR:			Matthew Allen
**	DATE:			14/10/2000
**	DESCRIPTION:	GDC v2.xx header
**
**	Copyright (C) 2000, Matthew Allen
**		fret@memecode.com
*/

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

#include "lgi/common/Gdc2.h"
#include "lgi/common/LgiString.h"
using namespace Gtk;

/////////////////////////////////////////////////////////////////////////////////////////////////////
#define ROUND_UP(bits) (((bits) + 7) / 8)

class LMemDCPrivate
{
public:
	::LArray<LRect> Client;
	// LRect Client;
	cairo_t *cr;
	LCairoSurfaceT Img;
	LColourSpace CreateCs;

    LMemDCPrivate()
    {
		cr = NULL;
		CreateCs = CsNone;
    }

    ~LMemDCPrivate()
    {
    	Empty();
	}

	void Empty()
	{
		if (cr)
		{
			cairo_destroy(cr);
			cr = NULL;
		}
		Img.Empty();
	}
};

LMemDC::LMemDC(int x, int y, LColourSpace cs, int flags)
{
	d = new LMemDCPrivate;
	if (cs != CsNone)
		Create(x, y, cs, flags);
}

LMemDC::LMemDC(LSurface *pDC)
{
	d = new LMemDCPrivate;
	
	if (pDC &&
		Create(pDC->X(), pDC->Y(), pDC->GetColourSpace()))
	{
		Blt(0, 0, pDC);
	}
}

LMemDC::~LMemDC()
{
	Empty();
	DeleteObj(d);
}

cairo_surface_t *LMemDC::GetSurface()
{
	return d->Img;
}

LCairoSurfaceT LMemDC::GetSubImage(LRect &r)
{
	LCairoSurfaceT s;

	if (d->Img)
	{
		cairo_format_t fmt = cairo_image_surface_get_format(d->Img);
		int pixel_bytes = GColourSpaceToBits(ColourSpace) >> 3;
		return cairo_image_surface_create_for_data(	pMem->Base + (r.y1 * pMem->Line) + (r.x1 * pixel_bytes),
													fmt,
													r.X(),
													r.Y(),
													pMem->Line);
	}

	return s;
}

OsPainter LMemDC::Handle()
{
	if (!d->cr)
	{
		d->cr = cairo_create(d->Img);
		/*
		if (d->cr && d->Client.Length())
		{
			auto &c = d->Client.Last();
			printf("Translating new handle to %i,%i\n", c.x1, c.y1);
			cairo_translate(d->cr, c.x1, c.y1);
		}
		*/
	}
	return d->cr;
}

void FreeMemDC(guchar *pixels, LMemDC *data)
{
	delete data;	
}

GdkPixbuf *LMemDC::CreatePixBuf()
{
	LMemDC *Tmp = new LMemDC(X(), Y(), CsRgba32, SurfaceRequireExactCs);
	if (!Tmp)
		return NULL;

	Tmp->Colour(0, 32);
	Tmp->Rectangle();
	Tmp->Op(GDC_ALPHA);
	Tmp->Blt(0, 0, this);

	GdkPixbuf *Pb =
		gdk_pixbuf_new_from_data ((*Tmp)[0],
								  GDK_COLORSPACE_RGB,
								  true,
								  8,
								  Tmp->X(),
								  Tmp->Y(),
								  Tmp->GetRowStep(),
								  (GdkPixbufDestroyNotify) FreeMemDC,
								  Tmp);
	if (!Pb)
		printf("%s:%i - gdk_pixbuf_new_from_data failed.\n", _FL);

	return Pb;
}

bool LMemDC::SupportsAlphaCompositing()
{
	// We can blend RGBA into memory buffers, mostly because the code is in Lgi not GTK.
	return true;
}

LPoint LMemDC::GetSize()
{
	return LPoint(pMem->x, pMem->y);
}

LRect LMemDC::ClipRgn(LRect *Rgn)
{
	LRect Old = Clip;
	
	if (Rgn)
	{
		LRect Dc(0, 0, X()-1, Y()-1);
		
		Clip = *Rgn;
		Clip.Offset(-OriginX, -OriginY);
		Clip.Bound(&Dc);
	}
	else
	{
		Clip.ZOff(X()-1, Y()-1);
	}
	
	return Old;
}

void LMemDC::SetClient(LRect *c)
{
	if (c)
	{
		Handle();

		LRect Doc;
		if (d->Client.Length())
			Doc = d->Client.Last();
		else
			Doc = Bounds();
		
		LRect r = *c;
		r.Bound(&Doc);
		d->Client.Add(r);
		
		Clip = r;
		
		OriginX = -r.x1;
		OriginY = -r.y1;

		cairo_save(d->cr);
		// cairo_rectangle(d->cr, Clip.x1, Clip.y1, Clip.X(), Clip.Y());
		// cairo_clip(d->cr);		
		cairo_translate(d->cr, c->x1, c->y1);
	}
	else
	{
		if (d->Client.Length())
			d->Client.PopLast();
		cairo_restore(d->cr);

		if (d->Client.Length())
		{
			auto &r = d->Client.Last();
			OriginX = -r.x1;
			OriginY = -r.y1;
			Clip = r;
		}
		else
		{
			OriginX = 0;
			OriginY = 0;
			Clip.ZOff(pMem->x-1, pMem->y-1);
		}
	}
}


void LMemDC::Empty()
{
	d->Empty();
	DeleteObj(pMem);
}

bool LMemDC::Lock()
{
	return false;
}

bool LMemDC::Unlock()
{
	return false;
}

void LMemDC::GetOrigin(int &x, int &y)
{
	LSurface::GetOrigin(x, y);
}

void LMemDC::SetOrigin(int x, int y)
{
	Handle();
	LSurface::SetOrigin(x, y);

	if (d->cr)
	{		
		cairo_matrix_t m;
		cairo_get_matrix(d->cr, &m);
		m.x0 = -x;
		m.y0 = -y;
		cairo_set_matrix(d->cr, &m);
	}
}

LColourSpace LMemDC::GetCreateCs()
{
	// Sometimes the colour space we get is different to the requested colour space.
	// This function returns the original requested colour space.
	return d->CreateCs;
}

bool LMemDC::Create(int x, int y, LColourSpace Cs, int Flags)
{
	int Bits = GColourSpaceToBits(Cs);
	if (x < 1 || y < 1 || Bits < 1)
		return false;
	if (Bits < 8)
		Bits = 8;

	Empty();

	bool Exact = (Flags & SurfaceRequireExactCs) != 0;
	d->CreateCs = Cs;
	
	cairo_format_t fmt = CAIRO_FORMAT_RGB24;
	switch (Bits)
	{
		case 8:
			fmt = CAIRO_FORMAT_A8;
			ColourSpace = CsIndex8;
			break;
		case 16:
			fmt = CAIRO_FORMAT_RGB16_565;
			ColourSpace = CsRgb16;
			break;
		case 24:
			fmt = CAIRO_FORMAT_RGB24;
			ColourSpace = CsBgrx32;
			break;
		case 32:
			fmt = CAIRO_FORMAT_ARGB32;
			ColourSpace = CsBgra32;
			break;
		default:
			ColourSpace = CsNone;
			return false;
	}

	if (!pMem)
		pMem = new LBmpMem;
	if (!pMem)
		return false;

	pMem->x = x;
	pMem->y = y;
	pMem->Flags = 0;
	pMem->Cs = CsNone;

	if (d->CreateCs != ColourSpace && Exact)
	{
		// Don't bother creating a cairo imaage surface, as the colour space we want is not
		// supported. Just create an in memory bitmap.
		pMem->OwnMem(true);
		pMem->Line = ((pMem->x * Bits + 31) / 32) * 4;
		LAssert(pMem->Line > 0);
		pMem->Base = new uchar[pMem->Line * pMem->y];
		pMem->Cs = ColourSpace = Cs;
	}
	else
	{
		LAssert(d->Img == NULL);
		d->Img = cairo_image_surface_create(fmt, x, y);
		if (!d->Img)
			return false;

		auto OutFmt = cairo_image_surface_get_format(d->Img);
		switch (OutFmt)
		{
			case CAIRO_FORMAT_ARGB32:
				pMem->Cs = CsBgra32;
				break;
			case CAIRO_FORMAT_RGB24:
				pMem->Cs = CsBgrx32;
				break;
			case CAIRO_FORMAT_A8:
				pMem->Cs = CsIndex8;
				break;
			case CAIRO_FORMAT_RGB16_565:
				pMem->Cs = CsRgb16;
				break;
			default:
				// LAssert(0);
				printf("%s:%i - Invalid surface format: %i (0x%x), in: %i (0x%x), sz: %ix%i\n", _FL, OutFmt, OutFmt, fmt, fmt, x, y);
				return false;
		}			
		pMem->Base = cairo_image_surface_get_data(d->Img);
		pMem->Line = cairo_image_surface_get_stride(d->Img);
	}

	int NewOp = (pApp) ? Op() : GDC_SET;

	if ( (Flags & GDC_OWN_APPLICATOR) &&
		!(Flags & GDC_CACHED_APPLICATOR))
	{
		DeleteObj(pApp);
	}

	for (int i=0; i<GDC_CACHE_SIZE; i++)
	{
		DeleteObj(pAppCache[i]);
	}

	if (NewOp < GDC_CACHE_SIZE && !DrawOnAlpha())
	{
		pApp = (pAppCache[NewOp]) ? pAppCache[NewOp] : pAppCache[NewOp] = CreateApplicator(NewOp);
		Flags &= ~GDC_OWN_APPLICATOR;
		Flags |= GDC_CACHED_APPLICATOR;
	}
	else
	{
		pApp = CreateApplicator(NewOp, pMem->Cs);
		Flags &= ~GDC_CACHED_APPLICATOR;
		Flags |= GDC_OWN_APPLICATOR;
	}

	if (!pApp)
	{
		printf("LMemDC::Create(%i,%i,%i) No Applicator.\n", x, y, Bits);
		LAssert(0);
	}

	Clip.ZOff(X()-1, Y()-1);

	return true;
}

void LMemDC::Blt(int x, int y, LSurface *Src, LRect *a)
{
	if (!Src)
		return;

	bool Status = false;
	LBlitRegions br(this, x, y, Src, a);
	if (!br.Valid())
		return;

	LScreenDC *Screen;
	if ((Screen = Src->IsScreen()))
	{
		if (pMem->Base)
		{
			// Screen -> Memory
			GdkWindow *root_window = gdk_get_default_root_window();
			if (root_window)
			{
				LAssert(!"Gtk3 FIXME");
				
				// Call the capture screen handler to draw anything between the screen and
				// cursor layers
				OnCaptureScreen();
				
				if (TestFlag(Flags, GDC_CAPTURE_CURSOR))
				{
					// Capture the cursor as well...
				}
				
				Status = true;
			}
		}
		
		if (!Status)
		{
			Colour(Rgb24(255, 0, 255), 24);
			Rectangle();
		}
	}
	else if ((*Src)[0])
	{
		// Memory -> Memory (Source alpha used)
		LSurface::Blt(x, y, Src, a);
	}
}

void LMemDC::StretchBlt(LRect *d, LSurface *Src, LRect *s)
{
	// LAssert(!"Not implemented");
}

void LMemDC::HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b)
{
	if (x1 > x2) LSwap(x1, x2);

	if (x1 < Clip.x1) x1 = Clip.x1;
	if (x2 > Clip.x2) x2 = Clip.x2;
	if (x1 <= x2 &&
		y >= Clip.y1 &&
		y <= Clip.y2 &&
		pApp)
	{
		COLOUR Prev = pApp->c;

		pApp->SetPtr(x1, y);
		for (; x1 <= x2; x1++)
		{
			if (x1 & 1)
			{
				pApp->c = a;
			}
			else
			{
				pApp->c = b;
			}

			pApp->Set();
			pApp->IncX();
		}

		pApp->c = Prev;
	}
}

void LMemDC::VertLine(int x, int y1, int y2, COLOUR a, COLOUR b)
{
	if (y1 > y2) LSwap(y1, y2);
	
	if (y1 < Clip.y1) y1 = Clip.y1;
	if (y2 > Clip.y2) y2 = Clip.y2;
	if (y1 <= y2 &&
		x >= Clip.x1 &&
		x <= Clip.x2 &&
		pApp)
	{
		COLOUR Prev = pApp->c;

		pApp->SetPtr(x, y1);
		for (; y1 <= y2; y1++)
		{
			if (y1 & 1)
			{
				pApp->c = a;
			}
			else
			{
				pApp->c = b;
			}

			pApp->Set();
			pApp->IncY();
		}

		pApp->c = Prev;
	}
}
