/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
/* Contributor: Dan Spencer <dan@atomicpotato.net> */

/* This program is free software. It comes without any warranty, to the
 * extent permitted by applicable law. You can redistribute it and/or
 * modify it under the terms of the Do What The Fuck You Want To Public
 * License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */

#include "xm_internal.h"

/* .xm files are little-endian. */

/* Bounded reader macros.
 * If we attempt to read the buffer out-of-bounds, pretend that the buffer is
 * infinitely padded with zeroes.
 */
#define READ_U8_BOUND(offset, bound) (((offset) < (bound)) ? (*(uint8_t*)(moddata + (offset))) : 0)
#define READ_U16_BOUND(offset, bound) ((uint16_t)READ_U8_BOUND(offset, bound) | ((uint16_t)READ_U8_BOUND((offset) + 1, bound) << 8))
#define READ_U32_BOUND(offset, bound) ((uint32_t)READ_U16_BOUND(offset, bound) | ((uint32_t)READ_U16_BOUND((offset) + 2, bound) << 16))
#define READ_MEMCPY_BOUND(ptr, offset, length, bound) memcpy_pad(ptr, length, moddata, bound, offset)

#define READ_U8(offset) READ_U8_BOUND(offset, moddata_length)
#define READ_U16(offset) READ_U16_BOUND(offset, moddata_length)
#define READ_U32(offset) READ_U32_BOUND(offset, moddata_length)
#define READ_MEMCPY(ptr, offset, length) READ_MEMCPY_BOUND(ptr, offset, length, moddata_length)

#define MIN(a, b) ((a) < (b) ? (a) : (b))

static inline void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) {
	uint8_t* dst_c = dst;
	const uint8_t* src_c = src;

	/* how many bytes can be copied without overrunning `src` */
	size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0;
	copy_bytes = copy_bytes > dst_len ? dst_len : copy_bytes;

	memcpy(dst_c, src_c + offset, copy_bytes);
	/* padded bytes */
	memset(dst_c + copy_bytes, 0, dst_len - copy_bytes);
}

int xm_check_sanity_preload(const char* module, size_t module_length) {
	if(module_length < 60) {
		return 4;
	}

	if(memcmp("Extended Module: ", module, 17) != 0) {
		return 1;
	}

	if(module[37] != 0x1A) {
		return 2;
	}

	if(module[59] != 0x01 || module[58] != 0x04) {
		/* Not XM 1.04 */
		return 3;
	}

	return 0;
}

int xm_check_sanity_postload(xm_context_t* ctx) {
	/* @todo: plenty of stuff to do here… */
	return 0;
}

size_t xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) {
	size_t memory_needed = 0;
	size_t offset = 60; /* Skip the first header */
	uint16_t num_channels;
	uint16_t num_patterns;
	uint16_t num_instruments;

	/* Read the module header */

	num_channels = READ_U16(offset + 8);
	num_patterns = READ_U16(offset + 10);
	memory_needed += num_patterns * sizeof(xm_pattern_t);

	num_instruments = READ_U16(offset + 12);
	memory_needed += num_instruments * sizeof(xm_instrument_t);

	memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */

	/* Header size */
	offset += READ_U32(offset);

	/* Read pattern headers */
	for(uint16_t i = 0; i < num_patterns; ++i) {
		uint16_t num_rows;

		num_rows = READ_U16(offset + 5);
		memory_needed += num_rows * num_channels * sizeof(xm_pattern_slot_t);

		/* Pattern header length + packed pattern data size */
		offset += READ_U32(offset) + READ_U16(offset + 7);
	}

	/* Read instrument headers */
	for(uint16_t i = 0; i < num_instruments; ++i) {
		uint16_t num_samples;
		uint32_t sample_size_aggregate = 0;

		num_samples = READ_U16(offset + 27);
		memory_needed += num_samples * sizeof(xm_sample_t);

		/* Instrument header size */
		uint32_t ins_header_size = READ_U32(offset);
		if (ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
			ins_header_size = INSTRUMENT_HEADER_LENGTH;
		offset += ins_header_size;

		for(uint16_t j = 0; j < num_samples; ++j) {
			uint32_t sample_size;

			sample_size = READ_U32(offset);
			sample_size_aggregate += sample_size;
			memory_needed += sample_size;
			offset += 40; /* See comment in xm_load_module() */
		}

		offset += sample_size_aggregate;
	}

	memory_needed += num_channels * sizeof(xm_channel_context_t);
	memory_needed += sizeof(xm_context_t);

	return memory_needed;
}

char* xm_load_module(xm_context_t* ctx, const char* moddata, size_t moddata_length, char* mempool) {
	size_t offset = 0;
	xm_module_t* mod = &(ctx->module);

	/* Read XM header */
#if XM_STRINGS
	READ_MEMCPY(mod->name, offset + 17, MODULE_NAME_LENGTH);
	READ_MEMCPY(mod->trackername, offset + 38, TRACKER_NAME_LENGTH);
#endif
	offset += 60;

	/* Read module header */
	uint32_t header_size = READ_U32(offset);

	mod->length = READ_U16(offset + 4);
	mod->restart_position = READ_U16(offset + 6);
	mod->num_channels = READ_U16(offset + 8);
	mod->num_patterns = READ_U16(offset + 10);
	mod->num_instruments = READ_U16(offset + 12);

	mod->patterns = (xm_pattern_t*)mempool;
	mempool += mod->num_patterns * sizeof(xm_pattern_t);

	mod->instruments = (xm_instrument_t*)mempool;
	mempool += mod->num_instruments * sizeof(xm_instrument_t);

	uint16_t flags = READ_U32(offset + 14);
	mod->frequency_type = (flags & (1 << 0)) ? XM_LINEAR_FREQUENCIES : XM_AMIGA_FREQUENCIES;

	ctx->tempo = READ_U16(offset + 16);
	ctx->bpm = READ_U16(offset + 18);

	READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH);
	offset += header_size;

	/* Read patterns */
	for(uint16_t i = 0; i < mod->num_patterns; ++i) {
		uint16_t packed_patterndata_size = READ_U16(offset + 7);
		xm_pattern_t* pat = mod->patterns + i;

		pat->num_rows = READ_U16(offset + 5);

		pat->slots = (xm_pattern_slot_t*)mempool;
		mempool += mod->num_channels * pat->num_rows * sizeof(xm_pattern_slot_t);

		/* Pattern header length */
		offset += READ_U32(offset);

		if(packed_patterndata_size == 0) {
			/* No pattern data is present */
			memset(pat->slots, 0, sizeof(xm_pattern_slot_t) * pat->num_rows * mod->num_channels);
		} else {
			/* This isn't your typical for loop */
			for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) {
				uint8_t note = READ_U8(offset + j);
				xm_pattern_slot_t* slot = pat->slots + k;

				if(note & (1 << 7)) {
					/* MSB is set, this is a compressed packet */
					++j;

					if(note & (1 << 0)) {
						/* Note follows */
						slot->note = READ_U8(offset + j);
						++j;
					} else {
						slot->note = 0;
					}

					if(note & (1 << 1)) {
						/* Instrument follows */
						slot->instrument = READ_U8(offset + j);
						++j;
					} else {
						slot->instrument = 0;
					}

					if(note & (1 << 2)) {
						/* Volume column follows */
						slot->volume_column = READ_U8(offset + j);
						++j;
					} else {
						slot->volume_column = 0;
					}

					if(note & (1 << 3)) {
						/* Effect follows */
						slot->effect_type = READ_U8(offset + j);
						++j;
					} else {
						slot->effect_type = 0;
					}

					if(note & (1 << 4)) {
						/* Effect parameter follows */
						slot->effect_param = READ_U8(offset + j);
						++j;
					} else {
						slot->effect_param = 0;
					}
				} else {
					/* Uncompressed packet */
					slot->note = note;
					slot->instrument = READ_U8(offset + j + 1);
					slot->volume_column = READ_U8(offset + j + 2);
					slot->effect_type = READ_U8(offset + j + 3);
					slot->effect_param = READ_U8(offset + j + 4);
					j += 5;
				}
			}
		}

		offset += packed_patterndata_size;
	}

	/* Read instruments */
	for(uint16_t i = 0; i < ctx->module.num_instruments; ++i) {
		xm_instrument_t* instr = mod->instruments + i;

		/* Original FT2 would load instruments with a direct read into the
		   instrument data structure that was previously zeroed. This means
		   that if the declared length was less than INSTRUMENT_HEADER_LENGTH,
		   all excess data would be zeroed. This is used by the XM compressor
		   BoobieSqueezer. To implement this, bound all reads to the header size. */
		uint32_t ins_header_size = READ_U32(offset);
		if (ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
			ins_header_size = INSTRUMENT_HEADER_LENGTH;

#if XM_STRINGS
		READ_MEMCPY_BOUND(instr->name, offset + 4, INSTRUMENT_NAME_LENGTH, offset + ins_header_size);
		instr->name[INSTRUMENT_NAME_LENGTH] = 0;
#endif
	    instr->num_samples = READ_U16_BOUND(offset + 27, offset + ins_header_size);

		if(instr->num_samples > 0) {
			/* Read extra header properties */
			READ_MEMCPY_BOUND(instr->sample_of_notes, offset + 33, NUM_NOTES, offset + ins_header_size);

			instr->volume_envelope.num_points = READ_U8_BOUND(offset + 225, offset + ins_header_size);
			if (instr->volume_envelope.num_points > NUM_ENVELOPE_POINTS)
				instr->volume_envelope.num_points = NUM_ENVELOPE_POINTS;

			instr->panning_envelope.num_points = READ_U8_BOUND(offset + 226, offset + ins_header_size);
			if (instr->panning_envelope.num_points > NUM_ENVELOPE_POINTS)
				instr->panning_envelope.num_points = NUM_ENVELOPE_POINTS;

			for(uint8_t j = 0; j < instr->volume_envelope.num_points; ++j) {
				instr->volume_envelope.points[j].frame = READ_U16_BOUND(offset + 129 + 4 * j, offset + ins_header_size);
				instr->volume_envelope.points[j].value = READ_U16_BOUND(offset + 129 + 4 * j + 2, offset + ins_header_size);
			}

			for(uint8_t j = 0; j < instr->panning_envelope.num_points; ++j) {
				instr->panning_envelope.points[j].frame = READ_U16_BOUND(offset + 177 + 4 * j, offset + ins_header_size);
				instr->panning_envelope.points[j].value = READ_U16_BOUND(offset + 177 + 4 * j + 2, offset + ins_header_size);
			}

			instr->volume_envelope.sustain_point = READ_U8_BOUND(offset + 227, offset + ins_header_size);
			instr->volume_envelope.loop_start_point = READ_U8_BOUND(offset + 228, offset + ins_header_size);
			instr->volume_envelope.loop_end_point = READ_U8_BOUND(offset + 229, offset + ins_header_size);

			instr->panning_envelope.sustain_point = READ_U8_BOUND(offset + 230, offset + ins_header_size);
			instr->panning_envelope.loop_start_point = READ_U8_BOUND(offset + 231, offset + ins_header_size);
			instr->panning_envelope.loop_end_point = READ_U8_BOUND(offset + 232, offset + ins_header_size);

			// Fix broken modules with loop points outside of defined points
			if (instr->volume_envelope.num_points > 0) {
				instr->volume_envelope.loop_start_point =
					MIN(instr->volume_envelope.loop_start_point, instr->volume_envelope.num_points-1);
				instr->volume_envelope.loop_end_point =
					MIN(instr->volume_envelope.loop_end_point,   instr->volume_envelope.num_points-1);
			}
			if (instr->panning_envelope.num_points > 0) {
				instr->panning_envelope.loop_start_point =
					MIN(instr->panning_envelope.loop_start_point, instr->panning_envelope.num_points-1);
				instr->panning_envelope.loop_end_point =
					MIN(instr->panning_envelope.loop_end_point,   instr->panning_envelope.num_points-1);
			}

			uint8_t flags = READ_U8_BOUND(offset + 233, offset + ins_header_size);
			instr->volume_envelope.enabled = flags & (1 << 0);
			instr->volume_envelope.sustain_enabled = flags & (1 << 1);
			instr->volume_envelope.loop_enabled = flags & (1 << 2);

			flags = READ_U8_BOUND(offset + 234, offset + ins_header_size);
			instr->panning_envelope.enabled = flags & (1 << 0);
			instr->panning_envelope.sustain_enabled = flags & (1 << 1);
			instr->panning_envelope.loop_enabled = flags & (1 << 2);

			instr->vibrato_type = READ_U8_BOUND(offset + 235, offset + ins_header_size);
			if(instr->vibrato_type == 2) {
				instr->vibrato_type = 1;
			} else if(instr->vibrato_type == 1) {
				instr->vibrato_type = 2;
			}
			instr->vibrato_sweep = READ_U8_BOUND(offset + 236, offset + ins_header_size);
			instr->vibrato_depth = READ_U8_BOUND(offset + 237, offset + ins_header_size);
			instr->vibrato_rate = READ_U8_BOUND(offset + 238, offset + ins_header_size);
			instr->volume_fadeout = READ_U16_BOUND(offset + 239, offset + ins_header_size);

			instr->samples = (xm_sample_t*)mempool;
			mempool += instr->num_samples * sizeof(xm_sample_t);
		} else {
			instr->samples = NULL;
		}

		/* Instrument header size */
		offset += ins_header_size;

		for(uint16_t j = 0; j < instr->num_samples; ++j) {
			/* Read sample header */
			xm_sample_t* sample = instr->samples + j;

			sample->length = READ_U32(offset);
			sample->loop_start = READ_U32(offset + 4);
			sample->loop_length = READ_U32(offset + 8);
			sample->loop_end = sample->loop_start + sample->loop_length;
			sample->volume = (float)READ_U8(offset + 12) / (float)0x40;
			sample->finetune = (int8_t)READ_U8(offset + 13);

			/* Fix invalid loop definitions */
			if (sample->loop_start > sample->length)
				sample->loop_start = sample->length;
			if (sample->loop_end > sample->length)
				sample->loop_end = sample->length;
			sample->loop_length = sample->loop_end - sample->loop_start;

			uint8_t flags = READ_U8(offset + 14);
			if((flags & 3) == 0 || sample->loop_length == 0) {
				sample->loop_type = XM_NO_LOOP;
			} else if((flags & 3) == 1) {
				sample->loop_type = XM_FORWARD_LOOP;
			} else {
				sample->loop_type = XM_PING_PONG_LOOP;
			}

			sample->bits = (flags & (1 << 4)) ? 16 : 8;

			sample->panning = (float)READ_U8(offset + 15) / (float)0xFF;
			sample->relative_note = (int8_t)READ_U8(offset + 16);
#if XM_STRINGS
			READ_MEMCPY(sample->name, offset + 18, SAMPLE_NAME_LENGTH);
			sample->name[SAMPLE_NAME_LENGTH] = 0;
#endif
			sample->data8 = (int8_t*)mempool;
			mempool += sample->length;

			if(sample->bits == 16) {
				sample->loop_start >>= 1;
				sample->loop_length >>= 1;
				sample->loop_end >>= 1;
				sample->length >>= 1;
			}

			/* Notice that, even if there's a "sample header size" in the
			   instrument header, that value seems ignored, and might even
			   be wrong in some corrupted modules. */
			offset += 40;
		}

		for(uint16_t j = 0; j < instr->num_samples; ++j) {
			/* Read sample data */
			xm_sample_t* sample = instr->samples + j;
			uint32_t length = sample->length;

			if(sample->bits == 16) {
				int16_t v = 0;
				for(uint32_t k = 0; k < length; ++k) {
					v = v + (int16_t)READ_U16(offset + (k << 1));
					sample->data16[k] = v;
				}
				offset += sample->length << 1;
			} else {
				int8_t v = 0;
				for(uint32_t k = 0; k < length; ++k) {
					v = v + (int8_t)READ_U8(offset + k);
					sample->data8[k] = v;
				}
				offset += sample->length;
			}
		}
	}

	return mempool;
}
