//
//  modDecoder.m
//  modplay
//
//  Created by Christopher Snowhill on 03/17/14.
//  Copyright 2014 __NoWork, Inc__. All rights reserved.
//

#import "modDecoder.h"

#import "PlaylistController.h"

@implementation modDecoder

BOOL s3m_probe_length(unsigned long *intro_length, unsigned long *loop_length, const void *src, unsigned long size, unsigned int subsong) {
	void *st3play = st3play_Alloc(44100, 0, 0);
	if(!st3play) return NO;

	if(!st3play_LoadModule(st3play, src, size)) {
		st3play_Free(st3play);
		return NO;
	}

	st3play_PlaySong(st3play, subsong);

	unsigned long length_total = 0;
	unsigned long length_saved;

	const long length_safety = 44100 * 60 * 30;

	while(st3play_GetLoopCount(st3play) < 1 && length_total < length_safety) {
		st3play_RenderFloat(st3play, NULL, 512);
		length_total += 512;
	}

	if(st3play_GetLoopCount(st3play) < 1) {
		*loop_length = 0;
		*intro_length = 44100 * 60 * 3;
		st3play_Free(st3play);
		return YES;
	}

	length_saved = length_total;

	while(st3play_GetLoopCount(st3play) < 2) {
		st3play_RenderFloat(st3play, NULL, 512);
		length_total += 512;
	}

	st3play_Free(st3play);

	*loop_length = length_total - length_saved;

	if(length_saved > *loop_length)
		*intro_length = length_saved - *loop_length;
	else
		*intro_length = 0;

	return YES;
}

BOOL xm_probe_length(unsigned long *intro_length, unsigned long *loop_length, const void *src, unsigned long size, unsigned int subsong) {
	void *ft2play = ft2play_Alloc(44100, 0, 0);
	if(!ft2play) return NO;

	if(!ft2play_LoadModule(ft2play, src, size)) {
		ft2play_Free(ft2play);
		return NO;
	}

	ft2play_PlaySong(ft2play, subsong);

	unsigned long length_total = 0;
	unsigned long length_saved;

	const long length_safety = 44100 * 60 * 30;

	while(ft2play_GetLoopCount(ft2play) < 1 && length_total < length_safety) {
		ft2play_RenderFloat(ft2play, NULL, 512);
		length_total += 512;
	}

	if(ft2play_GetLoopCount(ft2play) < 1) {
		*loop_length = 0;
		*intro_length = 44100 * 60 * 3;
		ft2play_Free(ft2play);
		return YES;
	}

	length_saved = length_total;

	while(ft2play_GetLoopCount(ft2play) < 2) {
		ft2play_RenderFloat(ft2play, NULL, 512);
		length_total += 512;
	}

	ft2play_Free(ft2play);

	*loop_length = length_total - length_saved;

	if(length_saved > *loop_length)
		*intro_length = length_saved - *loop_length;
	else
		*intro_length = 0;

	return YES;
}

- (id)init {
	self = [super init];
	if(self) {
		player = NULL;
		data = NULL;
	}
	return self;
}

- (BOOL)open:(id<CogSource>)s {
	[s seek:0 whence:SEEK_END];
	size = [s tell];
	[s seek:0 whence:SEEK_SET];

	data = malloc(size);
	[s read:data amount:size];

	type = TYPE_UNKNOWN;

	dataWasMo3 = 0;
	if(size >= 3 && memcmp(data, "MO3", 3) == 0) {
		void *in_data = data;
		unsigned usize = (unsigned)size;
		if(UNMO3_Decode(&in_data, &usize, 0) == 0) {
			free(data);
			data = in_data;
			size = usize;
			dataWasMo3 = 1;
		}
	} else if(size >= 4 && memcmp(data, "\xC1\x83\x2A\x9E", 4) == 0) {
		long out_size = size;
		void *out_data = unpackUmx(data, &out_size);
		if(out_data) {
			free(data);
			data = out_data;
			size = out_size;
		}
	}

	if(size >= (0x2C + 4) && memcmp(data + 0x2C, "SCRM", 4) == 0)
		type = TYPE_S3M;
	else if(size >= 17 && memcmp(data, "Extended Module: ", 17) == 0)
		type = TYPE_XM;
	else
		return NO;

	if([[[s url] fragment] length] == 0)
		track_num = 0;
	else
		track_num = [[[s url] fragment] intValue];

	unsigned long intro_length = 0, loop_length = 0;

	if(type == TYPE_S3M && !s3m_probe_length(&intro_length, &loop_length, data, size, track_num))
		return NO;
	else if(type == TYPE_XM && !xm_probe_length(&intro_length, &loop_length, data, size, track_num))
		return NO;

	framesLength = intro_length + loop_length * 2;
	totalFrames = framesLength + 44100 * 8;

	[self willChangeValueForKey:@"properties"];
	[self didChangeValueForKey:@"properties"];

	return YES;
}

- (BOOL)decoderInitialize {
	int resampling_int = -1;
	NSString *resampling = [[NSUserDefaults standardUserDefaults] stringForKey:@"resampling"];
	if([resampling isEqualToString:@"zoh"])
		resampling_int = 0;
	else if([resampling isEqualToString:@"blep"])
		resampling_int = 1;
	else if([resampling isEqualToString:@"linear"])
		resampling_int = 2;
	else if([resampling isEqualToString:@"blam"])
		resampling_int = 3;
	else if([resampling isEqualToString:@"cubic"])
		resampling_int = 4;
	else if([resampling isEqualToString:@"sinc"])
		resampling_int = 5;

	if(type == TYPE_S3M) {
		player = st3play_Alloc(44100, resampling_int, 2);
		if(!player)
			return NO;

		if(!st3play_LoadModule(player, data, size))
			return NO;

		st3play_PlaySong(player, track_num);
	} else if(type == TYPE_XM) {
		player = ft2play_Alloc(44100, resampling_int, 2);
		if(!player)
			return NO;

		if(!ft2play_LoadModule(player, data, size))
			return NO;

		ft2play_PlaySong(player, track_num);
	}

	framesRead = 0;

	return YES;
}

- (void)decoderShutdown {
	if(player) {
		if(type == TYPE_S3M)
			st3play_Free(player);
		else if(type == TYPE_XM)
			ft2play_Free(player);
		player = NULL;
	}
}

- (NSDictionary *)properties {
	return [NSDictionary dictionaryWithObjectsAndKeys:
	                     [NSNumber numberWithInt:0], @"bitrate",
	                     [NSNumber numberWithFloat:44100], @"sampleRate",
	                     [NSNumber numberWithDouble:totalFrames], @"totalFrames",
	                     [NSNumber numberWithInt:32], @"bitsPerSample",
	                     [NSNumber numberWithBool:YES], @"floatingPoint",
	                     [NSNumber numberWithInt:2], @"channels",
	                     [NSNumber numberWithBool:YES], @"seekable",
	                     @"host", @"endian",
	                     nil];
}

- (int)readAudio:(void *)buf frames:(UInt32)frames {
	BOOL repeat_one = IsRepeatOneSet();

	if(!repeat_one && framesRead >= totalFrames)
		return 0;

	if(!player) {
		if(![self decoderInitialize])
			return 0;
	}

	int total = 0;
	while(total < frames) {
		int framesToRender = 512;
		if(!repeat_one && framesToRender > totalFrames - framesRead)
			framesToRender = (int)(totalFrames - framesRead);
		if(framesToRender > frames - total)
			framesToRender = frames - total;

		float *sampleBuf = (float *)buf + total * 2;

		if(type == TYPE_S3M)
			st3play_RenderFloat(player, sampleBuf, framesToRender);
		else if(type == TYPE_XM)
			ft2play_RenderFloat(player, sampleBuf, framesToRender);

		if(!repeat_one && framesRead + framesToRender > framesLength) {
			long fadeStart = (framesLength > framesRead) ? framesLength : framesRead;
			long fadeEnd = (framesRead + framesToRender < totalFrames) ? framesRead + framesToRender : totalFrames;
			const long fadeTotal = totalFrames - framesLength;
			for(long fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
				const double scale = (double)(fadeTotal - (fadePos - framesLength)) / (double)fadeTotal;
				const long offset = fadePos - framesRead;
				float *samples = sampleBuf + offset * 2;
				samples[0] = samples[0] * scale;
				samples[1] = samples[1] * scale;
			}

			framesToRender = (int)(fadeEnd - framesRead);
		}

		if(!framesToRender)
			break;

		total += framesToRender;
		framesRead += framesToRender;
	}

	return total;
}

- (long)seek:(long)frame {
	if(frame < framesRead || !player) {
		[self decoderShutdown];
		if(![self decoderInitialize])
			return 0;
	}

	while(framesRead < frame) {
		int frames_todo = INT_MAX;
		if(frames_todo > frame - framesRead)
			frames_todo = (int)(frame - framesRead);
		if(type == TYPE_S3M)
			st3play_RenderFloat(player, NULL, frames_todo);
		else if(type == TYPE_XM)
			ft2play_RenderFloat(player, NULL, frames_todo);
		framesRead += frames_todo;
	}

	framesRead = frame;

	return frame;
}

- (void)close {
	[self decoderShutdown];

	if(data) {
		if(dataWasMo3)
			UNMO3_Free(data);
		else
			free(data);
		data = NULL;
	}
}

- (void)dealloc {
	[self close];
}

+ (NSArray *)fileTypes {
	return [NSArray arrayWithObjects:@"s3m", @"s3z", @"xm", @"xmz", @"mo3", @"umx", nil];
}

+ (NSArray *)mimeTypes {
	return [NSArray arrayWithObjects:@"audio/x-s3m", @"audio/x-xm", nil];
}

+ (float)priority {
	return 1.5;
}

@end
