#include <CtrlCore/CtrlCore.h>

#ifdef GUI_GTK

//#include <shellapi.h>

namespace Upp {

#define LTIMING(x) // RTIMING(x)
#define LLOG(x)

void SetSurface(SystemDraw& w, const Rect& dest, const RGBA *pixels, Size srcsz, Point poff)
{
	w.FlushText();
	Size dsz = dest.GetSize();
	cairo_surface_t *surface = cairo_image_surface_create_for_data((byte *)pixels, CAIRO_FORMAT_ARGB32, dsz.cx, dsz.cy, 4 * dsz.cx);
	cairo_set_source_surface(w, surface, dest.left, dest.top);
	cairo_paint(w);
	cairo_surface_destroy(surface);
}

struct ImageSysData {
	Image            img;
	cairo_surface_t *surface = NULL;
	
	void Init(const Image& m, cairo_surface_t *other);
	~ImageSysData();
};

cairo_surface_t *CreateCairoSurface(const Image& img, cairo_surface_t *other)
{
	Size isz = img.GetSize();
	cairo_format_t fmt = CAIRO_FORMAT_ARGB32;
	cairo_surface_t *surface = other ? cairo_surface_create_similar_image(other, fmt, isz.cx, isz.cy)
	                                 : cairo_image_surface_create(fmt, isz.cx, isz.cy);
	cairo_surface_flush(surface);
	byte *a = (byte *)cairo_image_surface_get_data(surface);
	int stride = cairo_format_stride_for_width(fmt, isz.cx);
	for(int yy = 0; yy < isz.cy; yy++) {
		Copy((RGBA *)a, img[yy], isz.cx);
		a += stride;
	}
	cairo_surface_mark_dirty(surface);
	return surface;
}

cairo_surface_t *CreateCairoSurface(const Image& img)
{
	return CreateCairoSurface(img, NULL);
}

void ImageSysData::Init(const Image& m, cairo_surface_t *other)
{
	img = m;
	surface = CreateCairoSurface(m, other);
	SysImageRealized(img);
}

ImageSysData::~ImageSysData()
{
	SysImageReleased(img);
	cairo_surface_destroy(surface);
}

struct ImageSysDataMaker : LRUCache<ImageSysData, int64>::Maker {
	Image img;
	cairo_surface_t *other;

	virtual int64  Key() const                      { return img.GetSerialId(); }
	virtual int    Make(ImageSysData& object) const { object.Init(img, other); return img.GetLength(); }
};

void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
{
	GuiLock __;
	FlushText();
	if(img.GetLength() == 0)
		return;
	if(img.IsPaintOnceHint()) {
		SetSurface(*this, x, y, img.GetWidth(), img.GetHeight(), ~img);
		return;
	}
	LLOG("SysDrawImageOp " << img.GetSerialId() << ' ' << x << ", " << y << ", "<< img.GetSize());
	ImageSysDataMaker m;
	static LRUCache<ImageSysData, int64> cache;
	static int Rsz;
	Rsz += img.GetLength();
	if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
		Rsz = 0;
		cache.Remove([](const ImageSysData& object) {
			return object.img.GetRefCount() == 1;
		});
	}
	LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
	m.img = img;
	m.other = cairo_get_target(cr);
	ImageSysData& sd = cache.Get(m);
	if(!IsNull(color)) {
		SetColor(color);
		cairo_mask_surface(cr, sd.surface, x, y);
	}
	else {
		cairo_set_source_surface(cr, sd.surface, x, y);
		cairo_paint(cr);
	}
	static Size ssz;
	if(ssz.cx == 0)
		ssz = Ctrl::GetVirtualScreenArea().GetSize();
	cache.Shrink(4 * ssz.cx * ssz.cy, 1000); // Cache must be after Paint because of PaintOnly!
}

Draw& ImageDraw::Alpha()
{
	if(!alpha_surface) {
		alpha_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, isz.cx, isz.cy);
		alpha.cr = cairo_create(alpha_surface);
	//	cairo_set_source_rgb(alpha.cr, 0, 0, 0);
	//	cairo_paint(alpha.cr);
	}
	return alpha;
}

void CairoGet(ImageBuffer& b, Size isz, cairo_surface_t *surface, cairo_surface_t *alpha_surface)
{
	cairo_surface_flush(surface);
	byte *a = (byte *)cairo_image_surface_get_data(surface);
	int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, isz.cx);
	RGBA *t = b;
	byte *aa = NULL;
	if(alpha_surface) {
		cairo_surface_flush(alpha_surface);
		aa = (byte *)cairo_image_surface_get_data(alpha_surface);
	}
	for(int yy = 0; yy < isz.cy; yy++) {
		RGBA *s = (RGBA *)a;
		RGBA *e = s + isz.cx;
		if(aa) {
			RGBA *ss = (RGBA *)aa;
			while(s < e) {
				*t = *s++;
				(t++)->a = (ss++)->r;
			}
			aa += stride;
			b.SetKind(IMAGE_ALPHA);
		}
		else {
			while(s < e) {
				*t = *s++;
				(t++)->a = 255;
			}
			b.SetKind(IMAGE_OPAQUE);
		}
		a += stride;
	}
}

void ImageDraw::FetchStraight(ImageBuffer& b) const
{
	ImageDraw *m = const_cast<ImageDraw *>(this);
	m->FlushText();
	if(alpha_surface)
		m->alpha.FlushText();
	CairoGet(b, isz, surface, alpha_surface);
}

ImageDraw::operator Image() const
{
	ImageBuffer img(isz);
	FetchStraight(img);
	Premultiply(img);
	return Image(img);
}

Image ImageDraw::GetStraight() const
{
	ImageBuffer img(isz);
	FetchStraight(img);
	return Image(img);
}

void ImageDraw::Init(Size sz)
{
	isz = sz;
	surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, isz.cx, isz.cy);
	cr = cairo_create(surface);
//	cairo_set_source_rgb(cr, 0, 0, 0);
//	cairo_paint(cr);
	alpha_surface = NULL;
	del = true;
}

ImageDraw::ImageDraw(Size sz)
{
	Init(sz);
}

ImageDraw::ImageDraw(int cx, int cy)
{
	Init(Size(cx, cy));
}

ImageDraw::ImageDraw(cairo_t *cr_, Size sz)
{
	isz = sz;
	cr = cr_;
	surface = cairo_get_target(cr);
	alpha_surface = NULL;
	del = false;
}

ImageDraw::~ImageDraw()
{
	if(del) {
		cairo_destroy(cr);
		cairo_surface_destroy(surface);
		if(alpha_surface) {
			cairo_destroy(alpha.cr);
			cairo_surface_destroy(alpha_surface);
		}
	}
}

void BackDraw::Put(SystemDraw& w, int x, int y)
{
}

void BackDraw::Create(SystemDraw& w, int cx, int cy)
{
}

void BackDraw::Destroy()
{
}

#define FCURSOR_(x) { static Image h; ONCELOCK { h = CreateImage(Size(1, 1), Black); h.SetAuxData(x + 1); } return h; }

Image Image::Arrow() FCURSOR_(GDK_LEFT_PTR)
Image Image::Wait() FCURSOR_(GDK_WATCH)
Image Image::IBeam() FCURSOR_(GDK_XTERM)
Image Image::No() FCURSOR_(GDK_CIRCLE)
Image Image::SizeAll() FCURSOR_(GDK_FLEUR)
Image Image::SizeHorz() FCURSOR_(GDK_SB_H_DOUBLE_ARROW)
Image Image::SizeVert() FCURSOR_(GDK_SB_V_DOUBLE_ARROW)
Image Image::SizeTopLeft() FCURSOR_(GDK_TOP_LEFT_CORNER)
Image Image::SizeTop() FCURSOR_(GDK_TOP_SIDE)
Image Image::SizeTopRight() FCURSOR_(GDK_TOP_RIGHT_CORNER)
Image Image::SizeLeft() FCURSOR_(GDK_LEFT_SIDE)
Image Image::SizeRight() FCURSOR_(GDK_RIGHT_SIDE)
Image Image::SizeBottomLeft() FCURSOR_(GDK_BOTTOM_LEFT_CORNER)
Image Image::SizeBottom() FCURSOR_(GDK_BOTTOM_SIDE)
Image Image::SizeBottomRight()  FCURSOR_(GDK_BOTTOM_RIGHT_CORNER)
Image Image::Cross() FCURSOR_(GDK_CROSSHAIR)
Image Image::Hand() FCURSOR_(GDK_HAND2)

}

#endif
