/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/ynamic    \____/niversal  /__\  /____\usic   /|  .  . ibliotheque
 *                                                      /  \
 *                                                     / .  \
 * readpsm.c - Code to read a Protracker Studio       / / \  \
 *             module from an open file.             | <  /   \_
 *                                                   |  \/ /\   /
 * By Chris Moeller.                                  \_  /  > /
 *                                                      | \ / /
 *                                                      |  ' /
 *                                                       \__/
 */

#include <stdlib.h>
#include <string.h>

#include "dumb.h"
#include "internal/it.h"

#define PSMV_OLD 940730
#define PSMV_NEW 940902

typedef struct _PSMCHUNK
{
	int id;
	int len;
	unsigned char * data;
} PSMCHUNK;

typedef struct _PSMEVENT
{
	int type;
	unsigned char data[8];
} PSMEVENT;

#define PSM_EVENT_END               0
#define PSM_EVENT_PLAY_PATTERN      1
#define PSM_EVENT_JUMP_TO_LINE      4
#define PSM_EVENT_SET_SPEED         7
#define PSM_EVENT_SET_BPM           8
#define PSM_EVENT_SAMPLE_MAP_TABLE 12
#define PSM_EVENT_CHANGE_PAN       13
#define PSM_EVENT_CHANGE_VOL       14

static int it_psm_process_sample(IT_SAMPLE * sample, const unsigned char * data, int len, int id, int version) {
	int flags;
	int insno;
	int length;
	int loopstart;
	int loopend;
	int panpos;
	int defvol;
	int samplerate;

	if (len < 0x60) return -1;

	flags = data[0];

	if (version == PSMV_OLD) {
		memcpy(sample->name, data + 0x0D, 34);
		sample->name[34] = 0;

		insno = data[0x34] | (data[0x35] << 8);
		length = data[0x36] | (data[0x37] << 8) | (data[0x38] << 16) | (data[0x39] << 24);
		loopstart = data[0x3A] | (data[0x3B] << 8) | (data[0x3C] << 16) | (data[0x3D] << 24);
		loopend = data[0x3E] | (data[0x3F] << 8) | (data[0x40] << 16) | (data[0x41] << 24);
		panpos = data[0x43];
		defvol = data[0x44];
		samplerate = data[0x49] | (data[0x4A] << 8) | (data[0x4B] << 16) | (data[0x4C] << 24);
	} else if (version == PSMV_NEW) {
		memcpy(sample->name, data + 0x11, 34);
		sample->name[34] = 0;

		insno = data[0x38] | (data[0x39] << 8);
		length = data[0x3A] | (data[0x3B] << 8) | (data[0x3C] << 16) | (data[0x3D] << 24);
		loopstart = data[0x3E] | (data[0x3F] << 8) | (data[0x40] << 16) | (data[0x41] << 24);
		loopend = data[0x42] | (data[0x43] << 8) | (data[0x44] << 16) | (data[0x45] << 24);
		panpos = data[0x48];
		defvol = data[0x49];
		samplerate = data[0x4E] | (data[0x4F] << 8) | (data[0x50] << 16) | (data[0x51] << 24);
	}

	if (insno != id) return -1;

	if (!length) {
		sample->flags &= ~IT_SAMPLE_EXISTS;
		return 0;
	}
	
	if ((length > len - 0x60) || ((flags & 0x7F) != 0)) return -1;

	sample->flags = IT_SAMPLE_EXISTS;
	sample->length = length;
	sample->loop_start = loopstart;
	sample->loop_end = loopend;
	sample->C5_speed = samplerate;
	sample->default_volume = defvol >> 1;
	sample->default_pan = 0;
	sample->filename[0] = 0;
	sample->global_volume = 64;
	sample->vibrato_speed = 0;
	sample->vibrato_depth = 0;
	sample->vibrato_rate = 0;
	sample->vibrato_waveform = IT_VIBRATO_SINE;
	sample->finetune = 0;
	sample->max_resampling_quality = -1;

	if (flags & 0x80) {
		if (((unsigned int)sample->loop_end <= (unsigned int)sample->length) &&
			((unsigned int)sample->loop_start < (unsigned int)sample->loop_end)) {
			sample->length = sample->loop_end;
			sample->flags |= IT_SAMPLE_LOOP;
		}
	}

	sample->data = malloc(sample->length);
	if (!sample->data)
		return -1;

	flags = 0;
	data += 0x60;

	for (insno = 0; insno < sample->length; insno++) {
		flags += (signed char)(*data++);
		((signed char *)sample->data)[insno] = flags;
	}

	return 0;
}

static int it_psm_process_pattern(IT_PATTERN * pattern, const unsigned char * data, int len, int speed, int bpm, const unsigned char * pan, const int * vol, int version) {
	int length, nrows, row, rowlen, pos;
	unsigned flags, chan;
	IT_ENTRY * entry;

	length = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
	if (len > length) len = length;

	if (version == PSMV_OLD) {
		if (len < 10) return -1;
		data += 8;
		len -= 8;
	} else if (version == PSMV_NEW) {
		if (len < 14) return -1;
		data += 12;
		len -= 12;
	}

	nrows = data[0] | (data[1] << 8);

	if (!nrows) return 0;

	pattern->n_rows = nrows;

	data += 2;
	len -= 2;

	pattern->n_entries = 0;

	row = 0;
	pos = 2;
	rowlen = data[0] | (data[1] << 8);

	while ((row < nrows) && (pos < len)) {
		if (pos >= rowlen) {
			row++;
			rowlen += data[pos] | (data[pos+1] << 8);
			pos += 2;
			continue;
		}

		flags = data[pos++];
		chan = data[pos++];

		if (chan > 63) return -1;

		if (flags & 0xF0) {
			pattern->n_entries++;
			if (flags & 0x80) pos++;
			if (flags & 0x40) pos++;
			if (flags & 0x20) pos++;
			if (flags & 0x10) {
				switch (data[pos]) {
					case 0x29:
						pos++;
					case 0x33:
						pos++;
					default:
						pos += 2;
				}
			}
		}
	}

	if (!pattern->n_entries) return 0;

	pattern->n_entries += nrows;
	if (speed) pattern->n_entries++;
	if (bpm >= 0x20) pattern->n_entries++;

	for (pos = 0; pos < 32; pos++) {
		if (!(pan[pos*2+1] & 0xF9)) pattern->n_entries++;
		if (vol[pos] != -1) pattern->n_entries++;
	}

	pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry));
	if (!pattern->entry) return -1;

	entry = pattern->entry;

	if (speed) {
		entry->channel = 0;
		entry->mask = IT_ENTRY_EFFECT;
		entry->effect = IT_SET_SPEED;
		entry->effectvalue = speed;
		entry++;
	}

	if (bpm >= 0x20) {
		entry->channel = 0;
		entry->mask = IT_ENTRY_EFFECT;
		entry->effect = IT_SET_SONG_TEMPO;
		entry->effectvalue = bpm;
		entry++;
	}

	for (pos = 0; pos < 32; pos++) {
		if (!(pan[pos*2+1] & 0xF9)) {
			entry->channel = pos;
			entry->mask = IT_ENTRY_EFFECT;
			switch (pan[pos*2+1]) {
			case 0:
				entry->effect = IT_SET_PANNING;
				entry->effectvalue = pan[pos*2] ^ 128;
				break;
			case 2:
				entry->effect = IT_S;
				entry->effectvalue = EFFECT_VALUE(IT_S_SET_SURROUND_SOUND,1);
				break;
			case 4:
				entry->effect = IT_SET_PANNING;
				entry->effectvalue = 128;
				break;
			}
			entry++;
		}
		if (vol[pos] != -1) {
			entry->channel = pos;
			entry->mask = IT_ENTRY_EFFECT;
			entry->effect = IT_SET_CHANNEL_VOLUME;
			entry->effectvalue = (vol[pos] + 2) >> 2;
			entry++;
		}
	}

	row = 0;
	pos = 2;
	rowlen = data[0] | (data[1] << 8);

	while ((row < nrows) && (pos < len)) {
		if (pos >= rowlen) {
			IT_SET_END_ROW(entry);
			entry++;
			row++;
			rowlen += data[pos] | (data[pos+1] << 8);
			pos += 2;
			continue;
		}

		flags = data[pos++];
		entry->channel = data[pos++];
		entry->mask = 0;

		if (flags & 0xF0) {
			if (flags & 0x80) {
				entry->mask |= IT_ENTRY_NOTE;
				if (version == PSMV_OLD) {
					if ((data[pos] < 0x80)) entry->note = (data[pos]>>4)*12+(data[pos]&0x0f)+12;
					else entry->mask &= ~IT_ENTRY_NOTE;
				} else if (version == PSMV_NEW) {
					if ((data[pos]) && (data[pos] < 84)) entry->note = data[pos] + 35;
					else entry->mask &= ~IT_ENTRY_NOTE;
				}
				pos++;
			}

			if (flags & 0x40) {
				entry->mask |= IT_ENTRY_INSTRUMENT;
				entry->instrument = data[pos++] + 1;
			}

			if (flags & 0x20) {
				entry->mask |= IT_ENTRY_VOLPAN;
				entry->volpan = (data[pos++] + 1) >> 1;
			}

			if (flags & 0x10) {
				entry->mask |= IT_ENTRY_EFFECT;
				length = data[pos+1];
				switch (data[pos]) {
					case 1:
						entry->effect = IT_VOLUME_SLIDE;
						if (version == PSMV_OLD) entry->effectvalue = ((length&0x1e)<<3) | 0xF;
						else if (version == PSMV_NEW) entry->effectvalue = (length<<4) | 0xF;
						break;

					case 2:
						entry->effect = IT_VOLUME_SLIDE;
						if (version == PSMV_OLD) entry->effectvalue = (length << 3) & 0xF0;
						else if (version == PSMV_NEW) entry->effectvalue = (length << 4) & 0xF0;
						break;

					case 3:
						entry->effect = IT_VOLUME_SLIDE;
						if (version == PSMV_OLD) entry->effectvalue = (length >> 1) | 0xF0;
						else if (version == PSMV_NEW) entry->effectvalue = length | 0xF0;
						break;

					case 4:
						entry->effect = IT_VOLUME_SLIDE;
						if (version == PSMV_OLD) entry->effectvalue = (length >> 1) & 0xF;
						else if (version == PSMV_NEW) entry->effectvalue = length & 0xF;
						break;

					case 12:
						entry->effect = IT_PORTAMENTO_UP;
						if (version == PSMV_OLD) {
							if (length < 4) entry->effectvalue = length | 0xF0;
							else entry->effectvalue = length >> 2;
						} else if (version == PSMV_NEW) {
							entry->effectvalue = length;
						}
						break;

					case 14:
						entry->effect = IT_PORTAMENTO_DOWN;
						if (version == PSMV_OLD) {
							if (length < 4) entry->effectvalue = length | 0xF0;
							else entry->effectvalue = length >> 2;
						} else if (version == PSMV_NEW) {
							entry->effectvalue = length;
						}
						break;

					case 15:
						entry->effect = IT_TONE_PORTAMENTO;
						if (version == PSMV_OLD) entry->effectvalue = length >> 2;
						else if (version == PSMV_NEW) entry->effectvalue = length;
						break;

					case 0x15:
						entry->effect = IT_VIBRATO;
						entry->effectvalue = length;
						break;

					case 0x18:
						entry->effect = IT_VOLSLIDE_VIBRATO;
						entry->effectvalue = length;
						break;

					case 0x29:
						entry->effect = IT_SET_SAMPLE_OFFSET;
						entry->effectvalue = data[pos+2];
						pos += 2;
						break;

					case 0x2A:
						entry->effect = IT_RETRIGGER_NOTE;
						entry->effectvalue = length;
						break;

					case 0x33:
#if 0
						entry->effect = IT_POSITION_JUMP;
						entry->effectvalue = data[pos+2];
#else
						entry->mask &= ~IT_ENTRY_EFFECT;
#endif
						pos++;
						break;

					case 0x34:
						entry->effect = IT_BREAK_TO_ROW;
						entry->effectvalue = length;
						break;

					case 0x3D:
						entry->effect = IT_SET_SPEED;
						entry->effectvalue = length;
						break;

					case 0x3E:
						if (length >= 0x20) {
							entry->effect = IT_SET_SONG_TEMPO;
							entry->effectvalue = length;
						} else {
							entry->mask &= ~IT_ENTRY_EFFECT;
						}
						break;

					case 0x47:
						entry->effect = IT_ARPEGGIO;
						entry->effectvalue = length;
						break;

					default:
						return -1;
				}
				pos += 2;
			}
			if (entry->mask) entry++;
		}
	}

	while (row < nrows) {
		IT_SET_END_ROW(entry);
		entry++;
		row++;
	}

	pattern->n_entries = entry - pattern->entry;
	if (!pattern->n_entries) return -1;

	return 0;
}


static void free_chunks(PSMCHUNK * chunk, int count) {
	int n;

	for (n = 0; n < count; n++) {
		if (chunk[n].data)
			free(chunk[n].data);
	}

	free(chunk);
}

static void dumb_it_optimize_orders(DUMB_IT_SIGDATA * sigdata);

static int pattcmp( const unsigned char *, const unsigned char *, size_t );

static DUMB_IT_SIGDATA *it_psm_load_sigdata(DUMBFILE *f, int * ver, int subsong)
{
	DUMB_IT_SIGDATA *sigdata;

	PSMCHUNK *chunk;
	int n_chunks = 0;

	PSMCHUNK *songchunk;
	int n_song_chunks = 0;

	PSMEVENT *event = NULL;
	int n_events = 0;

	unsigned char * ptr;

	int n, length, o;

	int found;

	int n_patterns = 0;

	int first_pattern_line = -1;
	int first_pattern;

	int speed, bpm;
	unsigned char pan[64];
	int vol[32];

	if (dumbfile_mgetl(f) != DUMB_ID('P','S','M',' ')) goto error;

	length = dumbfile_igetl(f);

	if (dumbfile_mgetl(f) != DUMB_ID('F','I','L','E')) goto error;

	chunk = calloc(768, sizeof(*chunk));

	while (length >= 8) {
		chunk[n_chunks].id = dumbfile_mgetl(f);
		n = dumbfile_igetl(f);
		length -= 8;
		if (n < 0 || n > length)
			goto error_fc;
		chunk[n_chunks].len = n;
		if (n) {
			ptr = malloc(n);
			if (!ptr) goto error_fc;
			if (dumbfile_getnc(ptr, n, f) < n)
			{
				free(ptr);
				goto error_fc;
			}
			chunk[n_chunks].data = ptr;
		}
		n_chunks++;
		length -= n;
	}

	if (!n_chunks) goto error_fc;
				
	sigdata = malloc(sizeof(*sigdata));
	if (!sigdata) goto error_fc;

	sigdata->n_patterns = 0;
	sigdata->n_samples = 0;
	sigdata->name[0] = 0;

	found = 0;

	for (n = 0; n < n_chunks; n++) {
		PSMCHUNK * c = &chunk[n];
		switch(c->id) {
		case DUMB_ID('S','D','F','T'):
			/* song data format? */
			if ((found & 1) || (c->len != 8) || memcmp(c->data, "MAINSONG", 8)) goto error_sd;
			found |= 1;
			break;

		case DUMB_ID('S','O','N','G'):
			if (/*(found & 2) ||*/ (c->len < 11) /*|| memcmp(c->data, "MAINSONG", 8)*/) goto error_sd;
			found |= 2;
			break;

		case DUMB_ID('D','S','M','P'):
			sigdata->n_samples++;
			break;

		case DUMB_ID('T','I','T','L'):
			length = min(sizeof(sigdata->name) - 1, c->len);
			memcpy(sigdata->name, c->data, length);
			sigdata->name[length] = 0;
		}
	}

	if (found != 3 || !sigdata->n_samples) goto error_sd;

	sigdata->song_message = NULL;
	sigdata->order = NULL;
	sigdata->instrument = NULL;
	sigdata->sample = NULL;
	sigdata->pattern = NULL;
	sigdata->midi = NULL;
	sigdata->checkpoint = NULL;

	sigdata->n_instruments = 0;
	sigdata->n_orders = 0;

	for (n = 0; n < n_chunks; n++) {
		PSMCHUNK * c = &chunk[n];
		if (c->id == DUMB_ID('S','O','N','G')) {
			if (subsong == 0) break;
			subsong--;
		}
	}

	if (n == n_chunks) return NULL;
	subsong = n;

	/*for (n = 0; n < n_chunks; n++) {
		PSMCHUNK * c = &chunk[n];
		if (c->id == DUMB_ID('S','O','N','G')) {*/
	{
		PSMCHUNK * c = &chunk[subsong];
		{
			ptr = c->data;
			if (ptr[10] > 32) goto error_usd;
			sigdata->n_pchannels = ptr[10];
			length = c->len - 11;
			ptr += 11;
			songchunk = 0;
			if (length >= 8) {
				songchunk = malloc(128 * sizeof(*songchunk));
				if (!songchunk) goto error_usd;
				while (length >= 8) {
					songchunk[n_song_chunks].id = DUMB_ID(ptr[0], ptr[1], ptr[2], ptr[3]);
					n = ptr[4] | (ptr[5] << 8) | (ptr[6] << 16) | (ptr[7] << 24);
					length -= 8;
					if (n > length) goto error_sc;
					songchunk[n_song_chunks].len = n;
					songchunk[n_song_chunks].data = ptr + 8;
					n_song_chunks++;
					length -= n;
					ptr += 8 + n;
				}
			}
			/*break;*/
		}
	}

	if (!n_song_chunks) goto error_sc;

	found = 0;

	for (n = 0; n < n_song_chunks; n++) {
		PSMCHUNK * c = &songchunk[n];

		if (c->id == DUMB_ID('D','A','T','E')) {
			/* date of the library build / format spec */
			if (c->len == 6) {
				length = c->len;
				ptr = c->data;
				while (length > 0) {
					if (*ptr >= '0' && *ptr <= '9') {
						found = (found * 10) + (*ptr - '0');
					} else {
						found = 0;
						break;
					}
					ptr++;
					length--;
				}
			}
			break;
		}
	}

	/*
	if (found != 940506 &&
		found != 940509 &&
		found != 940510 &&
		found != 940530 &&
		found != 940629 &&
		found != PSMV_OLD &&
		found != 941011 &&
		found != PSMV_NEW &&
		found != 940906 &&
		found != 940903 &&
		found != 940914 &&
		found != 941213 &&
		found != 800211)   /* WTF?
		goto error_sc;
	*/

	*ver = found;

	if (found == 800211 ||
		found == PSMV_NEW ||
		found == 940903 ||
		found == 940906 ||
		found == 940914 ||
		found == 941213) found = PSMV_NEW;
	else found = PSMV_OLD;

	memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS);

	for (n = 0; n < DUMB_IT_N_CHANNELS; n += 4) {
		sigdata->channel_pan[n  ] = 16;
		sigdata->channel_pan[n+1] = 48;
		sigdata->channel_pan[n+2] = 48;
		sigdata->channel_pan[n+3] = 16;
	}

	for (n = 0; n < n_song_chunks; n++) {
		PSMCHUNK * c = &songchunk[n];

		switch (c->id) {
			case DUMB_ID('O','P','L','H'):
				if (c->len < 2) goto error_sc;
				ptr = c->data;
				o = ptr[0] | (ptr[1] << 8);
				if (!o) goto error_sc;
				event = malloc(o * sizeof(*event));
				if (!event) goto error_sc;
				length = c->len - 2;
				ptr += 2;
				while ((length > 0) && (n_events < o)) {
					event[n_events].type = *ptr;
					switch (*ptr) {
					case PSM_EVENT_END:
						ptr++;
						length--;
						break;

					case PSM_EVENT_PLAY_PATTERN:
						if (found == PSMV_OLD) {
							if (length < 5) goto error_ev;
							memcpy(event[n_events].data, ptr + 1, 4);
							ptr += 5;
							length -= 5;
						} else if (found == PSMV_NEW) {
							if (length < 9) goto error_ev;
							memcpy(event[n_events].data, ptr + 1, 8);
							ptr += 9;
							length -= 9;
						}
						break;

					case PSM_EVENT_SET_SPEED:
					case PSM_EVENT_SET_BPM:
						if (length < 2) goto error_ev;
						event[n_events].data[0] = ptr[1];
						ptr += 2;
						length -= 2;
						break;

					case PSM_EVENT_JUMP_TO_LINE:
					case PSM_EVENT_CHANGE_VOL:
						if (length < 3) goto error_ev;
						memcpy(event[n_events].data, ptr + 1, 2);
						ptr += 3;
						length -= 3;
						break;

					case PSM_EVENT_SAMPLE_MAP_TABLE:
						if (length < 7) goto error_ev;
						memcpy(event[n_events].data, ptr + 1, 6);
						ptr += 7;
						length -= 7;
						break;

					case PSM_EVENT_CHANGE_PAN:
						if (length < 4) goto error_ev;
						memcpy(event[n_events].data, ptr + 1, 3);
						ptr += 4;
						length -= 4;
						break;

					default:
						goto error_ev;
					}
					n_events++;
				}
				break;

			case DUMB_ID('P','P','A','N'):
				length = c->len;
				if (length & 1) goto error_ev;
				ptr = c->data;
				o = 0;
				while (length > 0) {
					switch (ptr[0]) {
					case 0:
						sigdata->channel_pan[o] = ((((int)(signed char)ptr[1]) * 32) / 127) + 32;
						break;
					case 2:
						sigdata->channel_pan[o] = IT_SURROUND;
						break;
					case 4:
						sigdata->channel_pan[o] = 32;
						break;
					}
					ptr += 2;
					length -= 2;
					if (++o >= DUMB_IT_N_CHANNELS) break;
				}
				break;

			/*
			case DUMB_ID('P','A','T','T'):
			case DUMB_ID('D','S','A','M'):
			*/
		}
	}

	sigdata->flags = IT_STEREO | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX;

	sigdata->global_volume = 128;
	sigdata->speed = 6;
	sigdata->tempo = 125;
	sigdata->mixing_volume = 48;
	sigdata->pan_separation = 128;

	speed = 0;
	bpm = 0;
	memset(pan, 255, sizeof(pan));
	memset(vol, 255, sizeof(vol));

	sigdata->n_patterns = n_events;
	sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern));
	if (!sigdata->pattern) goto error_ev;
	for (n = 0; n < sigdata->n_patterns; n++)
		sigdata->pattern[n].entry = NULL;

	for (n = 0; n < n_events; n++) {
		PSMEVENT * e = &event[n];
		switch (e->type) {
		case PSM_EVENT_END:
			n = n_events;
			break;

		case PSM_EVENT_PLAY_PATTERN:
			for (o = 0; o < n_chunks; o++) {
				PSMCHUNK * c = &chunk[o];
				if (c->id == DUMB_ID('P','B','O','D')) {
					ptr = c->data;
					length = c->len;
					if (found == PSMV_OLD) {
						if (length < 8) goto error_ev;
						if (!pattcmp(ptr + 4, e->data, 4)) {
							if (it_psm_process_pattern(&sigdata->pattern[n_patterns], ptr, length, speed, bpm, pan, vol, found)) goto error_ev;
							if (first_pattern_line < 0) {
								first_pattern_line = n;
								first_pattern = o;
							}
							e->data[0] = n_patterns;
							e->data[1] = n_patterns >> 8;
							n_patterns++;
							break;
						}
					} else if (found == PSMV_NEW) {
						if (length < 12) goto error_ev;
						if (!pattcmp(ptr + 4, e->data, 8)) {
							if (it_psm_process_pattern(&sigdata->pattern[n_patterns], ptr, length, speed, bpm, pan, vol, found)) goto error_ev;
							if (first_pattern_line < 0) {
								first_pattern_line = n;
								first_pattern = o;
							}
							e->data[0] = n_patterns;
							e->data[1] = n_patterns >> 8;
							n_patterns++;
							break;
						}
					}
				}
			}
			if (o == n_chunks) goto error_ev;

			speed = 0;
			bpm = 0;
			memset(pan, 255, sizeof(pan));
			memset(vol, 255, sizeof(vol));

			e->type = PSM_EVENT_END;
			break;

		case PSM_EVENT_JUMP_TO_LINE:
			o = e->data[0] | (e->data[1] << 8);
			if (o >= n_events) goto error_ev;
			if (o == 0) {
				/* whew! easy case! */
				sigdata->restart_position = 0;
				n = n_events;
			} else if (o == n) {
				/* freeze */
				n = n_events;
			} else if (o > n) {
				/* jump ahead, setting played event numbers to zero will prevent endless looping */
				n = o - 1;
			} else if (o >= first_pattern_line) {
				/* another semi-easy case */
				sigdata->restart_position = event[o].data[0] | (event[o].data[1] << 8);
				n = n_events;
			} else {
				/* crud, try to simulate rerunning all of the commands from the indicated
				 * line up to the first pattern, then dupe the first pattern again.
				 */
				/*
				PSMCHUNK * c = &chunk[first_pattern];

				for (; o < first_pattern_line; o++) {
					PSMEVENT * ev = &event[o];
					switch (ev->type) {
					case PSM_EVENT_SET_SPEED:
						speed = ev->data[0];
						break;
					case PSM_EVENT_SET_BPM:
						bpm = ev->data[0];
						break;
					case PSM_EVENT_CHANGE_PAN:
						if (ev->data[0] > 31) goto error_ev;
						pan[ev->data[0] * 2] = ev->data[1];
						pan[ev->data[0] * 2 + 1] = ev->data[2];
						break;
					case PSM_EVENT_CHANGE_VOL:
						if (ev->data[0] > 31) goto error_ev;
						vol[ev->data[0]] = ev->data[1];
						break;
					}
				}

				if (it_psm_process_pattern(&sigdata->pattern[n_patterns], c->data, c->len, speed, bpm, pan, vol, found)) goto error_ev;
				n_patterns++;
				sigdata->restart_position = 1;
				n = n_events;

				Eh, what the hell? PSM has no panning commands anyway.
				*/
				sigdata->restart_position = 0;
				n = n_events;
			}
			e->type = PSM_EVENT_END;
			break;

		case PSM_EVENT_SET_SPEED:
			speed = e->data[0];
			break;

		case PSM_EVENT_SET_BPM:
			bpm = e->data[0];
			break;

		case PSM_EVENT_CHANGE_PAN:
			o = e->data[0];
			if (o > 31) goto error_ev;
			pan[o * 2] = e->data[1];
			pan[o * 2 + 1] = e->data[2];
			break;

		case PSM_EVENT_CHANGE_VOL:
			o = e->data[0];
			if (o > 31) goto error_ev;
			vol[o] = e->data[1];
			break;

		case PSM_EVENT_SAMPLE_MAP_TABLE:
			if (e->data[0] != 0 || e->data[1] != 0xFF ||
				e->data[2] != 0 || e->data[3] != 0 ||
				e->data[4] != 1 || e->data[5] != 0)
				goto error_ev;
			break;
		}
	}

	if (n_patterns > 256) goto error_ev;

	sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample));
	if (!sigdata->sample) goto error_ev;
	for (n = 0; n < sigdata->n_samples; n++)
		sigdata->sample[n].data = NULL;

	o = 0;
	for (n = 0; n < n_chunks; n++) {
		PSMCHUNK * c = &chunk[n];
		if (c->id == DUMB_ID('D','S','M','P')) {
			if (it_psm_process_sample(&sigdata->sample[o], c->data, c->len, o, found)) goto error_ev;
			o++;
		}
	}

	sigdata->n_orders = n_patterns;
	sigdata->n_patterns = n_patterns;

	sigdata->order = malloc(n_patterns);

	for (n = 0; n < n_patterns; n++) {
		sigdata->order[n] = n;
	}

	free(event);
	free(songchunk);
	free_chunks(chunk, n_chunks);

	_dumb_it_fix_invalid_orders(sigdata);

	dumb_it_optimize_orders(sigdata);

	return sigdata;

error_ev:
	free(event);
error_sc:
	if (songchunk) free(songchunk);
error_usd:
	_dumb_it_unload_sigdata(sigdata);
	goto error_fc;
error_sd:
	free(sigdata);
error_fc:
	free_chunks(chunk, n_chunks);
error:
	return NULL;
}

static int it_order_compare(const void *e1, const void *e2) {
	if (*((const char *)e1) < *((const char *)e2))
		return -1;

	if (*((const char *)e1) > *((const char *)e2))
		return 1;

	return 0;
}

static int it_optimize_compare(const void *e1, const void *e2) {
	if (((const IT_ENTRY *)e1)->channel < ((const IT_ENTRY *)e2)->channel)
		return -1;

	if (((const IT_ENTRY *)e1)->channel > ((const IT_ENTRY *)e2)->channel)
		return 1;

	return 0;
}

static int it_entry_compare(const IT_ENTRY * e1, const IT_ENTRY * e2) {
	if (IT_IS_END_ROW(e1) && IT_IS_END_ROW(e2)) return 1;
	if (e1->channel != e2->channel) return 0;
	if (e1->mask != e2->mask) return 0;
	if ((e1->mask & IT_ENTRY_NOTE) && (e1->note != e2->note)) return 0;
	if ((e1->mask & IT_ENTRY_INSTRUMENT) && (e1->instrument != e2->instrument)) return 0;
	if ((e1->mask & IT_ENTRY_VOLPAN) && (e1->volpan != e2->volpan)) return 0;
	if ((e1->mask & IT_ENTRY_EFFECT) && ((e1->effect != e2->effect) || (e1->effectvalue != e2->effectvalue))) return 0;
	return 1;
}

/*
static void dumb_it_optimize_pattern(IT_PATTERN * pattern) {
	IT_ENTRY * entry, * end;
	IT_ENTRY * rowstart, * rowend;
	IT_ENTRY * current;

	if (!pattern->n_entries || !pattern->entry) return;

	current = entry = pattern->entry;
	end = entry + pattern->n_entries;

	while (entry < end) {
		rowstart = entry;
		while (!IT_IS_END_ROW(entry)) entry++;
		rowend = entry;
		if (rowend > rowstart + 1)
			qsort(rowstart, rowend - rowstart, sizeof(IT_ENTRY), &it_optimize_compare);
		entry = rowstart;
		while (entry < rowend) {
			if (!(entry->mask)) {}
			else if (it_entry_compare(entry, current)) {}
			else if (!(current->mask) ||
					 ((entry->channel == current->channel) &&
					 ((entry->mask | current->mask) == (entry->mask ^ current->mask)))) {
				current->mask |= entry->mask;
				if (entry->mask & IT_ENTRY_NOTE) current->note = entry->note;
				if (entry->mask & IT_ENTRY_INSTRUMENT) current->instrument = entry->instrument;
				if (entry->mask & IT_ENTRY_VOLPAN) current->volpan = entry->volpan;
				if (entry->mask & IT_ENTRY_EFFECT) {
					current->effect = entry->effect;
					current->effectvalue = entry->effectvalue;
				}
			} else {
				if (++current < entry) *current = *entry;
			}
			entry++;
		}
		if (++current < entry) *current = *entry;
		entry++;
	}

	current++;

	if (current < end) {
		IT_ENTRY * opt;
		pattern->n_entries = current - pattern->entry;
		opt = realloc(pattern->entry, pattern->n_entries * sizeof(*pattern->entry));
		if (opt) pattern->entry = opt;
	}
}
*/

static int it_pattern_compare(const IT_PATTERN * p1, const IT_PATTERN * p2) {
	IT_ENTRY * e1, * end;
	IT_ENTRY * e2;

	if (p1 == p2) return 1;
	if (p1->n_entries != p2->n_entries) return 0;
	
	e1 = p1->entry; end = e1 + p1->n_entries;
	e2 = p2->entry;

	while (e1 < end) {
		if (!it_entry_compare(e1, e2)) return 0;
		e1++; e2++;
	}

	return 1;
}

static void dumb_it_optimize_orders(DUMB_IT_SIGDATA * sigdata) {
	int n, o, p;

	int last_invalid = (sigdata->flags & IT_WAS_AN_XM) ? 255 : 253;

	unsigned char * order_list;
	int n_patterns;

	IT_PATTERN * pattern;

	if (!sigdata->n_orders || !sigdata->n_patterns) return;

	n_patterns = 0;
	order_list = malloc(sigdata->n_orders);

	if (!order_list) return;

	for (n = 0; n < sigdata->n_orders; n++) {
		if (sigdata->order[n] < sigdata->n_patterns) {
			for (o = 0; o < n_patterns; o++) {
				if (sigdata->order[n] == order_list[o]) break;
			}
			if (o == n_patterns) {
				order_list[n_patterns++] = sigdata->order[n];
			}
		}
	}

	if (!n_patterns) {
		free(order_list);
		return;
	}

	/*for (n = 0; n < n_patterns; n++) {
		dumb_it_optimize_pattern(&sigdata->pattern[order_list[n]]);
	}*/

	for (n = 0; n < n_patterns; n++) {
		for (o = n + 1; o < n_patterns; o++) {
			if ((order_list[n] != order_list[o]) &&
				it_pattern_compare(&sigdata->pattern[order_list[n]], &sigdata->pattern[order_list[o]])) {
				for (p = 0; p < sigdata->n_orders; p++) {
					if (sigdata->order[p] == order_list[o]) {
						sigdata->order[p] = order_list[n];
					}
				}
				for (p = o + 1; p < n_patterns; p++) {
					if (order_list[p] == order_list[o]) {
						order_list[p] = order_list[n];
					}
				}
				order_list[o] = order_list[n];
			}
		}
	}

	qsort(order_list, n_patterns, sizeof(*order_list), &it_order_compare);

	for (n = 0, o = 0; n < n_patterns; n++) {
		if (order_list[n] != order_list[o]) {
			if (++o < n) order_list[o] = order_list[n];
		}
	}

	n_patterns = o + 1;

	pattern = malloc(n_patterns * sizeof(*pattern));
	if (!pattern) {
		free(order_list);
		return;
	}

	for (n = 0; n < n_patterns; n++) {
		pattern[n] = sigdata->pattern[order_list[n]];
	}

	for (n = 0; n < sigdata->n_patterns; n++) {
		for (o = 0; o < n_patterns; o++) {
			if (order_list[o] == n) break;
		}
		if (o == n_patterns) {
			if (sigdata->pattern[n].entry)
				free(sigdata->pattern[n].entry);
		}
	}

	free(sigdata->pattern);
	sigdata->pattern = pattern;
	sigdata->n_patterns = n_patterns;

	for (n = 0; n < sigdata->n_orders; n++) {
		for (o = 0; o < n_patterns; o++) {
			if (sigdata->order[n] == order_list[o]) {
				sigdata->order[n] = o;
				break;
			}
		}
	}

	free(order_list);
}

int dumb_get_psm_subsong_count(DUMBFILE *f) {
	int length, subsongs;
	long l;
	
	if (dumbfile_mgetl(f) != DUMB_ID('P','S','M',' ')) return 0;

	length = dumbfile_igetl(f);

	if (dumbfile_mgetl(f) != DUMB_ID('F','I','L','E')) return 0;

	subsongs = 0;

	while (length >= 8 && !dumbfile_error(f)) {
		if (dumbfile_mgetl(f) == DUMB_ID('S','O','N','G')) subsongs++;
		l = dumbfile_igetl(f);
		dumbfile_skip(f, l);
		length -= l + 8;
	}

	if (dumbfile_error(f)) return 0;

	return subsongs;
}



/* Eww */
int pattcmp( const unsigned char * a, const unsigned char * b, size_t l )
{
	int i, j, na, nb;
	char * p;

	i = memcmp( a, b, l );
	if ( !i ) return i;

	/* damnit */

	for ( i = 0; i < l; ++i )
	{
		if ( a [i] >= '0' && a [i] <= '9' ) break;
	}

	if ( i < l )
	{
		na = strtoul( a + i, &p, 10 );
		if ( (const unsigned char *)p == a + i ) return 1;
	}

	for ( j = 0; j < l; ++j )
	{
		if ( b [j] >= '0' && b [j] <= '9' ) break;
	}

	if ( j < l )
	{
		nb = strtoul( b + j, &p, 10 );
		if ( (const unsigned char *)p == b + j ) return -1;
	}

	if ( i < j ) return -1;
	else if ( j > i ) return 1;

	i = memcmp( a, b, j );
	if ( i ) return i;

	return na - nb;
}



DUH *dumb_read_psm_quick(DUMBFILE *f, int subsong)
{
	sigdata_t *sigdata;
	int ver;

	DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it;

	sigdata = it_psm_load_sigdata(f, &ver, subsong);

	if (!sigdata)
		return NULL;

	{
		int n_tags = 2;
		char version[16];
		const char *tag[3][2];
		tag[0][0] = "TITLE";
		tag[0][1] = ((DUMB_IT_SIGDATA *)sigdata)->name;
		tag[1][0] = "FORMAT";
		tag[1][1] = "PSM";
		if ( ver )
		{
			tag[2][0] = "FORMATVERSION";
			snprintf (version, 10, "%d", ver);
			tag[2][1] = (const char *) &version;
			++n_tags;
		}
		return make_duh(-1, n_tags, (const char *const (*)[2])tag, 1, &descptr, &sigdata);
	}
}
