/*****************************************************************************\
     Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

/***********************************************************************************
  SNES9X for Mac OS (c) Copyright John Stiles

  Snes9x for Mac OS X

  (c) Copyright 2001 - 2011  zones
  (c) Copyright 2002 - 2005  107
  (c) Copyright 2002         PB1400c
  (c) Copyright 2004         Alexander and Sander
  (c) Copyright 2004 - 2005  Steven Seeger
  (c) Copyright 2005         Ryan Vogt
 ***********************************************************************************/


#include "snes9x.h"
#include "memmap.h"
#include "apu.h"

#include <QuickTime/QuickTime.h>

#include "mac-prefix.h"
#include "mac-gworld.h"
#include "mac-os.h"
#include "mac-screenshot.h"
#include "mac-quicktime.h"

#define	kMovDoubleSize		(1 << 0)
#define	kMovExtendedHeight	(1 << 1)

static void CheckError (OSStatus, int);
static void MacQTOpenVideoComponent (ComponentInstance *);
static void MacQTCloseVideoComponent (ComponentInstance);
static OSStatus WriteFrameCallBack (void *, ICMCompressionSessionRef, OSStatus, ICMEncodedFrameRef, void *);

typedef struct
{
	Movie							movie;
	Track							vTrack, sTrack;
	Media							vMedia, sMedia;
	ComponentInstance				vci;
	SoundDescriptionHandle			soundDesc;
	DataHandler						dataHandler;
	Handle							soundBuffer;
	Handle							dataRef;
	OSType							dataRefType;
	CVPixelBufferPoolRef			pool;
	ICMCompressionSessionRef		session;
	ICMCompressionSessionOptionsRef	option;
	CGImageRef						srcImage;
	TimeValue64						timeStamp;
	long							keyFrame, keyFrameCount;
	long							frameSkip, frameSkipCount;
	int								width, height;
	int								soundBufferSize;
	int								samplesPerSec;
}	MacQTState;

static MacQTState	sqt;


static void CheckError (OSStatus err, int n)
{
	if (err != noErr)
	{
		char	mes[32];

		sprintf(mes, "quicktime %02d", n);
		QuitWithFatalError(err, mes);
	}
}

static void MacQTOpenVideoComponent (ComponentInstance *rci)
{
	OSStatus			err;
	ComponentInstance	ci;
	CFDataRef			data;

	ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);

	data = (CFDataRef) CFPreferencesCopyAppValue(CFSTR("QTVideoSetting"), kCFPreferencesCurrentApplication);
	if (data)
	{
		CFIndex	len;
		Handle	hdl;

		len = CFDataGetLength(data);
		hdl = NewHandleClear((Size) len);
		if (MemError() == noErr)
		{
			HLock(hdl);
			CFDataGetBytes(data, CFRangeMake(0, len), (unsigned char *) *hdl);
			err = SCSetInfo(ci, scSettingsStateType, &hdl);

			DisposeHandle(hdl);
		}

		CFRelease(data);
	}
	else
	{
		SCSpatialSettings	ss;
		SCTemporalSettings	ts;

		ss.codecType       = kAnimationCodecType;
		ss.codec           = 0;
		ss.depth           = 16;
		ss.spatialQuality  = codecMaxQuality;
		err = SCSetInfo(ci, scSpatialSettingsType, &ss);

		ts.frameRate       = FixRatio(Memory.ROMFramesPerSecond, 1);
		ts.keyFrameRate    = Memory.ROMFramesPerSecond;
		ts.temporalQuality = codecMaxQuality;
		err = SCSetInfo(ci, scTemporalSettingsType, &ts);
	}

	*rci = ci;
}

static void MacQTCloseVideoComponent (ComponentInstance ci)
{
	OSStatus	err;

	err = CloseComponent(ci);
}

void MacQTVideoConfig (void)
{
	OSStatus			err;
	ComponentInstance	ci;

	MacQTOpenVideoComponent(&ci);

	long	flag;
	flag = scListEveryCodec | scAllowZeroKeyFrameRate | scDisableFrameRateItem | scAllowEncodingWithCompressionSession;
	err = SCSetInfo(ci, scPreferenceFlagsType, &flag);

	SCWindowSettings	ws;
	ws.size          = sizeof(SCWindowSettings);
	ws.windowRefKind = scWindowRefKindCarbon;
	ws.parentWindow  = NULL;
	err = SCSetInfo(ci, scWindowOptionsType, &ws);

	err = SCRequestSequenceSettings(ci);
	if (err == noErr)
	{
		CFDataRef	data;
		Handle		hdl;

		err = SCGetInfo(ci, scSettingsStateType, &hdl);
		if (err == noErr)
		{
			HLock(hdl);
			data = CFDataCreate(kCFAllocatorDefault, (unsigned char *) *hdl, GetHandleSize(hdl));
			if (data)
			{
				CFPreferencesSetAppValue(CFSTR("QTVideoSetting"), data, kCFPreferencesCurrentApplication);
				CFRelease(data);
			}

			DisposeHandle(hdl);
		}
	}

	MacQTCloseVideoComponent(ci);
}

void MacQTStartRecording (char *path)
{
	OSStatus	err;
	CFStringRef str;
	CFURLRef	url;

	memset(&sqt, 0, sizeof(sqt));

	// storage

	str = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
	url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
	err = QTNewDataReferenceFromCFURL(url, 0, &(sqt.dataRef), &(sqt.dataRefType));
	CheckError(err, 21);
	CFRelease(url);
	CFRelease(str);

	err = CreateMovieStorage(sqt.dataRef, sqt.dataRefType, 'TVOD', smSystemScript, createMovieFileDeleteCurFile | newMovieActive, &(sqt.dataHandler), &(sqt.movie));
	CheckError(err, 22);

	// video

	MacQTOpenVideoComponent(&(sqt.vci));

	long				flag;
	SCTemporalSettings	ts;

	flag = scAllowEncodingWithCompressionSession;
	err = SCSetInfo(sqt.vci, scPreferenceFlagsType, &flag);

	err = SCGetInfo(sqt.vci, scTemporalSettingsType, &ts);
	ts.frameRate = FixRatio(Memory.ROMFramesPerSecond, 1);
	if (ts.keyFrameRate < 1)
		ts.keyFrameRate = Memory.ROMFramesPerSecond;
	sqt.keyFrame  = sqt.keyFrameCount  = ts.keyFrameRate;
	sqt.frameSkip = sqt.frameSkipCount = (macQTMovFlag & 0xFF00) >> 8;
	err = SCSetInfo(sqt.vci, scTemporalSettingsType, &ts);

	sqt.width  = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * SNES_WIDTH;
	sqt.height = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * ((macQTMovFlag & kMovExtendedHeight) ? SNES_HEIGHT_EXTENDED : SNES_HEIGHT);

	sqt.srcImage = NULL;
	sqt.timeStamp = 0;

	SCSpatialSettings			ss;
	ICMEncodedFrameOutputRecord	record;
	ICMMultiPassStorageRef		nullStorage = NULL;

	err = SCCopyCompressionSessionOptions(sqt.vci, &(sqt.option));
	CheckError(err, 61);
	err = ICMCompressionSessionOptionsSetProperty(sqt.option, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_MultiPassStorage, sizeof(ICMMultiPassStorageRef), &nullStorage);

	record.encodedFrameOutputCallback = WriteFrameCallBack;
	record.encodedFrameOutputRefCon   = NULL;
	record.frameDataAllocator         = NULL;
	err = SCGetInfo(sqt.vci, scSpatialSettingsType, &ss);
	err = ICMCompressionSessionCreate(kCFAllocatorDefault, sqt.width, sqt.height, ss.codecType, Memory.ROMFramesPerSecond, sqt.option, NULL, &record, &(sqt.session));
	CheckError(err, 62);

	CFMutableDictionaryRef	dic;
	CFNumberRef				val;
	OSType					pix = k16BE555PixelFormat;
	int						row = sqt.width * 2;

	dic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

	val = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix);
	CFDictionaryAddValue(dic, kCVPixelBufferPixelFormatTypeKey, val);
	CFRelease(val);

	val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.width));
	CFDictionaryAddValue(dic, kCVPixelBufferWidthKey, val);
	CFRelease(val);

	val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.height));
	CFDictionaryAddValue(dic, kCVPixelBufferHeightKey, val);
	CFRelease(val);

	val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &row);
	CFDictionaryAddValue(dic, kCVPixelBufferBytesPerRowAlignmentKey, val);
	CFRelease(val);

	CFDictionaryAddValue(dic, kCVPixelBufferCGImageCompatibilityKey, kCFBooleanTrue);
	CFDictionaryAddValue(dic, kCVPixelBufferCGBitmapContextCompatibilityKey, kCFBooleanTrue);

	err = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, dic, &(sqt.pool));
	CheckError(err, 63);

	CFRelease(dic);

	sqt.vTrack = NewMovieTrack(sqt.movie, FixRatio(sqt.width, 1), FixRatio(sqt.height, 1), kNoVolume);
	CheckError(GetMoviesError(), 23);

	sqt.vMedia = NewTrackMedia(sqt.vTrack, VideoMediaType, Memory.ROMFramesPerSecond, NULL, 0);
	CheckError(GetMoviesError(), 24);

	err = BeginMediaEdits(sqt.vMedia);
	CheckError(err, 25);

	// sound

	sqt.soundDesc = (SoundDescriptionHandle) NewHandleClear(sizeof(SoundDescription));
	CheckError(MemError(), 26);

	(**sqt.soundDesc).descSize    = sizeof(SoundDescription);
#ifdef __BIG_ENDIAN__
	(**sqt.soundDesc).dataFormat  = Settings.SixteenBitSound ? k16BitBigEndianFormat    : k8BitOffsetBinaryFormat;
#else
	(**sqt.soundDesc).dataFormat  = Settings.SixteenBitSound ? k16BitLittleEndianFormat : k8BitOffsetBinaryFormat;
#endif
	(**sqt.soundDesc).numChannels = Settings.Stereo ? 2 : 1;
	(**sqt.soundDesc).sampleSize  = Settings.SixteenBitSound ? 16 : 8;
	(**sqt.soundDesc).sampleRate  = (UnsignedFixed) FixRatio(Settings.SoundPlaybackRate, 1);

	sqt.samplesPerSec = Settings.SoundPlaybackRate / Memory.ROMFramesPerSecond;

	sqt.soundBufferSize = sqt.samplesPerSec;
	if (Settings.SixteenBitSound)
		sqt.soundBufferSize <<= 1;
	if (Settings.Stereo)
		sqt.soundBufferSize <<= 1;

	sqt.soundBuffer = NewHandleClear(sqt.soundBufferSize);
	CheckError(MemError(), 27);
	HLock(sqt.soundBuffer);

	sqt.sTrack = NewMovieTrack(sqt.movie, 0, 0, kFullVolume);
	CheckError(GetMoviesError(), 28);

	sqt.sMedia = NewTrackMedia(sqt.sTrack, SoundMediaType, Settings.SoundPlaybackRate, NULL, 0);
	CheckError(GetMoviesError(), 29);

	err = BeginMediaEdits(sqt.sMedia);
	CheckError(err, 30);
}

void MacQTRecordFrame (int width, int height)
{
	OSStatus	err;

	// video

	if (sqt.frameSkipCount == sqt.frameSkip)
	{
		CVPixelBufferRef	buf;

		err = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, sqt.pool, &buf);
		if (err == noErr)
		{
			CGColorSpaceRef		color;
			CGContextRef		ctx;
			uint16				*p;

			err = CVPixelBufferLockBaseAddress(buf, 0);
			p = (uint16 *) CVPixelBufferGetBaseAddress(buf);

			color = CGColorSpaceCreateDeviceRGB();
			ctx = CGBitmapContextCreate((void *) p, sqt.width, sqt.height, 5, sqt.width * 2, color, kCGImageAlphaNoneSkipFirst | ((systemVersion >= 0x1040) ? kCGBitmapByteOrder16Host : 0));
			CGContextSetShouldAntialias(ctx, false);

			if (sqt.srcImage)
				CGImageRelease(sqt.srcImage);
			sqt.srcImage = CreateGameScreenCGImage();

			CGRect	dst = CGRectMake(0.0f, 0.0f, (float) sqt.width, (float) sqt.height);

			if ((!(height % SNES_HEIGHT_EXTENDED)) && (!(macQTMovFlag & kMovExtendedHeight)))
			{
				CGRect	src;

				src.size.width  = (float) width;
				src.size.height = (float) ((height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
				src.origin.x    = (float) 0;
				src.origin.y    = (float) height - src.size.height;
				DrawSubCGImage(ctx, sqt.srcImage, src, dst);
			}
			else
			if ((sqt.height << 1) % height)
			{
				CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);
				CGContextFillRect(ctx, dst);

				float	dh  = (float) ((sqt.height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
				float	ofs = (float) ((int) ((drawoverscan ? 1.0 : 0.5) * ((float) sqt.height - dh) + 0.5));
				dst = CGRectMake(0.0f, ofs, (float) sqt.width, dh);
				CGContextDrawImage(ctx, dst, sqt.srcImage);
			}
			else
				CGContextDrawImage(ctx, dst, sqt.srcImage);

			CGContextRelease(ctx);
			CGColorSpaceRelease(color);

		#ifndef __BIG_ENDIAN__
			for (int i = 0; i < sqt.width * sqt.height; i++)
				SWAP_WORD(p[i]);
		#endif

			err = CVPixelBufferUnlockBaseAddress(buf, 0);

			err = ICMCompressionSessionEncodeFrame(sqt.session, buf, sqt.timeStamp, 0, kICMValidTime_DisplayTimeStampIsValid, NULL, NULL, NULL);

			CVPixelBufferRelease(buf);
		}

		sqt.keyFrameCount--;
		if (sqt.keyFrameCount <= 0)
			sqt.keyFrameCount = sqt.keyFrame;
	}

	sqt.frameSkipCount--;
	if (sqt.frameSkipCount < 0)
		sqt.frameSkipCount = sqt.frameSkip;

	sqt.timeStamp++;

	// sound

	int	sample_count = sqt.soundBufferSize;
	if (Settings.SixteenBitSound)
		sample_count >>= 1;

	S9xMixSamples((uint8 *) *(sqt.soundBuffer), sample_count);

	err = AddMediaSample(sqt.sMedia, sqt.soundBuffer, 0, sqt.soundBufferSize, 1, (SampleDescriptionHandle) sqt.soundDesc, sqt.samplesPerSec, mediaSampleNotSync, NULL);
}

static OSStatus WriteFrameCallBack (void *refCon, ICMCompressionSessionRef session, OSStatus r, ICMEncodedFrameRef frame, void *reserved)
{
	OSStatus	err;

	err = AddMediaSampleFromEncodedFrame(sqt.vMedia, frame, NULL);
	return (err);
}

void MacQTStopRecording (void)
{
	OSStatus   err;

	// video

	err = ICMCompressionSessionCompleteFrames(sqt.session, true, 0, sqt.timeStamp);
	err = ExtendMediaDecodeDurationToDisplayEndTime(sqt.vMedia, NULL);

	err = EndMediaEdits(sqt.vMedia);
	CheckError(err, 52);

	err = InsertMediaIntoTrack(sqt.vTrack, 0, 0, (TimeValue) GetMediaDisplayDuration(sqt.vMedia), fixed1);
	CheckError(err, 58);

	CGImageRelease(sqt.srcImage);
	CVPixelBufferPoolRelease(sqt.pool);
	ICMCompressionSessionRelease(sqt.session);
	ICMCompressionSessionOptionsRelease(sqt.option);

	// sound

	err = EndMediaEdits(sqt.sMedia);
	CheckError(err, 54);

	err = InsertMediaIntoTrack(sqt.sTrack, 0, 0, GetMediaDuration(sqt.sMedia), fixed1);
	CheckError(err, 55);

	DisposeHandle(sqt.soundBuffer);
	DisposeHandle((Handle) sqt.soundDesc);

	// storage

	err = AddMovieToStorage(sqt.movie, sqt.dataHandler);
	CheckError(err, 56);

	MacQTCloseVideoComponent(sqt.vci);

	err = CloseMovieStorage(sqt.dataHandler);
	CheckError(err, 57);

	DisposeHandle(sqt.dataRef);
	DisposeMovie(sqt.movie);
}