/*
 *	clip_macosx64.mm - Clipboard handling, MacOS X (Pasteboard Manager) implementation
 *
 *	(C) 2012 Jean-Pierre Stierlin
 *	(C) 2012 Alexei Svitkine
 *	(C) 2012 Charles Srstka
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "sysdeps.h"
#define _UINT64
#import <Cocoa/Cocoa.h>
#include <ApplicationServices/ApplicationServices.h>

#include "clip.h"
#include "main.h"
#include "cpu_emulation.h"
#include "emul_op.h"
#include "autorelease.h"
#include "pict.h"

#define DEBUG 0
#include "debug.h"

#ifndef FOURCC
#define FOURCC(a,b,c,d) (((uint32)(a) << 24) | ((uint32)(b) << 16) | ((uint32)(c) << 8) | (uint32)(d))
#endif

#define TYPE_PICT FOURCC('P','I','C','T')
#define TYPE_TEXT FOURCC('T','E','X','T')
#define TYPE_STYL FOURCC('s','t','y','l')
#define TYPE_UTXT FOURCC('u','t','x','t')
#define TYPE_UT16 FOURCC('u','t','1','6')
#define TYPE_USTL FOURCC('u','s','t','l')
#define TYPE_MOOV FOURCC('m','o','o','v')
#define TYPE_SND  FOURCC('s','n','d',' ')
#define TYPE_ICNS FOURCC('i','c','n','s')

static NSPasteboard *g_pboard;
static NSInteger g_pb_change_count = 0;

// Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the MacOS X side
static bool we_put_this_data = false;

static bool should_clear = false;

static NSMutableDictionary *g_macScrap;

// font face types

enum {
	FONT_FACE_PLAIN = 0,
	FONT_FACE_BOLD = 1,
	FONT_FACE_ITALIC = 2,
	FONT_FACE_UNDERLINE = 4,
	FONT_FACE_OUTLINE = 8,
	FONT_FACE_SHADOW = 16,
	FONT_FACE_CONDENSED = 32,
	FONT_FACE_EXTENDED = 64
};

// Script Manager constants

#define smRoman 			0
#define smMacSysScript		18
#define smMacRegionCode		40

static NSString *UTIForFlavor(uint32_t type)
{
	switch (type) {
		case TYPE_MOOV:
			return (NSString *)kUTTypeQuickTimeMovie;
		case TYPE_SND:
			return (NSString *)kUTTypeAudio;
		case TYPE_ICNS:
			return (NSString *)kUTTypeAppleICNS;
		default: {
			CFStringRef typeString = UTCreateStringForOSType(type);
			NSString *uti = (NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, NULL);

			CFRelease(typeString);

			if (uti == nil || [uti hasPrefix:@"dyn."]) {
				// The docs threaten that this may stop working at some unspecified point in the future.
				// However, it seems to work on Lion and Mountain Lion, and there's no other way to do this
				// that I can see. Most likely, whichever release eventually breaks this will probably also
				// drop support for the 32-bit applications which typically use these 32-bit scrap types anyway,
				// making it irrelevant. When this happens, we should include a version check for the version of
				// OS X that dropped this support, and leave uti alone in that case.

				[uti release];
				uti = [[NSString alloc] initWithFormat:@"CorePasteboardFlavorType 0x%08x", type];
			}

			return [uti autorelease];
		}
	}
}

static uint32_t FlavorForUTI(NSString *uti)
{
	CFStringRef typeTag = UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassOSType);

	if (!typeTag)
		return 0;

	uint32_t type = UTGetOSTypeFromString(typeTag);

	CFRelease(typeTag);

	return type;
}

/*
 *	Get current system script encoding on Mac
 */

static int GetMacScriptManagerVariable(uint16_t varID)
{
	int ret = -1;
	M68kRegisters r;
	static uint8_t proc[] = {
		0x59, 0x4f,							// subq.w	 #4,sp
		0x3f, 0x3c, 0x00, 0x00,				// move.w	 #varID,-(sp)
		0x2f, 0x3c, 0x84, 0x02, 0x00, 0x08, // move.l	 #-2080243704,-(sp)
		0xa8, 0xb5,							// ScriptUtil()
		0x20, 0x1f,							// move.l	 (a7)+,d0
		M68K_RTS >> 8, M68K_RTS & 0xff
	};
	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];
	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		WriteMacInt16(proc_area + 4, varID);
		Execute68k(proc_area, &r);
		ret = r.d[0];
		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}
	return ret;
}

static ScriptCode ScriptNumberForFontID(int16_t fontID)
{
	ScriptCode ret = -1;
	M68kRegisters r;
	static uint8_t proc[] = {
		0x55, 0x4f,							// subq.w	#2,sp
		0x3f, 0x3c, 0x00, 0x00,				// move.w	#fontID,-(sp)
		0x2f, 0x3c, 0x82, 0x02, 0x00, 0x06, // move.l	#-2113798138,-(sp)
		0xa8, 0xb5,							// ScriptUtil()
		0x30, 0x1f,							// move.w	(sp)+,d0
		M68K_RTS >> 8, M68K_RTS & 0xff
	};
	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];
	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		WriteMacInt16(proc_area + 4, fontID);
		Execute68k(proc_area, &r);
		ret = r.d[0];
		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}
	return ret;
}

/*
 *  Get Mac's default text encoding
 */

static TextEncoding MacDefaultTextEncoding()
{
	int script = GetMacScriptManagerVariable(smMacSysScript);
	int region = GetMacScriptManagerVariable(smMacRegionCode);
	TextEncoding encoding;

	if (UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, region, NULL, &encoding))
		encoding = kTextEncodingMacRoman;

	return encoding;
}

static NSData *ConvertToMacTextEncoding(NSAttributedString *aStr, NSArray **styleAndScriptRuns)
{
	NSUInteger length = [aStr length];

	NSMutableArray *styleRuns = [NSMutableArray array];

	for (NSUInteger index = 0; index < length;) {
		NSRange attrRange;
		NSDictionary *attrs = [aStr attributesAtIndex:index effectiveRange:&attrRange];

		[styleRuns addObject:[NSDictionary dictionaryWithObjectsAndKeys:
							  [NSNumber numberWithUnsignedInteger:index], @"offset",
							  attrs, @"attributes", nil]];

		index = NSMaxRange(attrRange);
	}

	UnicodeToTextRunInfo info;

	OSStatus err = CreateUnicodeToTextRunInfoByScriptCode(0, NULL, &info);

	if (err != noErr) {
		if (styleAndScriptRuns)
			*styleAndScriptRuns = styleRuns;

		return [[aStr string] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];
	}

	unichar chars[length];

	[[aStr string] getCharacters:chars range:NSMakeRange(0, length)];

	ByteCount unicodeLength = length * sizeof(unichar);
	ByteCount bufLen = unicodeLength * 2;
	uint8_t buf[bufLen];
	ByteCount bytesRead;

	ItemCount scriptRunCount = 1601; // max number of allowed style changes
	ScriptCodeRun scriptRuns[scriptRunCount];

	ItemCount inOffsetCount = [styleRuns count];
	ByteOffset inOffsets[inOffsetCount];

	if (inOffsetCount) {
		for (NSUInteger i = 0; i < inOffsetCount; i++) {
			NSDictionary *eachRun = [styleRuns objectAtIndex:i];

			inOffsets[i] = [[eachRun objectForKey:@"offset"] unsignedLongValue] * 2;
		}
	}

	ItemCount offsetCount;
	ByteOffset offsets[inOffsetCount];

	err = ConvertFromUnicodeToScriptCodeRun(info, unicodeLength, chars,
											kUnicodeTextRunMask | kUnicodeUseFallbacksMask | kUnicodeLooseMappingsMask,
											inOffsetCount, inOffsets, &offsetCount, offsets,
											bufLen, &bytesRead, &bufLen, buf,
											scriptRunCount, &scriptRunCount, scriptRuns);

	if (err != noErr) {
		if (styleAndScriptRuns)
			*styleAndScriptRuns = styleRuns;

		return [[aStr string] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];
	}

	if (styleAndScriptRuns) {
		NSMutableArray *runs = [NSMutableArray array];
		NSUInteger currentStyleRun = 0;
		NSUInteger currentScriptRun = 0;

		for (NSUInteger currentOffset = 0; currentOffset < bufLen;) {
			ScriptCodeRun scriptRun = scriptRuns[currentScriptRun];
			NSDictionary *attrs = [[styleRuns objectAtIndex:currentStyleRun] objectForKey:@"attributes"];

			NSUInteger nextStyleOffset = (currentStyleRun < offsetCount - 1) ? offsets[currentStyleRun + 1] : bufLen;
			NSUInteger nextScriptOffset = (currentScriptRun < scriptRunCount - 1) ? scriptRuns[currentScriptRun + 1].offset : bufLen;

			[runs addObject:[NSDictionary dictionaryWithObjectsAndKeys:
							 [NSNumber numberWithUnsignedInteger:currentOffset], @"offset",
							 [NSNumber numberWithShort:scriptRun.script], @"script",
							 attrs, @"attributes", nil]];

			if (nextStyleOffset == nextScriptOffset) {
				currentStyleRun++;
				currentScriptRun++;
				currentOffset = nextStyleOffset;
			} else if (nextStyleOffset < nextScriptOffset) {
				currentStyleRun++;
				currentOffset = nextStyleOffset;
			} else {
				currentScriptRun++;
				currentOffset = nextScriptOffset;
			}
		}

		*styleAndScriptRuns = runs;
	}

	return [NSData dataWithBytes:buf length:bufLen];
}

/*
 *  Count all Mac font IDs on the system
 */

static NSUInteger CountMacFonts()
{
	M68kRegisters r;
	static uint8_t proc[] = {
		0x55, 0x4f,						// subq.w	#2,sp
		0x2f, 0x3c, 'F', 'O', 'N', 'D',	// move.l	#'FOND',-(sp)
		0xa9, 0x9c,						// CountResources()
		0x30, 0x1f,						// move.w (sp)+,D0
		M68K_RTS >> 8, M68K_RTS & 0xff
	};
	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];
	int16_t fontCount = 0;

	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		Execute68k(proc_area, &r);

		fontCount = r.d[0];

		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}

	if (fontCount < 0) {
		fontCount = 0;
	}

	return fontCount;
}

/*
 *  Get Mac font ID at index
 */

static int16_t MacFontIDAtIndex(NSUInteger index)
{
	M68kRegisters r;
	static uint8_t get_res_handle_proc[] = {
		0x42, 0x27,						// clr.b	-(sp)
		0xa9, 0x9b,						// SetResLoad()
		0x59, 0x4f,						// subq.w	#4,sp
		0x2f, 0x3c, 'F', 'O', 'N', 'D',	// move.l	#'FOND',-(sp)
		0x3f, 0x3c,	0, 0,				// move.w	#index,-(sp)
		0xa9, 0x9d,						// GetIndResource()
		0x26, 0x5f,						// movea.l	(sp)+,A3
		0x1f, 0x3c, 0x00, 0x01,			// move.b	#1,-(sp)
		0xa9, 0x9b,						// SetResLoad()
		M68K_RTS >> 8, M68K_RTS & 0xff
	};
	r.d[0] = sizeof(get_res_handle_proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];

	uint32_t res_handle = 0;
	int16_t fontID = 0;

	if (proc_area) {
		Host2Mac_memcpy(proc_area, get_res_handle_proc, sizeof(get_res_handle_proc));
		WriteMacInt16(proc_area + 14, (uint16_t)(index + 1));

		Execute68k(proc_area, &r);

		res_handle = r.a[3];

		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr()
	}

	if (res_handle) {
		static uint8_t get_info_proc[] = {
			0x2f, 0x0a,						// move.l	A2,-(sp)
			0x2f, 0x0b,						// move.l	A3,-(sp)
			0x42, 0xa7,						// clr.l	-(sp)
			0x42, 0xa7,						// clr.l	-(sp)
			0xa9, 0xa8,						// GetResInfo()
			0x2f, 0x0a,						// move.l	A2,-(sp)
			0xa9, 0xa3,						// ReleaseResource()
			M68K_RTS >> 8, M68K_RTS & 0xff,
			0, 0
		};

		r.d[0] = sizeof(get_info_proc);
		Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
		proc_area = r.a[0];

		if (proc_area) {
			Host2Mac_memcpy(proc_area, get_info_proc, sizeof(get_info_proc));
			r.a[2] = res_handle;
			r.a[3] = proc_area + 16;

			Execute68k(proc_area, &r);

			fontID = ReadMacInt16(proc_area + 16);

			r.a[0] = proc_area;
			Execute68kTrap(0xa01f, &r);	// DisposePtr()
		}
	}

	return fontID;
}

/*
 *  List all font IDs on the system
 */

static NSArray *ListMacFonts()
{
	NSUInteger fontCount = CountMacFonts();
	NSMutableArray *fontIDs = [NSMutableArray array];

	for (NSUInteger i = 0; i < fontCount; i++) {
		int16_t eachFontID = MacFontIDAtIndex(i);

		[fontIDs addObject:[NSNumber numberWithShort:eachFontID]];
	}

	return fontIDs;
}

/*
 *  List all font IDs having a certain script
 */

static NSArray *ListMacFontsForScript(ScriptCode script)
{
	NSMutableArray *fontIDs = [NSMutableArray array];

	for (NSNumber *eachFontIDNum in ListMacFonts()) {
		if (ScriptNumberForFontID([eachFontIDNum shortValue]) == script)
			[fontIDs addObject:eachFontIDNum];
	}

	return fontIDs;
}

/*
 *  Convert Mac font ID to font name
 */

static NSString *FontNameFromFontID(int16_t fontID)
{
	M68kRegisters r;
	r.d[0] = 256;					// Str255: 255 characters + length byte
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t name_area = r.a[0];

	if (!name_area)
		return nil;

	uint8_t proc[] = {
		0x3f, 0x3c, 0, 0,	// move.w	#fontID,-(sp)
		0x2f, 0x0a,			// move.l	A2,-(sp)
		0xa8, 0xff,			// GetFontName()
		M68K_RTS >> 8, M68K_RTS & 0xff
	};

	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];

	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		WriteMacInt16(proc_area + 2, fontID);
		r.a[2] = name_area;
		Execute68k(proc_area, &r);

		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}

	uint8_t * const namePtr = Mac2HostAddr(name_area);

	NSString *name = (NSString *)CFStringCreateWithPascalString(kCFAllocatorDefault, namePtr, kCFStringEncodingMacRoman);

	r.a[0] = name_area;
	Execute68kTrap(0xa01f, &r);			// DisposePtr

	return [name autorelease];
}

/*
 *  Convert font name to Mac font ID
 */

static int16_t FontIDFromFontName(NSString *fontName)
{
	M68kRegisters r;
	r.d[0] = 256;					// Str255: 255 characters + length byte
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t name_area = r.a[0];

	if (!name_area)
		return 0;

	uint8_t * const namePtr = Mac2HostAddr(name_area);

	CFStringGetPascalString((CFStringRef)fontName, namePtr, 256, kCFStringEncodingMacRoman);

	uint8_t proc[] = {
		0x2f, 0x0a,			// move.l	A2,-(sp)
		0x2f, 0x0b,			// move.l	A3,-(sp)
		0xa9, 0x00,			// GetFNum()
		M68K_RTS >> 8, M68K_RTS & 0xff,
		0, 0
	};

	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];
	int16_t fontID = 0;

	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		r.a[2] = name_area;
		r.a[3] = proc_area + 8;

		Execute68k(proc_area, &r);

		fontID = ReadMacInt16(proc_area + 8);

		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}

	r.a[0] = name_area;
	Execute68kTrap(0xa01f, &r);			// DisposePtr

	return fontID;
}

/*
 *  Get font ID in desired script if possible; otherwise, try to get some font in the desired script.
 */

static int16_t FontIDFromFontNameAndScript(NSString *fontName, ScriptCode script)
{
	int16_t fontID = FontIDFromFontName(fontName);

	if (ScriptNumberForFontID(fontID) == script)
		return fontID;

	NSArray *fontIDs = ListMacFontsForScript(script);

	if ([fontIDs count] == 0)
		return fontID; // no fonts are going to work; might as well return the original one

	if (fontName) {
		// look for a localized version of our font; e.g. "Helvetica CE" if our font is Helvetica
		for (NSNumber *eachFontIDNum in fontIDs) {
			int16_t eachFontID = [eachFontIDNum shortValue];

			if ([FontNameFromFontID(eachFontID) hasPrefix:fontName])
				return eachFontID;
		}
	}

	// Give up and just return a font that will work
	return [[fontIDs objectAtIndex:0] shortValue];
}

/*
 *  Convert Mac TEXT/styl to attributed string
 */

static NSAttributedString *AttributedStringFromMacTEXTAndStyl(NSData *textData, NSData *stylData)
{
	NSMutableAttributedString *aStr = [[[NSMutableAttributedString alloc] init] autorelease];

	if (aStr == nil)
		return nil;

	const uint8_t *bytes = (const uint8_t *)[stylData bytes];
	NSUInteger length = [stylData length];

	if (length < 2)
		return nil;

	uint16_t elements = CFSwapInt16BigToHost(*(uint16_t *)bytes);
	const NSUInteger elementSize = 20;

	if (length < elements * elementSize)
		return nil;

	NSUInteger cursor = 2;

	for (NSUInteger i = 0; i < elements; i++) AUTORELEASE_POOL {
		int32_t startChar = CFSwapInt32BigToHost(*(int32_t *)(bytes + cursor)); cursor += 4;
		int16_t height __attribute__((unused)) = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		int16_t ascent __attribute__((unused)) = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		int16_t fontID = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		uint8_t face = bytes[cursor]; cursor += 2;
		int16_t size = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		uint16_t red = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		uint16_t green = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;
		uint16_t blue = CFSwapInt16BigToHost(*(int16_t *)&bytes[cursor]); cursor += 2;

		int32_t nextChar;

		if (i + 1 == elements)
			nextChar = [textData length];
		else
			nextChar = CFSwapInt32BigToHost(*(int32_t *)(bytes + cursor));

		NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
		NSColor *color = [NSColor colorWithDeviceRed:(CGFloat)red / 65535.0 green:(CGFloat)green / 65535.0 blue:(CGFloat)blue / 65535.0 alpha:1.0];
		NSFont *font;
		TextEncoding encoding;

		if (fontID == 0) {			// System font
			CGFloat fontSize = (size == 0) ? [NSFont systemFontSize] : (CGFloat)size;
			font = [NSFont systemFontOfSize:fontSize];
		} else if (fontID == 1) {	// Application font
			font = [NSFont userFontOfSize:(CGFloat)size];
		} else {
			NSString *fontName = FontNameFromFontID(fontID);
			font = [NSFont fontWithName:fontName size:(CGFloat)size];

			if (font == nil) {
				// Convert localized variants of fonts; e.g. "Helvetica CE" to "Helvetica"

				NSRange wsRange = [fontName rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:NSBackwardsSearch];

				if (wsRange.length) {
					fontName = [fontName substringToIndex:wsRange.location];
					font = [NSFont fontWithName:fontName size:(CGFloat)size];
				}
			}
		}

		if (font == nil)
			font = [NSFont userFontOfSize:(CGFloat)size];

		if (UpgradeScriptInfoToTextEncoding(ScriptNumberForFontID(fontID), kTextLanguageDontCare, kTextRegionDontCare, NULL, &encoding))
			encoding = MacDefaultTextEncoding();

		NSFontManager *fm = [NSFontManager sharedFontManager];

		if (face & FONT_FACE_BOLD)
			font = [fm convertFont:font toHaveTrait:NSBoldFontMask];

		if (face & FONT_FACE_ITALIC)
			font = [fm convertFont:font toHaveTrait:NSItalicFontMask];

		if (face & FONT_FACE_CONDENSED)
			font = [fm convertFont:font toHaveTrait:NSCondensedFontMask];

		if (face & FONT_FACE_EXTENDED)
			font = [fm convertFont:font toHaveTrait:NSExpandedFontMask];

		[attrs setObject:font forKey:NSFontAttributeName];

		if (face & FONT_FACE_UNDERLINE)
			[attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];

		if (face & FONT_FACE_OUTLINE) {
			[attrs setObject:color forKey:NSStrokeColorAttributeName];
			[attrs setObject:[NSNumber numberWithInteger:3] forKey:NSStrokeWidthAttributeName];
		}

		if (face & FONT_FACE_SHADOW) {
			NSShadow *shadow = [[NSShadow alloc] init];
			NSColor *shadowColor = [NSColor colorWithDeviceRed:(CGFloat)red / 65535.0 green:(CGFloat)green / 65535.0 blue:(CGFloat)blue / 65535.0 alpha:0.5];

			[shadow setShadowColor:shadowColor];
			[shadow setShadowOffset:NSMakeSize(2, -2.0)];

			[attrs setObject:shadow forKey:NSShadowAttributeName];

			[shadow release];
		}

		[attrs setObject:color forKey:NSForegroundColorAttributeName];

		NSData *partialData = [textData subdataWithRange:NSMakeRange(startChar, nextChar - startChar)];
		NSString *partialString = [[NSString alloc] initWithData:partialData encoding:CFStringConvertEncodingToNSStringEncoding(encoding)];

		if (partialString) {
			NSAttributedString *partialAttribString = [[NSAttributedString alloc] initWithString:partialString attributes:attrs];

			[aStr appendAttributedString:partialAttribString];

			[partialAttribString release];
		}

		[partialString release];
		[attrs release];
	}

	return aStr;
}

/*
 *  Append styl data for one text run
 */

static void AppendStylRunData(NSMutableData *stylData, NSDictionary *attrs, ScriptCode script)
{
	NSFontManager *fontManager = [NSFontManager sharedFontManager];
	NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];

	NSFont *font = [attrs objectForKey:NSFontAttributeName];
	NSColor *color = [[attrs objectForKey:NSForegroundColorAttributeName] colorUsingColorSpaceName:NSDeviceRGBColorSpace device:nil];
	NSFontTraitMask traits = [fontManager traitsOfFont:font];
	NSNumber *underlineStyle = [attrs objectForKey:NSUnderlineStyleAttributeName];
	NSNumber *strokeWidth = [attrs objectForKey:NSStrokeWidthAttributeName];
	NSShadow *shadow = [attrs objectForKey:NSShadowAttributeName];

	int16_t hostFontID = FontIDFromFontNameAndScript([font familyName], script);

	if (hostFontID == 0) {
		hostFontID = [font isFixedPitch] ? 4 /* Monaco */ : 1 /* Application font */;
	}

	int16_t height = CFSwapInt16HostToBig((int16_t)rint([layoutManager defaultLineHeightForFont:font]));
	int16_t ascent = CFSwapInt16HostToBig((int16_t)rint([font ascender]));
	int16_t fontID = CFSwapInt16HostToBig(hostFontID);
	uint8_t face = 0;
	int16_t size = CFSwapInt16HostToBig((int16_t)rint([font pointSize]));
	uint16_t red = CFSwapInt16HostToBig((int16_t)rint([color redComponent] * 65535.0));
	uint16_t green = CFSwapInt16HostToBig((int16_t)rint([color greenComponent] * 65535.0));
	uint16_t blue = CFSwapInt16HostToBig((int16_t)rint([color blueComponent] * 65535.0));

	if (traits & NSBoldFontMask) {
		face |= FONT_FACE_BOLD;
	}

	if (traits & NSItalicFontMask) {
		face |= FONT_FACE_ITALIC;
	}

	if (traits & NSCondensedFontMask) {
		face |= FONT_FACE_CONDENSED;
	}

	if (traits & NSExpandedFontMask) {
		face |= FONT_FACE_EXTENDED;
	}

	if (underlineStyle && [underlineStyle integerValue] != NSUnderlineStyleNone) {
		face |= FONT_FACE_UNDERLINE;
	}

	if (strokeWidth && [strokeWidth doubleValue] > 0.0) {
		face |= FONT_FACE_OUTLINE;
	}

	if (shadow) {
		face |= FONT_FACE_SHADOW;
	}

	[stylData appendBytes:&height length:2];
	[stylData appendBytes:&ascent length:2];
	[stylData appendBytes:&fontID length:2];
	[stylData appendBytes:&face length:1];
	[stylData increaseLengthBy:1];
	[stylData appendBytes:&size length:2];
	[stylData appendBytes:&red length:2];
	[stylData appendBytes:&green length:2];
	[stylData appendBytes:&blue length:2];

	[layoutManager release];
}

/*
 *  Convert attributed string to TEXT/styl
 */

static NSData *ConvertToMacTEXTAndStyl(NSAttributedString *aStr, NSData **outStylData)
{
	// Limitations imposed by the Mac TextEdit system.
	const NSUInteger charLimit = 32 * 1024;
	const NSUInteger elementLimit = 1601;

	NSUInteger length = [aStr length];

	if (length > charLimit) {
		aStr = [aStr attributedSubstringFromRange:NSMakeRange(0, charLimit)];
	}

	NSArray *runs = nil;

	NSData *textData = ConvertToMacTextEncoding(aStr, &runs);

	NSMutableData *stylData = [NSMutableData dataWithLength:2]; // number of styles to be filled in at the end

	NSUInteger elements = 0;

	for (NSDictionary *eachRun in runs) {
		if (elements >= elementLimit)
			break;

		NSUInteger offset = [[eachRun objectForKey:@"offset"] unsignedIntegerValue];
		ScriptCode script = [[eachRun objectForKey:@"script"] shortValue];
		NSDictionary *attrs = [eachRun objectForKey:@"attributes"];

		if (![attrs count]) continue;
		
		int32_t startChar = CFSwapInt32HostToBig((int32_t)offset);
		[stylData appendBytes:&startChar length:4];

		AppendStylRunData(stylData, attrs, script);

		elements++;
	}

	uint16_t bigEndianElements = CFSwapInt16HostToBig((uint16_t)elements);

	[stylData replaceBytesInRange:NSMakeRange(0, 2) withBytes:&bigEndianElements length:2];

	if (outStylData)
		*outStylData = stylData;

	return textData;
}

/*
 *  Get data of a particular flavor from the pasteboard
 */

static NSData *DataFromPasteboard(NSPasteboard *pboard, NSString *flavor)
{
	return [pboard dataForType:flavor];
}

/*
 *  Convert Mac TEXT/styl to RTF
 */

static void WriteMacTEXTAndStylToPasteboard(NSPasteboard *pboard, NSData *textData, NSData *stylData)
{
	NSMutableAttributedString *aStr = [AttributedStringFromMacTEXTAndStyl(textData, stylData) mutableCopy];

	if (!aStr) {
		NSString *string = [[NSString alloc] initWithData:textData encoding:CFStringConvertEncodingToNSStringEncoding(MacDefaultTextEncoding())];

		if (!string)
			return;

		aStr = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];

		[string release];
	}

	// fix line endings
	[[aStr mutableString] replaceOccurrencesOfString:@"\r" withString:@"\n" options:NSLiteralSearch range:NSMakeRange(0, [aStr length])];

	[pboard writeObjects:[NSArray arrayWithObject:aStr]];

	[aStr release];
}

/*
 *  Convert RTF to Mac TEXT/styl
 */

static NSData *MacTEXTAndStylDataFromPasteboard(NSPasteboard *pboard, NSData **outStylData)
{
	NSMutableAttributedString *aStr;

	NSArray *objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSAttributedString class]] options:nil];

	if ([objs count]) {
		aStr = [[objs objectAtIndex:0] mutableCopy];
	} else {
		objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil];

		if (![objs count])
			return nil;

		aStr = [[NSMutableAttributedString alloc] initWithString:[objs objectAtIndex:0]];
	}

	// fix line endings
	[[aStr mutableString] replaceOccurrencesOfString:@"\n" withString:@"\r" options:NSLiteralSearch range:NSMakeRange(0, [[aStr mutableString] length])];

	NSData *stylData = nil;
	NSData *textData = ConvertToMacTEXTAndStyl(aStr, &stylData);

	[aStr release];

	if (outStylData)
		*outStylData = stylData;

	return textData;
}

/*
 *	Initialization
 */

void ClipInit(void)
{
	g_pboard = [[NSPasteboard generalPasteboard] retain];
	if (!g_pboard) {
		D(bug("could not create Pasteboard\n"));
	}

	g_macScrap = [[NSMutableDictionary alloc] init];
}


/*
 *	Deinitialization
 */

void ClipExit(void)
{
	[g_pboard release];
	g_pboard = nil;

	[g_macScrap release];
	g_macScrap = nil;
}

/*
 *  Convert an NSImage to PICT format.
 */

static NSData *ConvertImageToPICT(NSImage *image) {
	if ([[image representations] count] == 0) {
		return nil;
	}

	NSImageRep *rep = [[image representations] objectAtIndex:0];
	NSUInteger width;
	NSUInteger height;

	if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
		width = [rep pixelsWide];
		height = [rep pixelsHigh];
	} else {
		width = lrint([image size].width);
		height = lrint([image size].height);
	}

	// create a new bitmap image rep in our desired format, following the advice here:
	// https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html#X10_6Notes

	NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
																	   pixelsWide:width
																	   pixelsHigh:height
																	bitsPerSample:8
																  samplesPerPixel:4
																		 hasAlpha:YES
																		 isPlanar:NO
																   colorSpaceName:NSCalibratedRGBColorSpace
																	  bytesPerRow:width * 4
																	 bitsPerPixel:32];

	[NSGraphicsContext saveGraphicsState];
	[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
	[rep draw];
	[NSGraphicsContext restoreGraphicsState];

	unsigned char *rgba = [bitmap bitmapData];

	long bufSize = ConvertRGBAToPICT(NULL, 0, rgba, width, height);

	NSData *pictData = nil;

	if (bufSize > 0) {
		uint8_t *buf = (uint8_t *)malloc(bufSize);

		long pictSize = ConvertRGBAToPICT(buf, bufSize, rgba, width, height);

		if (pictSize > 0)
			pictData = [NSData dataWithBytes:buf length:pictSize];

		free(buf);
	}

	[bitmap release];

	return pictData;
}

/*
 *  Convert any images that may be on the clipboard to PICT format if possible.
 */

static NSData *MacPICTDataFromPasteboard(NSPasteboard *pboard)
{
	// check if there's any PICT data on the pasteboard
	NSData *pictData = DataFromPasteboard(pboard, (NSString *)kUTTypePICT);

	if (pictData)
		return pictData;

	// now check to see if any images on the pasteboard have PICT representations
	NSArray *objs = [pboard readObjectsForClasses:[NSArray arrayWithObject:[NSImage class]] options:nil];

	for (NSImage *eachImage in objs) {
		for (NSImageRep *eachRep in [eachImage representations]) {

			if ([eachRep isKindOfClass:[NSPICTImageRep class]])
				return [(NSPICTImageRep *)eachRep PICTRepresentation];
		}
	}

	// Give up and perform the conversion ourselves
	if ([objs count])
		return ConvertImageToPICT([objs objectAtIndex:0]);

	// If none of that worked, sorry, we're out of options
	return nil;
}

/*
 *  Zero Mac clipboard
 */

static void ZeroMacClipboard()
{
	D(bug("Zeroing Mac clipboard\n"));
	M68kRegisters r;
	static uint8_t proc[] = {
		0x59, 0x8f,					// subq.l	#4,sp
		0xa9, 0xfc,					// ZeroScrap()
		0x58, 0x8f,					// addq.l	#4,sp
		M68K_RTS >> 8, M68K_RTS & 0xff
	};
	r.d[0] = sizeof(proc);
	Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
	uint32_t proc_area = r.a[0];

	if (proc_area) {
		Host2Mac_memcpy(proc_area, proc, sizeof(proc));
		Execute68k(proc_area, &r);

		[g_macScrap removeAllObjects];

		r.a[0] = proc_area;
		Execute68kTrap(0xa01f, &r); // DisposePtr
	}
}

/*
 *  Write data to Mac clipboard
 */

static void WriteDataToMacClipboard(NSData *pbData, uint32_t type)
{
	D(bug("Writing data %s to Mac clipboard with type '%c%c%c%c'\n", [[pbData description] UTF8String],
		  (type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff, type & 0xff));

	if ([pbData length] == 0)
		return;

	NSNumber *typeNum = [NSNumber numberWithInteger:type];

	if ([g_macScrap objectForKey:typeNum]) {
		// the classic Mac OS can't have more than one object of the same type on the clipboard
		return;
	}

	// Allocate space for new scrap in MacOS side
	M68kRegisters r;
	r.d[0] = [pbData length];
	Execute68kTrap(0xa71e, &r);				// NewPtrSysClear()
	uint32_t scrap_area = r.a[0];

	// Get the native clipboard data
	if (scrap_area) {
		uint8_t * const data = Mac2HostAddr(scrap_area);

		memcpy(data, [pbData bytes], [pbData length]);

		// Add new data to clipboard
		static uint8_t proc[] = {
			0x59, 0x8f,					// subq.l	#4,sp
			0x2f, 0x3c, 0, 0, 0, 0,		// move.l	#length,-(sp)
			0x2f, 0x3c, 0, 0, 0, 0,		// move.l	#type,-(sp)
			0x2f, 0x3c, 0, 0, 0, 0,		// move.l	#outbuf,-(sp)
			0xa9, 0xfe,					// PutScrap()
			0x58, 0x8f,					// addq.l	#4,sp
			M68K_RTS >> 8, M68K_RTS & 0xff
		};
		r.d[0] = sizeof(proc);
		Execute68kTrap(0xa71e, &r);		// NewPtrSysClear()
		uint32_t proc_area = r.a[0];

		if (proc_area) {
			Host2Mac_memcpy(proc_area, proc, sizeof(proc));
			WriteMacInt32(proc_area + 4, [pbData length]);
			WriteMacInt32(proc_area + 10, type);
			WriteMacInt32(proc_area + 16, scrap_area);
			we_put_this_data = true;
			Execute68k(proc_area, &r);

			r.a[0] = proc_area;
			Execute68kTrap(0xa01f, &r); // DisposePtr

			[g_macScrap setObject:pbData forKey:typeNum];
		}

		r.a[0] = scrap_area;
		Execute68kTrap(0xa01f, &r);			// DisposePtr
	}
}

/*
 *  Take all the data on host pasteboard and convert it to something the Mac understands if possible
 */

static void ConvertHostPasteboardToMacScrap()
{
	D(bug("ConvertHostPasteboardToMacScrap\n"));

	ZeroMacClipboard();

	NSData *stylData = nil;
	NSData *textData = MacTEXTAndStylDataFromPasteboard(g_pboard, &stylData);

	if (textData) {
		if (stylData && [stylData length] > 2)
			WriteDataToMacClipboard(stylData, TYPE_STYL);

		WriteDataToMacClipboard(textData, TYPE_TEXT);
	}

	NSData *pictData = MacPICTDataFromPasteboard(g_pboard);

	if (pictData)
		WriteDataToMacClipboard(pictData, TYPE_PICT);

	for (NSString *eachType in [g_pboard types]) {
		if (UTTypeConformsTo((CFStringRef)eachType, kUTTypeText)) {
			// text types are already handled
			continue;
		}

		if (UTTypeConformsTo((CFStringRef)eachType, kUTTypeImage)) {
			// image types are already handled
			continue;
		}

		uint32_t type = FlavorForUTI(eachType);

		// skip styl and ustl as well; those fall under text, which is handled already
		if (!type || type == TYPE_STYL || type == TYPE_USTL)
			continue;

		WriteDataToMacClipboard(DataFromPasteboard(g_pboard, eachType), type);
	}
}

/*
 *  Take all the data on the Mac clipbord and convert it to something the host pasteboard understands if possible
 */

static void ConvertMacScrapToHostPasteboard()
{
	D(bug("ConvertMacScrapToHostPasteboard\n"));

	BOOL wroteText = NO;

	[g_pboard clearContents];

	for (NSNumber *eachTypeNum in g_macScrap) AUTORELEASE_POOL {
		uint32_t eachType = [eachTypeNum integerValue];

		if (eachType == TYPE_TEXT || eachType == TYPE_STYL || eachType == TYPE_UTXT || eachType == TYPE_UT16 || eachType == TYPE_USTL) {
			if (wroteText)
				continue;

			NSData *textData;
			NSData *stylData;

			textData = [g_macScrap objectForKey:[NSNumber numberWithInteger:TYPE_TEXT]];
			stylData = [g_macScrap objectForKey:[NSNumber numberWithInteger:TYPE_STYL]];

			if (textData) {
				WriteMacTEXTAndStylToPasteboard(g_pboard, textData, stylData);
				wroteText = YES;
			}

			// sometime, it might be interesting to write a converter for utxt/ustl if possible

			continue;
		}

		NSData *pbData = [g_macScrap objectForKey:eachTypeNum];

		if (pbData) {
			NSString *typeStr = UTIForFlavor(eachType);

			if (!typeStr)
				continue;

			[g_pboard setData:pbData forType:typeStr];
		}
	}
}

/*
 *  Check whether the pasteboard has changed since our last check; if it has, write it to the emulated pasteboard
 */

static void ConvertHostPasteboardToMacScrapIfChanged()
{
	if (!g_pboard)
		return;

	if ([g_pboard changeCount] > g_pb_change_count) {
		ConvertHostPasteboardToMacScrap();
		g_pb_change_count = [g_pboard changeCount];
	}
}

/*
 *	Mac application reads clipboard
 */

void GetScrap(void **handle, uint32_t type, int32_t offset)
{
	D(bug("GetScrap handle %p, type %4.4s, offset %d\n", handle, (char *)&type, offset));

	AUTORELEASE_POOL {
		ConvertHostPasteboardToMacScrapIfChanged();
	}
}

/*
 *  ZeroScrap() is called before a Mac application writes to the clipboard; clears out the previous contents
 */

void ZeroScrap()
{
	D(bug("ZeroScrap\n"));

	we_put_this_data = false;

	// Defer clearing the host pasteboard until the Mac tries to put something on it.
	// This prevents us from clearing the pasteboard when ZeroScrap() is called during startup.
	should_clear = true;
}

/*
 *	Mac application wrote to clipboard
 */

void PutScrap(uint32_t type, void *scrap, int32_t length)
{
	D(bug("PutScrap type %4.4s, data %p, length %ld\n", (char *)&type, scrap, (long)length));

	AUTORELEASE_POOL {
		if (!g_pboard)
			return;

		if (we_put_this_data) {
			we_put_this_data = false;
			return;
		}

		if (length <= 0)
			return;

		if (should_clear) {
			[g_macScrap removeAllObjects];
			should_clear = false;
		}

		NSData *pbData = [NSData dataWithBytes:scrap length:length];
		if (!pbData)
			return;

		[g_macScrap setObject:pbData forKey:[NSNumber numberWithInteger:type]];

		ConvertMacScrapToHostPasteboard();

		// So that our PutScrap() patch won't bounce the data we just wrote back to the Mac clipboard
		g_pb_change_count = [g_pboard changeCount];
	}
}
