#include "CocoMM.h"

#ifdef GUI_COCOA

#define LLOG(x) DLOG(x)

namespace Upp {

CGImageRef createCGImage(const Image& img)
{
	if(IsNull(img))
		return NULL;
	Image *km = new Image(img); // to keep data alive
	CGDataProvider *dataProvider = CGDataProviderCreateWithData(km, ~img, img.GetLength() * sizeof(RGBA),
	                               [](void *info, const void *data, size_t size) { delete (Image *)info; });
	static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong...
	Upp::Size isz = img.GetSize();
    CGImageRef cg_img = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA),
                                      colorSpace, kCGImageAlphaPremultipliedFirst,
                                      dataProvider, 0, false, kCGRenderingIntentDefault);
	CGDataProviderRelease(dataProvider);
	return cg_img;
}

struct ImageSysData {
	Image            img;
	CGImageRef       cgimg = NULL;
	
	void Init(const Image& img);
	~ImageSysData();
};

// Note: Cocoa U++ has to ignore paintonly flag as the data of image are directly referenced by
// data provider

void ImageSysData::Init(const Image& m)
{
	img = m;
	cgimg = createCGImage(img);
	// Do not call SysImageRealized(img) here
}

ImageSysData::~ImageSysData()
{
	CGImageRelease(cgimg);
}

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

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

static LRUCache<ImageSysData, int64> cg_image_cache;

static void sCleanImageCache(const Image& img)
{
	static int Rsz;
	Rsz += img.GetLength();
	if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
		Rsz = 0;
		cg_image_cache.Remove([](const ImageSysData& object) {
			return object.img.GetRefCount() == 1; // the object.img is nowhere else than in cache
		});
	}
}

void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
{
	GuiLock __;

	if(img.GetLength() == 0)
		return;
#if 0
	Image m = IsNull(color) ? img : CachedSetColorKeepAlpha(img, color);
	Image *km = new Image(m); // to keep data alive
	CGDataProvider *dataProvider = CGDataProviderCreateWithData(km, ~m, m.GetLength() * sizeof(RGBA),
	                               [](void *info, const void *data, size_t size) { delete (Image *)info; });
	static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong...
	Upp::Size isz = img.GetSize();
    CGImageRef cgimg = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA),
                                      colorSpace, kCGImageAlphaPremultipliedFirst,
                                      dataProvider, 0, false, kCGRenderingIntentDefault);


	if(cgimg) {
		Size isz = img.GetSize();
		CGContextSaveGState(cgHandle);
		Point off = GetOffset();
		CGContextTranslateCTM(cgHandle, x + off.x, y + off.y);
		CGContextScaleCTM(cgHandle, 1.0, -1.0);
		CGContextDrawImage(cgHandle, CGRectMake(0, -isz.cy, isz.cx, isz.cy), cgimg);
	    CGContextRestoreGState(cgHandle);
		CGImageRelease(cgimg);
	}

	CGDataProviderRelease(dataProvider);

#else
	sCleanImageCache(img);

	ImageSysDataMaker m;
	m.img = IsNull(color) ? img : CachedSetColorKeepAlpha(img, color); // TODO: Can setcolor be optimized out? By masks e.g.?
	ImageSysData& sd = cg_image_cache.Get(m);
	if(sd.cgimg) {
		Size isz = img.GetSize();
		CGContextSaveGState(cgHandle);
		Point off = GetOffset();
		CGContextTranslateCTM(cgHandle, x + off.x, y + off.y);
		CGContextScaleCTM(cgHandle, 1.0, -1.0);
		CGContextDrawImage(cgHandle, CGRectMake(0, -isz.cy, isz.cx, isz.cy), sd.cgimg);
	    CGContextRestoreGState(cgHandle);
	}
	Size sz = Ctrl::GetPrimaryScreenArea().GetSize();
	cg_image_cache.Shrink(4 * sz.cx * sz.cy, 1000); // Cache must be after Paint because of PaintOnly!
#endif
}

// TODO: https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview

enum {
	MM_Arrow = 1,
	MM_Wait,
	MM_IBeam,
	MM_No,
	MM_SizeAll,
	MM_SizeHorz,
	MM_SizeVert,
	MM_SizeTopLeft,
	MM_SizeTop,
	MM_SizeTopRight,
	MM_SizeLeft,
	MM_SizeRight,
	MM_SizeBottomLeft,
	MM_SizeBottom,
	MM_SizeBottomRight,
	MM_Cross,
	MM_Hand,
};


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

Image Image::Arrow() FCURSOR_(MM_Arrow)
Image Image::Wait() FCURSOR_(MM_Wait)
Image Image::IBeam() FCURSOR_(MM_IBeam)
Image Image::No() FCURSOR_(MM_No)
Image Image::SizeAll() FCURSOR_(MM_SizeAll)
Image Image::SizeHorz() FCURSOR_(MM_SizeHorz)
Image Image::SizeVert() FCURSOR_(MM_SizeVert)
Image Image::SizeTopLeft() FCURSOR_(MM_SizeTopLeft)
Image Image::SizeTop() FCURSOR_(MM_SizeTop)
Image Image::SizeTopRight() FCURSOR_(MM_SizeTopRight)
Image Image::SizeLeft() FCURSOR_(MM_SizeLeft)
Image Image::SizeRight() FCURSOR_(MM_SizeRight)
Image Image::SizeBottomLeft() FCURSOR_(MM_SizeBottomLeft)
Image Image::SizeBottom() FCURSOR_(MM_SizeBottom)
Image Image::SizeBottomRight() FCURSOR_(MM_SizeBottomRight)
Image Image::Cross() FCURSOR_(MM_Cross)
Image Image::Hand() FCURSOR_(MM_Hand)

// TODO: Missing kinds (sizers mostly)
// https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview/21786835#21786835

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"

#define XCURSOR(id) \
	if([NSCursor respondsToSelector:@selector(id)]) return [NSCursor performSelector:@selector(id)];

NSCursor *GetNSCursor(int kind)
{
	switch(kind) {
	case MM_SizeAll:
		XCURSOR(_moveCursor);
		break;
	case MM_SizeHorz:
		XCURSOR(_windowResizeEastWestCursor);
		break;
	case MM_SizeVert:
		XCURSOR(_windowResizeNorthSouthCursor);
		break;
	case MM_SizeTopLeft:
		XCURSOR(_windowResizeNorthWestSouthEastCursor);
		break;
	case MM_SizeTop:
		XCURSOR(_windowResizeNorthCursor);
		break;
	case MM_SizeTopRight:
		XCURSOR(_windowResizeNorthEastSouthWestCursor);
		break;
	case MM_SizeLeft:
		XCURSOR(_windowResizeWestCursor);
		break;
	case MM_SizeRight:
		XCURSOR(_windowResizeEastCursor);
		break;
	case MM_SizeBottomLeft:
		XCURSOR(_windowResizeNorthEastSouthWestCursor);
		break;
	case MM_SizeBottom:
		XCURSOR(_windowResizeSouthCursor);
		break;
	case MM_SizeBottomRight:
		XCURSOR(_windowResizeNorthWestSouthEastCursor);
		break;
	case MM_IBeam: return [NSCursor IBeamCursor];
	case MM_Cross: return [NSCursor crosshairCursor];
	case MM_Hand:  return [NSCursor pointingHandCursor];
	case MM_No:    return [NSCursor operationNotAllowedCursor];
	}
	return [NSCursor arrowCursor];
}

#pragma clang diagnostic pop

NSImage *CreateNSImage(const Image& img, CGImageRef cgimg)
{
	sCleanImageCache(img);
	Size isz = img.GetSize();
	double scale = 1.0 / DPI(1);
	NSSize size;
	size.width = scale * isz.cx;
	size.height = scale * isz.cy;
	NSImage *nsimage = [[NSImage alloc] initWithCGImage:cgimg size:size];
	cg_image_cache.Shrink(4 * 1024 * 768, 1000); // Cache must be after Paint because of PaintOnly!
	return nsimage;
}

struct NSImageSysData {
	Image            img;
	NSImage         *nsimage = NULL;
	
	void Init(const Image& img);
	~NSImageSysData();
};

void NSImageSysData::Init(const Image& img)
{
	ImageSysDataMaker m;
	m.img = img;
	nsimage = CreateNSImage(img, cg_image_cache.Get(m).cgimg);
}

NSImageSysData::~NSImageSysData()
{
	if(nsimage)
		[nsimage release];
}

struct NSImageSysDataMaker : LRUCache<NSImageSysData, int64>::Maker {
	Image img;

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

static LRUCache<NSImageSysData, int64> nsimage_cache;

NSImage *GetNSImage(const Image& img)
{
	NSImageSysDataMaker m;
	m.img = img;
	NSImage *nsimage = nsimage_cache.Get(m).nsimage;
	nsimage_cache.Shrink(4 * 1024*768, 1000);
	return nsimage;
}

void Ctrl::SetNSAppImage(const Image& img)
{
	ImageSysDataMaker m;
	m.img = img;
	ImageSysData& sd = cg_image_cache.Get(m);
	static CGImageRef cgimg;
	static NSImage   *nsimg;
	if(sd.cgimg != cgimg) {
		cgimg = sd.cgimg;
		if(nsimg)
			[nsimg release];
		nsimg = CreateNSImage(img, cgimg);
	}
	[NSApp setApplicationIconImage:nsimg];
}

void  Ctrl::SetMouseCursor(const Image& img)
{
	if(GetDragAndDropSource())
		return;
	int64 h = img.GetAuxData();
	if(h) {
		[GetNSCursor(h) set];
		return;
	}
	ImageSysDataMaker m;
	m.img = img;
	ImageSysData& sd = cg_image_cache.Get(m);
	static CGImageRef cgimg;
	static NSCursor  *cursor;
	if(sd.cgimg != cgimg) {
		cgimg = sd.cgimg;
		if(cursor)
			[cursor release];
		NSImage *nsimg = CreateNSImage(img, cgimg);
		double scale = 1.0 / DPI(1);
		Point p = img.GetHotSpot();
		NSPoint hot;
		hot.x = scale * p.x;
		hot.y = scale * p.y;
		cursor = [[NSCursor alloc] initWithImage:nsimg hotSpot:hot];
		[nsimg release];
	}
	[cursor set];
}

void ImageDraw::Init(int cx, int cy)
{
	ib.Create(cx, cy);
	Fill(~ib, RGBAZero(), ib.GetLength());

	static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

	SystemDraw::Init(CGBitmapContextCreateWithData(~ib, cx, cy, 8, cx * sizeof(RGBA),
	                                               colorSpace, kCGImageAlphaPremultipliedFirst,
	                                               NULL, NULL), NULL);
	CGContextTranslateCTM(cgHandle, 0, cy);

	if(Ctrl::GetDisplayScale() == 2) { // scale can only be 1 or 2...
		CGContextScaleCTM(cgHandle, 2, -2);
		CGContextTranslateCTM(cgHandle, 0, -cy / 2.0);
	}
	else
		CGContextScaleCTM(cgHandle, 1, -1);
}

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

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

Draw& ImageDraw::Alpha()
{
	if(!alpha)
		alpha.Create(ib.GetSize());
	return *alpha;
}

ImageDraw::~ImageDraw()
{
	CGContextRelease(cgHandle);
	handle = NULL; // avoid releasing invalid handle in ~SystemDraw
}

Image ImageDraw::GetStraight() const
{
	ImageBuffer ib2(ib.GetSize());
	memcpy_t(~ib2, ~ib, ib.GetLength());
	if(alpha) {
		RGBA *e = ib2.End();
		RGBA *t = ib2;
		const RGBA *s = alpha->ib;
		while(t < e) {
			t->a = s->r;
			s++;
			t++;
		}
	}
	return Image(ib2);
}

ImageDraw::operator Image() const
{
	if(alpha)
		return Premultiply(GetStraight());
	ImageBuffer ib2(ib.GetSize());
	memcpy_t(~ib2, ~ib, ib.GetLength());
	return Image(ib2);
}

Image GetIconForFile(const char *path)
{
	ImageDraw iw(DPI(16, 16));

	CGContextRef cg = (CGContextRef) iw.GetCGHandle();

	
	NSImage *image;
	CFRef<CFStringRef> fexe = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
	image = [[NSWorkspace sharedWorkspace]iconForFile:(__bridge NSString *)~fexe];
/*
	}
	else { // not used any more
		CFRef<CFStringRef> fext = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
		image = *ext == '*' ? [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)]
	                        : [[NSWorkspace sharedWorkspace]iconForFileType:(__bridge NSString *)~fext];
	}
*/
    NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES];
    NSGraphicsContext* cgc = [NSGraphicsContext currentContext];
    [NSGraphicsContext setCurrentContext:gc];
    [image drawInRect:NSMakeRect(0, 0, DPI(16), DPI(16))];
	[NSGraphicsContext setCurrentContext:cgc];
	
	return iw;
}

};

#endif
