//
//  HCDecoder.m
//  HighlyComplete
//
//  Created by Christopher Snowhill on 9/30/13.
//  Copyright 2013 __NoWork, Inc__. All rights reserved.
//

#import "HCDecoder.h"

#import "Logging.h"

#import "hebios.h"

#import <psflib/psf2fs.h>
#import <psflib/psflib.h>

#import <HighlyExperimental/bios.h>
#import <HighlyExperimental/iop.h>
#import <HighlyExperimental/psx.h>
#import <HighlyExperimental/r3000.h>

#import <HighlyTheoretical/sega.h>

#import <HighlyQuixotic/qsound.h>

#import <mgba-util/vfs.h>
#import <mgba/core/blip_buf.h>
#import <mgba/core/core.h>
#import <mgba/core/log.h>

#import <SSEQPlayer/Player.h>
#import <SSEQPlayer/SDAT.h>
#include <vector>

#import <vio2sf/state.h>

#import <lazyusf2/usf.h>

#include <zlib.h>

#include <dlfcn.h>

#import "PlaylistController.h"

#include <signal.h>

// #define USF_LOG

@interface psf_file_container : NSObject {
	NSLock *lock;
	NSMutableDictionary *list;
}
+ (psf_file_container *)instance;
- (void)add_hint:(NSString *)path source:(id)source;
- (void)remove_hint:(NSString *)path;
- (BOOL)try_hint:(NSString *)path source:(id *)source;
@end

@implementation psf_file_container
+ (psf_file_container *)instance {
	static psf_file_container *instance;

	@synchronized(self) {
		if(!instance) {
			instance = [[self alloc] init];
		}
	}

	return instance;
}
- (psf_file_container *)init {
	if((self = [super init])) {
		lock = [[NSLock alloc] init];
		list = [[NSMutableDictionary alloc] initWithCapacity:0];
	}
	return self;
}
- (void)add_hint:(NSString *)path source:(id)source {
	[lock lock];
	[list setObject:source forKey:path];
	[lock unlock];
}
- (void)remove_hint:(NSString *)path {
	[lock lock];
	[list removeObjectForKey:path];
	[lock unlock];
}
- (BOOL)try_hint:(NSString *)path source:(id *)source {
	[lock lock];
	*source = [list objectForKey:path];
	[lock unlock];
	if(*source) {
		[*source seek:0 whence:0];
		return YES;
	} else {
		return NO;
	}
}
@end

void *source_fopen(const char *path) {
	id<CogSource> source;
	if(![[psf_file_container instance] try_hint:[NSString stringWithUTF8String:path] source:&source]) {
		NSString *urlString = [NSString stringWithUTF8String:path];
		NSURL *url = [NSURL URLWithDataRepresentation:[urlString dataUsingEncoding:NSUTF8StringEncoding] relativeToURL:nil];

		id audioSourceClass = NSClassFromString(@"AudioSource");
		source = [audioSourceClass audioSourceForURL:url];

		if(![source open:url])
			return 0;

		if(![source seekable])
			return 0;
	}

	return (void *)CFBridgingRetain(source);
}

size_t source_fread(void *buffer, size_t size, size_t count, void *handle) {
	NSObject *_handle = (__bridge NSObject *)(handle);
	id<CogSource> __unsafe_unretained source = (id)_handle;

	return [source read:buffer amount:(size * count)] / size;
}

int source_fseek(void *handle, int64_t offset, int whence) {
	NSObject *_handle = (__bridge NSObject *)(handle);
	id<CogSource> __unsafe_unretained source = (id)_handle;

	return [source seek:(long)offset whence:whence] ? 0 : -1;
}

int source_fclose(void *handle) {
	CFBridgingRelease(handle);

	return 0;
}

long source_ftell(void *handle) {
	NSObject *_handle = (__bridge NSObject *)(handle);
	id<CogSource> __unsafe_unretained source = (id)_handle;

	return [source tell];
}

static psf_file_callbacks source_callbacks = {
	"/|\\",
	source_fopen,
	source_fread,
	source_fseek,
	source_fclose,
	source_ftell
};

@implementation HCDecoder

+ (void)initialize {
	if(self == [HCDecoder class]) {
		bios_set_image(hebios, HEBIOS_SIZE);
		psx_init();
		sega_init();
		qsound_init();
		mLogSetDefaultLogger(&gsf_logger);
	}
}

- (id)init {
	self = [super init];
	if(self) {
		hintAdded = NO;
		type = 0;
		emulatorCore = NULL;
		emulatorExtra = NULL;
	}
	return self;
}

- (NSDictionary *)metadata {
	return metadataList;
}

- (long)retrieveFrameCount:(long)ms {
	return ms * (sampleRate / 100) / 10;
}

struct psf_info_meta_state {
	NSMutableDictionary *info;

	bool utf8;

	int tag_length_ms;
	int tag_fade_ms;

	float albumGain;
	float albumPeak;
	float trackGain;
	float trackPeak;
	float volume;
};

static int parse_time_crap(NSString *value) {
	NSArray *crapFix = [value componentsSeparatedByString:@"\n"];
	NSArray *components = [[crapFix objectAtIndex:0] componentsSeparatedByString:@":"];

	float totalSeconds = 0;
	float multiplier = 1000;
	bool first = YES;
	for(id component in [components reverseObjectEnumerator]) {
		if(first) {
			first = NO;
			totalSeconds += [component floatValue] * multiplier;
		} else {
			totalSeconds += [component integerValue] * multiplier;
		}
		multiplier *= 60;
	}

	return totalSeconds;
}

static int psf_info_meta(void *context, const char *name, const char *value) {
	struct psf_info_meta_state *state = (struct psf_info_meta_state *)context;

	NSString *tag = guess_encoding_of_string(name);
	NSString *taglc = [tag lowercaseString];
	NSString *svalue = guess_encoding_of_string(value);

	if(svalue == nil)
		return 0;

	if([taglc isEqualToString:@"game"]) {
		taglc = @"album";
	} else if([taglc isEqualToString:@"date"]) {
		taglc = @"year";
	} else if([taglc isEqualToString:@"album artist"]) {
		taglc = @"albumartist";
	} else if([taglc isEqualToString:@"tracknumber"]) {
		taglc = @"track";
	} else if([taglc isEqualToString:@"discnumber"]) {
		taglc = @"disc";
	}

	if([taglc hasPrefix:@"replaygain_"]) {
		if([taglc hasPrefix:@"replaygain_album_"]) {
			if([taglc hasSuffix:@"gain"])
				state->albumGain = [svalue floatValue];
			else if([taglc hasSuffix:@"peak"])
				state->albumPeak = [svalue floatValue];
		} else if([taglc hasPrefix:@"replaygain_track_"]) {
			if([taglc hasSuffix:@"gain"])
				state->trackGain = [svalue floatValue];
			else if([taglc hasSuffix:@"peak"])
				state->trackPeak = [svalue floatValue];
		}
	} else if([taglc isEqualToString:@"volume"]) {
		state->volume = [svalue floatValue];
	} else if([taglc isEqualToString:@"length"]) {
		state->tag_length_ms = parse_time_crap(svalue);
	} else if([taglc isEqualToString:@"fade"]) {
		state->tag_fade_ms = parse_time_crap(svalue);
	} else if([taglc isEqualToString:@"utf8"]) {
		state->utf8 = true;
	} else if([taglc isEqualToString:@"title"] ||
	          [taglc isEqualToString:@"albumartist"] ||
	          [taglc isEqualToString:@"artist"] ||
	          [taglc isEqualToString:@"album"] ||
	          [taglc isEqualToString:@"genre"]) {
		[state->info setObject:svalue forKey:taglc];
	} else if([taglc isEqualToString:@"year"] ||
	          [taglc isEqualToString:@"track"] ||
	          [taglc isEqualToString:@"disc"]) {
		[state->info setObject:@([svalue intValue]) forKey:taglc];
	}

	return 0;
}

struct psf1_load_state {
	void *emu;
	bool first;
	unsigned refresh;
};

typedef struct {
	uint32_t pc0;
	uint32_t gp0;
	uint32_t t_addr;
	uint32_t t_size;
	uint32_t d_addr;
	uint32_t d_size;
	uint32_t b_addr;
	uint32_t b_size;
	uint32_t s_ptr;
	uint32_t s_size;
	uint32_t sp, fp, gp, ret, base;
} exec_header_t;

typedef struct {
	char key[8];
	uint32_t text;
	uint32_t data;
	exec_header_t exec;
	char title[60];
} psxexe_hdr_t;

static int psf1_info(void *context, const char *name, const char *value) {
	struct psf1_load_state *state = (struct psf1_load_state *)context;

	NSString *sname = [guess_encoding_of_string(name) lowercaseString];
	NSString *svalue = guess_encoding_of_string(value);

	if(!state->refresh && [sname isEqualToString:@"_refresh"]) {
		state->refresh = [svalue intValue];
	}

	return 0;
}

unsigned get_be16(void const *p) {
	return (unsigned)((unsigned char const *)p)[0] << 8 |
	       (unsigned)((unsigned char const *)p)[1];
}

unsigned get_le32(void const *p) {
	return (unsigned)((unsigned char const *)p)[3] << 24 |
	       (unsigned)((unsigned char const *)p)[2] << 16 |
	       (unsigned)((unsigned char const *)p)[1] << 8 |
	       (unsigned)((unsigned char const *)p)[0];
}

unsigned get_be32(void const *p) {
	return (unsigned)((unsigned char const *)p)[0] << 24 |
	       (unsigned)((unsigned char const *)p)[1] << 16 |
	       (unsigned)((unsigned char const *)p)[2] << 8 |
	       (unsigned)((unsigned char const *)p)[3];
}

void set_le32(void *p, unsigned n) {
	((unsigned char *)p)[0] = (unsigned char)n;
	((unsigned char *)p)[1] = (unsigned char)(n >> 8);
	((unsigned char *)p)[2] = (unsigned char)(n >> 16);
	((unsigned char *)p)[3] = (unsigned char)(n >> 24);
}

int psf1_loader(void *context, const uint8_t *exe, size_t exe_size,
                const uint8_t *reserved, size_t reserved_size) {
	struct psf1_load_state *state = (struct psf1_load_state *)context;

	psxexe_hdr_t *psx = (psxexe_hdr_t *)exe;

	if(exe_size < 0x800) return -1;
	if(exe_size > UINT_MAX) return -1;

	uint32_t addr = get_le32(&psx->exec.t_addr);
	uint32_t size = (uint32_t)exe_size - 0x800;

	addr &= 0x1fffff;
	if((addr < 0x10000) || (size > 0x1f0000) || (addr + size > 0x200000)) return -1;

	void *pIOP = psx_get_iop_state(state->emu);
	iop_upload_to_ram(pIOP, addr, exe + 0x800, size);

	if(!state->refresh) {
		if(!strncasecmp((const char *)exe + 113, "Japan", 5))
			state->refresh = 60;
		else if(!strncasecmp((const char *)exe + 113, "Europe", 6))
			state->refresh = 50;
		else if(!strncasecmp((const char *)exe + 113, "North America", 13))
			state->refresh = 60;
	}

	if(state->first) {
		void *pR3000 = iop_get_r3000_state(pIOP);
		r3000_setreg(pR3000, R3000_REG_PC, get_le32(&psx->exec.pc0));
		r3000_setreg(pR3000, R3000_REG_GEN + 29, get_le32(&psx->exec.s_ptr));
		state->first = false;
	}

	return 0;
}

static int EMU_CALL virtual_readfile(void *context, const char *path, int offset, char *buffer, int length) {
	return psf2fs_virtual_readfile(context, path, offset, buffer, length);
}

struct sdsf_loader_state {
	uint8_t *data;
	size_t data_size;
};

int sdsf_loader(void *context, const uint8_t *exe, size_t exe_size,
                const uint8_t *reserved, size_t reserved_size) {
	if(exe_size < 4) return -1;

	struct sdsf_loader_state *state = (struct sdsf_loader_state *)context;

	uint8_t *dst = state->data;

	if(state->data_size < 4) {
		state->data = dst = (uint8_t *)malloc(exe_size);
		state->data_size = exe_size;
		memcpy(dst, exe, exe_size);
		return 0;
	}

	uint32_t dst_start = get_le32(dst);
	uint32_t src_start = get_le32(exe);
	dst_start &= 0x7fffff;
	src_start &= 0x7fffff;
	size_t dst_len = state->data_size - 4;
	size_t src_len = exe_size - 4;
	if(dst_len > 0x800000) dst_len = 0x800000;
	if(src_len > 0x800000) src_len = 0x800000;

	if(src_start < dst_start) {
		uint32_t diff = dst_start - src_start;
		state->data_size = dst_len + 4 + diff;
		state->data = dst = (uint8_t *)realloc(dst, state->data_size);
		memmove(dst + 4 + diff, dst + 4, dst_len);
		memset(dst + 4, 0, diff);
		dst_len += diff;
		dst_start = src_start;
		set_le32(dst, dst_start);
	}
	if((src_start + src_len) > (dst_start + dst_len)) {
		size_t diff = (src_start + src_len) - (dst_start + dst_len);
		state->data_size = dst_len + 4 + diff;
		state->data = dst = (uint8_t *)realloc(dst, state->data_size);
		memset(dst + 4 + dst_len, 0, diff);
	}

	memcpy(dst + 4 + (src_start - dst_start), exe + 4, src_len);

	return 0;
}

struct qsf_loader_state {
	uint8_t *key;
	uint32_t key_size;

	uint8_t *z80_rom;
	uint32_t z80_size;

	uint8_t *sample_rom;
	uint32_t sample_size;
};

static int upload_qsf_section(struct qsf_loader_state *state, const char *section, uint32_t start,
                              const uint8_t *data, uint32_t size) {
	uint8_t **array = NULL;
	uint32_t *array_size = NULL;
	uint32_t max_size = 0x7fffffff;

	if(!strcmp(section, "KEY")) {
		array = &state->key;
		array_size = &state->key_size;
		max_size = 11;
	} else if(!strcmp(section, "Z80")) {
		array = &state->z80_rom;
		array_size = &state->z80_size;
	} else if(!strcmp(section, "SMP")) {
		array = &state->sample_rom;
		array_size = &state->sample_size;
	} else
		return -1;

	if((start + size) < start) return -1;

	uint32_t new_size = start + size;
	uint32_t old_size = *array_size;
	if(new_size > max_size) return -1;

	if(new_size > old_size) {
		*array = (uint8_t *)realloc(*array, new_size);
		*array_size = new_size;
		memset((*array) + old_size, 0, new_size - old_size);
	}

	memcpy((*array) + start, data, size);

	return 0;
}

static int qsf_loader(void *context, const uint8_t *exe, size_t exe_size,
                      const uint8_t *reserved, size_t reserved_size) {
	struct qsf_loader_state *state = (struct qsf_loader_state *)context;

	for(;;) {
		char s[4];
		if(exe_size < 11) break;
		memcpy(s, exe, 3);
		exe += 3;
		exe_size -= 3;
		s[3] = 0;
		uint32_t dataofs = get_le32(exe);
		exe += 4;
		exe_size -= 4;
		uint32_t datasize = get_le32(exe);
		exe += 4;
		exe_size -= 4;
		if(datasize > exe_size)
			return -1;

		if(upload_qsf_section(state, s, dataofs, exe, datasize) < 0)
			return -1;

		exe += datasize;
		exe_size -= datasize;
	}

	return 0;
}

struct gsf_loader_state {
	int entry_set;
	uint32_t entry;
	uint8_t *data;
	size_t data_size;
};

static int gsf_loader(void *context, const uint8_t *exe, size_t exe_size,
                      const uint8_t *reserved, size_t reserved_size) {
	if(exe_size < 12) return -1;

	struct gsf_loader_state *state = (struct gsf_loader_state *)context;

	unsigned char *iptr;
	size_t isize;
	unsigned char *xptr;
	unsigned xentry = get_le32(exe + 0);
	unsigned xsize = get_le32(exe + 8);
	unsigned xofs = get_le32(exe + 4) & 0x1ffffff;
	if(xsize < exe_size - 12) return -1;
	if(!state->entry_set) {
		state->entry = xentry;
		state->entry_set = 1;
	}
	{
		iptr = state->data;
		isize = state->data_size;
		state->data = 0;
		state->data_size = 0;
	}
	if(!iptr) {
		size_t rsize = xofs + xsize;
		{
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		iptr = (unsigned char *)malloc(rsize + 10);
		if(!iptr)
			return -1;
		memset(iptr, 0, rsize + 10);
		isize = rsize;
	} else if(isize < xofs + xsize) {
		size_t rsize = xofs + xsize;
		{
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		xptr = (unsigned char *)realloc(iptr, xofs + rsize + 10);
		if(!xptr) {
			free(iptr);
			return -1;
		}
		iptr = xptr;
		isize = rsize;
	}
	memcpy(iptr + xofs, exe + 12, xsize);
	{
		state->data = iptr;
		state->data_size = isize;
	}
	return 0;
}

struct gsf_running_state {
	struct mAVStream stream;
	void *rom;
	int16_t samples[2048 * 2];
	int buffered;
};

static void _gsf_postAudioBuffer(struct mAVStream *stream, blip_t *left, blip_t *right) {
	struct gsf_running_state *state = (struct gsf_running_state *)stream;
	blip_read_samples(left, state->samples, 2048, true);
	blip_read_samples(right, state->samples + 1, 2048, true);
	state->buffered = 2048;
}

void GSFLogger(struct mLogger *logger, int category, enum mLogLevel level, const char *format, va_list args) {
	(void)logger;
	(void)category;
	(void)level;
	(void)format;
	(void)args;
}

static struct mLogger gsf_logger = {
	.log = GSFLogger,
};

struct ncsf_loader_state {
	uint32_t sseq;
	std::vector<uint8_t> sdatData;
	std::unique_ptr<SDAT> sdat;

	std::vector<uint8_t> outputBuffer;

	ncsf_loader_state()
	: sseq(0) {
	}
};

static int ncsf_loader(void *context, const uint8_t *exe, size_t exe_size,
                       const uint8_t *reserved, size_t reserved_size) {
	struct ncsf_loader_state *state = (struct ncsf_loader_state *)context;

	if(reserved_size >= 4) {
		state->sseq = get_le32(reserved);
	}

	if(exe_size >= 12) {
		uint32_t sdat_size = get_le32(exe + 8);
		if(sdat_size > exe_size) return -1;

		if(state->sdatData.empty())
			state->sdatData.resize(sdat_size, 0);
		else if(state->sdatData.size() < sdat_size)
			state->sdatData.resize(sdat_size);
		memcpy(&state->sdatData[0], exe, sdat_size);
	}

	return 0;
}

struct twosf_loader_state {
	uint8_t *rom;
	uint8_t *state;
	size_t rom_size;
	size_t state_size;

	int initial_frames;
	int sync_type;
	int clockdown;
	int arm9_clockdown_level;
	int arm7_clockdown_level;
};

static int load_twosf_map(struct twosf_loader_state *state, int issave, const unsigned char *udata, unsigned usize) {
	if(usize < 8) return -1;

	unsigned char *iptr;
	size_t isize;
	unsigned char *xptr;
	unsigned xsize = get_le32(udata + 4);
	unsigned xofs = get_le32(udata + 0);
	if(issave) {
		iptr = state->state;
		isize = state->state_size;
		state->state = 0;
		state->state_size = 0;
	} else {
		iptr = state->rom;
		isize = state->rom_size;
		state->rom = 0;
		state->rom_size = 0;
	}
	if(!iptr) {
		size_t rsize = xofs + xsize;
		if(!issave) {
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		iptr = (unsigned char *)malloc(rsize + 10);
		if(!iptr)
			return -1;
		memset(iptr, 0, rsize + 10);
		isize = rsize;
	} else if(isize < xofs + xsize) {
		size_t rsize = xofs + xsize;
		if(!issave) {
			rsize -= 1;
			rsize |= rsize >> 1;
			rsize |= rsize >> 2;
			rsize |= rsize >> 4;
			rsize |= rsize >> 8;
			rsize |= rsize >> 16;
			rsize += 1;
		}
		xptr = (unsigned char *)realloc(iptr, xofs + rsize + 10);
		if(!xptr) {
			free(iptr);
			return -1;
		}
		iptr = xptr;
		isize = rsize;
	}
	memcpy(iptr + xofs, udata + 8, xsize);
	if(issave) {
		state->state = iptr;
		state->state_size = isize;
	} else {
		state->rom = iptr;
		state->rom_size = isize;
	}
	return 0;
}

static int load_twosf_mapz(struct twosf_loader_state *state, int issave, const unsigned char *zdata, unsigned zsize, unsigned zcrc) {
	int ret;
	int zerr;
	uLongf usize = 8;
	uLongf rsize = usize;
	unsigned char *udata;
	unsigned char *rdata;

	udata = (unsigned char *)malloc(usize);
	if(!udata)
		return -1;

	while(Z_OK != (zerr = uncompress(udata, &usize, zdata, zsize))) {
		if(Z_MEM_ERROR != zerr && Z_BUF_ERROR != zerr) {
			free(udata);
			return -1;
		}
		if(usize >= 8) {
			usize = get_le32(udata + 4) + 8;
			if(usize < rsize) {
				rsize += rsize;
				usize = rsize;
			} else
				rsize = usize;
		} else {
			rsize += rsize;
			usize = rsize;
		}
		rdata = (unsigned char *)realloc(udata, usize);
		if(!rdata) {
			free(udata);
			return -1;
		}
		udata = rdata;
	}

	rdata = (unsigned char *)realloc(udata, usize);
	if(!rdata) {
		free(udata);
		return -1;
	}

	{
		uLong ccrc = crc32(crc32(0L, Z_NULL, 0), rdata, (uInt)usize);
		if(ccrc != zcrc)
			return -1;
	}

	ret = load_twosf_map(state, issave, rdata, (unsigned)usize);
	free(rdata);
	return ret;
}

static int twosf_loader(void *context, const uint8_t *exe, size_t exe_size,
                        const uint8_t *reserved, size_t reserved_size) {
	struct twosf_loader_state *state = (struct twosf_loader_state *)context;

	if(exe_size >= 8) {
		if(load_twosf_map(state, 0, exe, (unsigned)exe_size))
			return -1;
	}

	if(reserved_size) {
		size_t resv_pos = 0;
		if(reserved_size < 16)
			return -1;
		while(resv_pos + 12 < reserved_size) {
			unsigned save_size = get_le32(reserved + resv_pos + 4);
			unsigned save_crc = get_le32(reserved + resv_pos + 8);
			if(get_le32(reserved + resv_pos + 0) == 0x45564153) {
				if(resv_pos + 12 + save_size > reserved_size)
					return -1;
				if(load_twosf_mapz(state, 1, reserved + resv_pos + 12, save_size, save_crc))
					return -1;
			}
			resv_pos += 12 + save_size;
		}
	}

	return 0;
}

static int twosf_info(void *context, const char *name, const char *value) {
	struct twosf_loader_state *state = (struct twosf_loader_state *)context;

	NSString *sname = [guess_encoding_of_string(name) lowercaseString];
	NSString *svalue = guess_encoding_of_string(value);

	if([sname isEqualToString:@"_frames"]) {
		state->initial_frames = [svalue intValue];
	} else if([sname isEqualToString:@"_clockdown"]) {
		state->clockdown = [svalue intValue];
	} else if([sname isEqualToString:@"_vio2sf_sync_type"]) {
		state->sync_type = [svalue intValue];
	} else if([sname isEqualToString:@"_vio2sf_arm9_clockdown_level"]) {
		state->arm9_clockdown_level = [svalue intValue];
	} else if([sname isEqualToString:@"_vio2sf_arm7_clockdown_level"]) {
		state->arm7_clockdown_level = [svalue intValue];
	}

	return 0;
}

struct usf_loader_state {
	uint32_t enablecompare;
	uint32_t enablefifofull;

	void *emu_state;
};

static int usf_loader(void *context, const uint8_t *exe, size_t exe_size,
                      const uint8_t *reserved, size_t reserved_size) {
	struct usf_loader_state *uUsf = (struct usf_loader_state *)context;
	if(exe && exe_size > 0) return -1;

	return usf_upload_section(uUsf->emu_state, reserved, reserved_size);
}

static int usf_info(void *context, const char *name, const char *value) {
	struct usf_loader_state *uUsf = (struct usf_loader_state *)context;

	NSString *sname = [guess_encoding_of_string(name) lowercaseString];
	NSString *svalue = guess_encoding_of_string(value);

	if([sname isEqualToString:@"_enablecompare"] && [svalue length])
		uUsf->enablecompare = 1;
	else if([sname isEqualToString:@"_enablefifofull"] && [svalue length])
		uUsf->enablefifofull = 1;

	return 0;
}

- (BOOL)initializeDecoder {
	unsigned int silence_seconds = 5;

	usfRemoveSilence = NO;

	if(type == 1) {
		emulatorCore = (uint8_t *)malloc(psx_get_state_size(1));

		psx_clear_state(emulatorCore, 1);

		struct psf1_load_state state;

		state.emu = emulatorCore;
		state.first = true;
		state.refresh = 0;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 1, psf1_loader, &state, psf1_info, &state, 1) <= 0)
			return NO;

		if(state.refresh)
			psx_set_refresh(emulatorCore, state.refresh);

		silence_seconds = 30;
	} else if(type == 2) {
		emulatorExtra = psf2fs_create();

		struct psf1_load_state state;

		state.refresh = 0;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 2, psf2fs_load_callback, emulatorExtra, psf1_info, &state, 1) <= 0)
			return NO;

		emulatorCore = (uint8_t *)malloc(psx_get_state_size(2));

		psx_clear_state(emulatorCore, 2);

		if(state.refresh)
			psx_set_refresh(emulatorCore, state.refresh);

		psx_set_readfile(emulatorCore, virtual_readfile, emulatorExtra);

		silence_seconds = 30;
	} else if(type == 0x11 || type == 0x12) {
		struct sdsf_loader_state state;
		memset(&state, 0, sizeof(state));

		if(psf_load([currentUrl UTF8String], &source_callbacks, type, sdsf_loader, &state, 0, 0, 0) <= 0)
			return NO;

		emulatorCore = (uint8_t *)malloc(sega_get_state_size(type - 0x10));

		sega_clear_state(emulatorCore, type - 0x10);

		sega_enable_dry(emulatorCore, 1);
		sega_enable_dsp(emulatorCore, 1);

		sega_enable_dsp_dynarec(emulatorCore, 0);

		uint32_t start = *(uint32_t *)state.data;
		size_t length = state.data_size;
		const size_t max_length = (type == 0x12) ? 0x800000 : 0x80000;
		if((start + (length - 4)) > max_length) {
			length = max_length - start + 4;
		}
		sega_upload_program(emulatorCore, state.data, (uint32_t)length);

		free(state.data);
	} else if(type == 0x21) {
		struct usf_loader_state state;
		memset(&state, 0, sizeof(state));

		state.emu_state = malloc(usf_get_state_size());
		if(!state.emu_state)
			return NO;

		usf_clear(state.emu_state);

		usf_set_hle_audio(state.emu_state, 1);

		emulatorCore = (uint8_t *)state.emu_state;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 0x21, usf_loader, &state, usf_info, &state, 1) <= 0)
			return NO;

		usf_set_compare(state.emu_state, state.enablecompare);
		usf_set_fifo_full(state.emu_state, state.enablefifofull);

		usfRemoveSilence = YES;

		silence_seconds = 10;
	} else if(type == 0x22) {
		struct gsf_loader_state state;
		memset(&state, 0, sizeof(state));

		if(psf_load([currentUrl UTF8String], &source_callbacks, 0x22, gsf_loader, &state, 0, 0, 0) <= 0)
			return NO;

		if(state.data_size > UINT_MAX)
			return NO;

		/*FILE * f = fopen("/tmp/rom.gba", "wb");
		fwrite(state.data, 1, state.data_size, f);
		fclose(f);*/

		struct VFile *rom = VFileFromConstMemory(state.data, state.data_size);
		if(!rom) {
			free(state.data);
			return NO;
		}

		struct mCore *core = mCoreFindVF(rom);
		if(!core) {
			free(state.data);
			return NO;
		}

		struct gsf_running_state *rstate = (struct gsf_running_state *)calloc(1, sizeof(*rstate));
		if(!rstate) {
			core->deinit(core);
			free(state.data);
			return NO;
		}

		rstate->rom = state.data;
		rstate->stream.postAudioBuffer = _gsf_postAudioBuffer;

		core->init(core);
		core->setAVStream(core, &rstate->stream);
		mCoreInitConfig(core, NULL);

		core->setAudioBufferSize(core, 2048);

		blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 44100);
		blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 44100);

		struct mCoreOptions opts = {
			.useBios = false,
			.skipBios = true,
			.volume = 0x100,
			.sampleRate = 44100,
		};

		mCoreConfigLoadDefaults(&core->config, &opts);

		core->loadROM(core, rom);
		core->reset(core);

		emulatorCore = (uint8_t *)core;
		emulatorExtra = rstate;
	} else if(type == 0x24) {
		struct twosf_loader_state state;
		memset(&state, 0, sizeof(state));
		state.initial_frames = -1;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 0x24, twosf_loader, &state, twosf_info, &state, 1) <= 0) {
			if(state.rom) free(state.rom);
			if(state.state) free(state.state);
			return NO;
		}

		if(state.rom_size > UINT_MAX || state.state_size > UINT_MAX) {
			if(state.rom) free(state.rom);
			if(state.state) free(state.state);
			return NO;
		}

		NDS_state *core = (NDS_state *)calloc(1, sizeof(NDS_state));
		if(!core) {
			if(state.rom) free(state.rom);
			if(state.state) free(state.state);
			return NO;
		}

		if(state_init(core)) {
			state_deinit(core);
			if(state.rom) free(state.rom);
			if(state.state) free(state.state);
			return NO;
		}

		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;

		core->dwInterpolation = resampling_int;
		core->dwChannelMute = 0;

		if(!state.arm7_clockdown_level)
			state.arm7_clockdown_level = state.clockdown;
		if(!state.arm9_clockdown_level)
			state.arm9_clockdown_level = state.clockdown;

		core->initial_frames = state.initial_frames;
		core->sync_type = state.sync_type;
		core->arm7_clockdown_level = state.arm7_clockdown_level;
		core->arm9_clockdown_level = state.arm9_clockdown_level;

		emulatorCore = (uint8_t *)core;
		emulatorExtra = state.rom;

		if(state.rom)
			state_setrom(core, state.rom, (u32)state.rom_size, 0);

		state_loadstate(core, state.state, (u32)state.state_size);

		if(state.state) free(state.state);
	} else if(type == 0x25) {
		struct ncsf_loader_state *state = new struct ncsf_loader_state;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 0x25, ncsf_loader, state, 0, 0, 0) <= 0) {
			delete state;
			return NO;
		}

		Player *player = new Player;

		player->interpolation = INTERPOLATION_SINC;

		PseudoFile file;
		file.data = &state->sdatData;

		state->sdat.reset(new SDAT(file, state->sseq));

		auto *sseqToPlay = state->sdat->sseq.get();

		player->sampleRate = 44100;
		player->Setup(sseqToPlay);
		player->Timer();

		state->outputBuffer.resize(1024 * sizeof(int16_t) * 2);

		emulatorCore = (uint8_t *)player;
		emulatorExtra = state;
	} else if(type == 0x41) {
		struct qsf_loader_state *state = (struct qsf_loader_state *)calloc(1, sizeof(*state));

		emulatorExtra = state;

		if(psf_load([currentUrl UTF8String], &source_callbacks, 0x41, qsf_loader, state, 0, 0, 0) <= 0)
			return NO;

		emulatorCore = (uint8_t *)malloc(qsound_get_state_size());

		qsound_clear_state(emulatorCore);

		if(state->key_size == 11) {
			uint8_t *ptr = state->key;
			uint32_t swap_key1 = get_be32(ptr + 0);
			uint32_t swap_key2 = get_be32(ptr + 4);
			uint32_t addr_key = get_be16(ptr + 8);
			uint8_t xor_key = *(ptr + 10);
			qsound_set_kabuki_key(emulatorCore, swap_key1, swap_key2, addr_key, xor_key);
		} else {
			qsound_set_kabuki_key(emulatorCore, 0, 0, 0, 0);
		}
		qsound_set_z80_rom(emulatorCore, state->z80_rom, state->z80_size);
		qsound_set_sample_rom(emulatorCore, state->sample_rom, state->sample_size);
	} else
		return NO;

	if(type == 1 || type == 2) {
		void *pIOP = psx_get_iop_state(emulatorCore);
		iop_set_compat(pIOP, IOP_COMPAT_HARSH);
	}

	framesRead = 0;

	silence_test_buffer.resize(sampleRate * silence_seconds * 2);

	if(![self fillBuffer])
		return NO;

	silence_test_buffer.remove_leading_silence();

	return YES;
}

- (BOOL)open:(id<CogSource>)source {
	if(![source seekable]) {
		return NO;
	}

	currentSource = source;

	struct psf_info_meta_state info;

	info.info = [NSMutableDictionary dictionary];
	info.utf8 = false;
	info.tag_length_ms = 0;
	info.tag_fade_ms = 0;

	info.albumGain = 0;
	info.albumPeak = 0;
	info.trackGain = 0;
	info.trackPeak = 0;
	info.volume = 1;

	currentUrl = [[[source url] absoluteString] stringByRemovingPercentEncoding];

	[[psf_file_container instance] add_hint:currentUrl source:currentSource];
	hintAdded = YES;

	type = psf_load([currentUrl UTF8String], &source_callbacks, 0, 0, 0, psf_info_meta, &info, 0);

	if(type <= 0)
		return NO;

	switch(type) {
		case 1:
		case 2:
		case 0x11:
		case 0x12:
		case 0x21:
		case 0x22:
		case 0x24:
		case 0x25:
		case 0x41:
			break;

		default:
			return NO;
	}

	emulatorCore = nil;
	emulatorExtra = nil;

	sampleRate = 44100;

	if(type == 2)
		sampleRate = 48000;
	else if(type == 0x41)
		sampleRate = 24038;

	tagLengthMs = info.tag_length_ms;
	tagFadeMs = info.tag_fade_ms;

	if(!tagLengthMs) {
		tagLengthMs = (2 * 60 + 30) * 1000;
		tagFadeMs = 8000;
	}

	replayGainAlbumGain = info.albumGain;
	replayGainAlbumPeak = info.albumPeak;
	replayGainTrackGain = info.trackGain;
	replayGainTrackPeak = info.trackPeak;
	volume = info.volume;

	metadataList = info.info;

	framesLength = [self retrieveFrameCount:tagLengthMs];
	totalFrames = [self retrieveFrameCount:tagLengthMs + tagFadeMs];

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

	return YES;
}

- (BOOL)fillBuffer {
	long frames_left = totalFrames - framesRead - silence_test_buffer.data_available() / 2;
	long free_space = silence_test_buffer.free_space() / 2;
	if(IsRepeatOneSet())
		frames_left = free_space;
	if(free_space > frames_left)
		free_space = frames_left;
	while(free_space > 0) {
		unsigned long samples_to_write = 0;
		int16_t *buf = silence_test_buffer.get_write_ptr(samples_to_write);
		int samples_read = [self readAudioInternal:buf frames:(UInt32)samples_to_write / 2];
		if(!samples_read) break;
		silence_test_buffer.samples_written(samples_read * 2);
		free_space -= samples_read;
	}
	return !silence_test_buffer.test_silence();
}

- (int)readAudioInternal:(void *)buf frames:(UInt32)frames {
	if(type == 1 || type == 2) {
		uint32_t howmany = frames;
		psx_execute(emulatorCore, 0x7fffffff, (int16_t *)buf, &howmany, 0);
		frames = howmany;
	} else if(type == 0x11 || type == 0x12) {
		uint32_t howmany = frames;
		sega_execute(emulatorCore, 0x7fffffff, (int16_t *)buf, &howmany);
		frames = howmany;
	} else if(type == 0x21) {
		const char *err;
		if((err = usf_render_resampled(emulatorCore, (int16_t *)buf, frames, sampleRate)) != 0) {
			DLog(@"USF Error: %s", err);
			return 0;
		}
	} else if(type == 0x22) {
		struct mCore *core = (struct mCore *)emulatorCore;
		struct gsf_running_state *rstate = (struct gsf_running_state *)emulatorExtra;

		unsigned long frames_to_render = frames;

		do {
			unsigned long frames_rendered = rstate->buffered;

			if(frames_rendered >= frames_to_render) {
				memcpy(buf, rstate->samples, frames_to_render * 4);
				frames_rendered -= frames_to_render;
				memcpy(rstate->samples, rstate->samples + frames_to_render * 2, frames_rendered * 4);
				frames_to_render = 0;
			} else {
				memcpy(buf, rstate->samples, frames_rendered * 4);
				buf = ((uint8_t *)buf) + frames_rendered * 4;
				frames_to_render -= frames_rendered;
				frames_rendered = 0;
			}

			rstate->buffered = (int)frames_rendered;

			if(frames_to_render) {
				while(!rstate->buffered)
					core->runFrame(core);
			}
		} while(frames_to_render);
	} else if(type == 0x24) {
		NDS_state *state = (NDS_state *)emulatorCore;
		state_render(state, (s16 *)buf, frames);
	} else if(type == 0x25) {
		Player *player = (Player *)emulatorCore;
		ncsf_loader_state *state = (ncsf_loader_state *)emulatorExtra;
		std::vector<uint8_t> &buffer = state->outputBuffer;
		unsigned long frames_to_do = frames;
		while(frames_to_do) {
			unsigned frames_this_run = 1024;
			if(frames_this_run > frames_to_do)
				frames_this_run = (unsigned int)frames_to_do;
			player->GenerateSamples(buffer, 0, frames_this_run);
			memcpy(buf, &buffer[0], frames_this_run * sizeof(int16_t) * 2);
			buf = ((uint8_t *)buf) + frames_this_run * sizeof(int16_t) * 2;
			frames_to_do -= frames_this_run;
		}
	} else if(type == 0x41) {
		uint32_t howmany = frames;
		qsound_execute(emulatorCore, 0x7fffffff, (int16_t *)buf, &howmany);
		frames = howmany;
	}

	return frames;
}

- (int)readAudio:(void *)buf frames:(UInt32)frames {
	if(!emulatorCore) {
		if(![self initializeDecoder])
			return 0;
	} else if(![self fillBuffer])
		return 0;

	if(usfRemoveSilence) {
		silence_test_buffer.remove_leading_silence();
		usfRemoveSilence = NO;
	}

	unsigned long written = silence_test_buffer.data_available() / 2;
	if(written > frames)
		written = frames;
	if(!IsRepeatOneSet() && written > totalFrames - framesRead)
		written = totalFrames - framesRead;
	if(written == 0)
		return 0;

	silence_test_buffer.read((int16_t *)buf, written * 2);

	if(!IsRepeatOneSet() && framesRead + written > framesLength) {
		long fadeStart = (framesLength > framesRead) ? framesLength : framesRead;
		long fadeEnd = framesRead + written;
		long fadeTotal = totalFrames - framesLength;
		long fadePos;

		int16_t *buf16 = (int16_t *)buf;

		for(fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
			long scale = totalFrames - fadePos;
			buf16[0] = buf16[0] * scale / fadeTotal;
			buf16[1] = buf16[1] * scale / fadeTotal;
			buf16 += 2;
		}
	}

	framesRead += written;

	return (int)written;
}

- (void)closeDecoder {
	if(emulatorCore) {
		if(type == 0x21) {
			usf_shutdown(emulatorCore);
			free(emulatorCore);
		} else if(type == 0x22) {
			struct mCore *core = (struct mCore *)emulatorCore;
			core->deinit(core);
		} else if(type == 0x24) {
			NDS_state *state = (NDS_state *)emulatorCore;
			state_deinit(state);
			free(state);
		} else if(type == 0x25) {
			Player *player = (Player *)emulatorCore;
			delete player;
		} else {
			free(emulatorCore);
		}
		emulatorCore = nil;
	}

	if(type == 2 && emulatorExtra) {
		psf2fs_delete(emulatorExtra);
		emulatorExtra = nil;
	} else if(type == 0x22 && emulatorExtra) {
		struct gsf_running_state *rstate = (struct gsf_running_state *)emulatorExtra;
		free(rstate->rom);
		free(rstate);
		emulatorExtra = nil;
	} else if(type == 0x24 && emulatorExtra) {
		free(emulatorExtra);
		emulatorExtra = nil;
	} else if(type == 0x25 && emulatorExtra) {
		struct ncsf_loader_state *state = (struct ncsf_loader_state *)emulatorExtra;
		delete state;
		emulatorExtra = nil;
	} else if(type == 0x41 && emulatorExtra) {
		struct qsf_loader_state *state = (struct qsf_loader_state *)emulatorExtra;
		free(state->key);
		free(state->z80_rom);
		free(state->sample_rom);
		free(state);
		emulatorExtra = nil;
	}
}

- (void)close {
	[self closeDecoder];
	currentSource = nil;
	if(hintAdded) {
		[[psf_file_container instance] remove_hint:currentUrl];
		hintAdded = NO;
	}
	currentUrl = nil;
}

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

- (long)seek:(long)frame {
	if(frame < framesRead || emulatorCore == NULL) {
		[self closeDecoder];
		if(![self initializeDecoder])
			return -1;
		if(usfRemoveSilence) {
			silence_test_buffer.remove_leading_silence();
			usfRemoveSilence = NO;
		}
	}

	unsigned long buffered_samples = silence_test_buffer.data_available() / 2;
	if(buffered_samples >= (frame - framesRead)) {
		frame -= framesRead;
		silence_test_buffer.read(NULL, frame * 2);
		framesRead += frame;
		return framesRead;
	} else if(buffered_samples) {
		silence_test_buffer.read(NULL, buffered_samples * 2);
		framesRead += buffered_samples;
	}

	if(type == 1 || type == 2) {
		do {
			uint32_t howmany = (uint32_t)(frame - framesRead);
			if(psx_execute(emulatorCore, 0x7fffffff, 0, &howmany, 0) < 0) break;
			framesRead += howmany;
		} while(framesRead < frame);
	} else if(type == 0x11 || type == 0x12) {
		do {
			uint32_t howmany = (uint32_t)(frame - framesRead);
			if(sega_execute(emulatorCore, 0x7fffffff, 0, &howmany) < 0) break;
			framesRead += howmany;
		} while(framesRead < frame);
	} else if(type == 0x21) {
		do {
			ssize_t howmany = frame - framesRead;
			if(howmany > 1024) howmany = 1024;
			if(usf_render_resampled(emulatorCore, NULL, howmany, sampleRate) != 0)
				return -1;
			framesRead += howmany;
		} while(framesRead < frame);
	} else if(type == 0x22) {
		struct mCore *core = (struct mCore *)emulatorCore;
		struct gsf_running_state *rstate = (struct gsf_running_state *)emulatorExtra;

		long frames_to_run = frame - framesRead;

		do {
			if(frames_to_run >= rstate->buffered) {
				frames_to_run -= rstate->buffered;
				rstate->buffered = 0;
			} else {
				rstate->buffered -= frames_to_run;
				memcpy(rstate->samples, rstate->samples + frames_to_run * 4, rstate->buffered * 4);
				frames_to_run = 0;
			}

			if(frames_to_run) {
				while(!rstate->buffered)
					core->runFrame(core);
			}
		} while(frames_to_run);

		framesRead = frame;
	} else if(type == 0x24) {
		NDS_state *state = (NDS_state *)emulatorCore;
		s16 temp[2048];

		long frames_to_run = frame - framesRead;

		while(frames_to_run) {
			unsigned frames_this_run = 1024;
			if(frames_this_run > frames_to_run)
				frames_this_run = (unsigned)frames_to_run;

			state_render(state, temp, frames_this_run);

			frames_to_run -= frames_this_run;
		}

		framesRead = frame;
	} else if(type == 0x25) {
		Player *player = (Player *)emulatorCore;
		ncsf_loader_state *state = (ncsf_loader_state *)emulatorExtra;
		std::vector<uint8_t> &buffer = state->outputBuffer;

		long frames_to_run = frame - framesRead;

		while(frames_to_run) {
			int frames_to_render = 1024;
			if(frames_to_render > frames_to_run) frames_to_render = (int)frames_to_run;

			player->GenerateSamples(buffer, 0, frames_to_render);

			frames_to_run -= frames_to_render;
		}

		framesRead = frame;
	} else if(type == 0x41) {
		do {
			uint32_t howmany = (uint32_t)(frame - framesRead);
			if(qsound_execute(emulatorCore, 0x7fffffff, 0, &howmany) < 0) break;
			framesRead += howmany;
		} while(framesRead < frame);
	}

	return framesRead;
}

- (NSDictionary *)properties {
	NSString *codec = @"";
	switch(type) {
		case 1:
			codec = @"PSF";
			break;
		case 2:
			codec = @"PSF2";
			break;
		case 0x11:
			codec = @"SSF";
			break;
		case 0x12:
			codec = @"DSF";
			break;
		case 0x21:
			codec = @"USF";
			break;
		case 0x22:
			codec = @"GSF";
			break;
		case 0x24:
			codec = @"2SF";
			break;
		case 0x25:
			codec = @"NCSF";
			break;
		case 0x41:
			codec = @"QSF";
			break;
	}

	return @{ @"channels": @(2),
		      @"bitsPerSample": @(16),
		      @"sampleRate": @(sampleRate),
		      @"totalFrames": @(totalFrames),
		      @"bitrate": @(0),
		      @"seekable": @(YES),
		      @"replayGainAlbumGain": @(replayGainAlbumGain),
		      @"replayGainAlbumPeak": @(replayGainAlbumPeak),
		      @"replayGainTrackGain": @(replayGainTrackGain),
		      @"replayGainTrackPeak": @(replayGainTrackPeak),
		      @"volume": @(volume),
		      @"codec": codec,
		      @"endian": @"host",
		      @"encoding": @"synthesized" };
}

+ (NSDictionary *)metadataForURL:(NSURL *)url {
	struct psf_info_meta_state info;

	info.info = [NSMutableDictionary dictionary];
	info.utf8 = false;
	info.tag_length_ms = 0;
	info.tag_fade_ms = 0;

	NSString *decodedUrl = [[url absoluteString] stringByRemovingPercentEncoding];

	psf_load([decodedUrl UTF8String], &source_callbacks, 0, 0, 0, psf_info_meta, &info, 0);

	return [NSDictionary dictionaryWithDictionary:info.info];
}

+ (NSArray *)fileTypes {
	return @[@"psf", @"minipsf", @"psf2", @"minipsf2", @"ssf", @"minissf", @"dsf", @"minidsf", @"qsf", @"miniqsf", @"gsf", @"minigsf", @"ncsf", @"minincsf", @"2sf", @"mini2sf", @"usf", @"miniusf"];
}

+ (NSArray *)mimeTypes {
	return @[@"audio/x-psf"];
}

+ (float)priority {
	return 1.0;
}

+ (NSArray *)fileTypeAssociations {
	NSMutableArray *ret = [[NSMutableArray alloc] init];
	[ret addObject:@"PSF Format Files"];
	[ret addObject:@"vg.icns"];
	[ret addObjectsFromArray:[self fileTypes]];

	return @[[NSArray arrayWithArray:ret]];
}

@end
