/* Extended Module Player
 * Copyright (C) 1996-2016 Claudio Matsuoka and Hipolito Carraro Jr
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "loader.h"
#include "mod.h"
#include "period.h"

static int flt_test(HIO_HANDLE *, char *, const int);
static int flt_load(struct module_data *, HIO_HANDLE *, const int);

const struct format_loader libxmp_loader_flt = {
	"Startrekker",
	flt_test,
	flt_load
};

static int flt_test(HIO_HANDLE * f, char *t, const int start)
{
	char buf[4];

	hio_seek(f, start + 1080, SEEK_SET);
	if (hio_read(buf, 1, 4, f) < 4)
		return -1;

	/* Also RASP? */
	if (memcmp(buf, "FLT", 3) && memcmp(buf, "EXO", 3))
		return -1;

	if (buf[3] != '4' && buf[3] != '8' && buf[3] != 'M')
		return -1;

	hio_seek(f, start + 0, SEEK_SET);
	libxmp_read_title(f, t, 20);

	return 0;
}

/* Waveforms from the Startrekker 1.2 AM synth replayer code */

static const int8 am_waveform[3][32] = {
	{    0,   25,   49,   71,   90,  106,  117,  125,	/* Sine */
	   127,  125,  117,  106,   90,   71,   49,   25,
	     0,  -25,  -49,  -71,  -90, -106, -117, -125,
	  -127, -125, -117, -106,  -90,  -71,  -49,  -25
	},

	{ -128, -120, -112, -104,  -96,  -88,  -80,  -72,	/* Ramp */
	   -64,  -56,  -48,  -40,  -32,  -24,  -16,   -8,
	     0,    8,   16,   24,   32,   40,   48,   56,
	    64,   72,   80,   88,   96,  104,  112,  120
	},

	{ -128, -128, -128, -128, -128, -128, -128, -128,	/* Square */
	  -128, -128, -128, -128, -128, -128, -128, -128,
	   127,  127,  127,  127,  127,  127,  127,  127,
	   127,  127,  127,  127,  127,  127,  127,  127
	}
};

struct am_instrument {
	int16 l0;		/* start amplitude */
	int16 a1l;		/* attack level */
	int16 a1s;		/* attack speed */
	int16 a2l;		/* secondary attack level */
	int16 a2s;		/* secondary attack speed */
	int16 sl;		/* sustain level */
	int16 ds;		/* decay speed */
	int16 st;		/* sustain time */
	int16 rs;		/* release speed */
	int16 wf;		/* waveform */
	int16 p_fall;		/* ? */
	int16 v_amp;		/* vibrato amplitude */
	int16 v_spd;		/* vibrato speed */
	int16 fq;		/* base frequency */
};

static int is_am_instrument(HIO_HANDLE *nt, int i)
{
	char buf[2];
	int16 wf;

	hio_seek(nt, 144 + i * 120, SEEK_SET);
	hio_read(buf, 1, 2, nt);
	if (memcmp(buf, "AM", 2))
		return 0;
	hio_seek(nt, 24, SEEK_CUR);
	wf = hio_read16b(nt);
	if (hio_error(nt) || wf < 0 || wf > 3)
		return 0;

	return 1;
}

static int read_am_instrument(struct module_data *m, HIO_HANDLE *nt, int i)
{
	struct xmp_module *mod = &m->mod;
	struct xmp_instrument *xxi = &mod->xxi[i];
	struct xmp_sample *xxs = &mod->xxs[i];
	struct xmp_envelope *vol_env = &xxi->aei;
	struct xmp_envelope *freq_env = &xxi->fei;
	struct am_instrument am;
	char *wave;
	int a, b;
	int8 am_noise[1024];

	hio_seek(nt, 144 + i * 120 + 2 + 4, SEEK_SET);
	am.l0  = hio_read16b(nt);
	am.a1l = hio_read16b(nt);
	am.a1s = hio_read16b(nt);
	am.a2l = hio_read16b(nt);
	am.a2s = hio_read16b(nt);
	am.sl  = hio_read16b(nt);
	am.ds  = hio_read16b(nt);
	am.st  = hio_read16b(nt);
	hio_read16b(nt);
	am.rs  = hio_read16b(nt);
	am.wf  = hio_read16b(nt);
	am.p_fall = -(int16) hio_read16b(nt);
	am.v_amp = hio_read16b(nt);
	am.v_spd = hio_read16b(nt);
	am.fq  = hio_read16b(nt);

	if (hio_error(nt)) {
		return -1;
	}

#if 0
	printf
	    ("L0=%d A1L=%d A1S=%d A2L=%d A2S=%d SL=%d DS=%d ST=%d RS=%d WF=%d\n",
	     am.l0, am.a1l, am.a1s, am.a2l, am.a2s, am.sl, am.ds, am.st, am.rs,
	     am.wf);
#endif

	if (am.wf < 3) {
		xxs->len = 32;
		xxs->lps = 0;
		xxs->lpe = 32;
		wave = (char *)&am_waveform[am.wf][0];
	} else {
		int j;

		xxs->len = 1024;
		xxs->lps = 0;
		xxs->lpe = 1024;

		for (j = 0; j < 1024; j++)
			am_noise[j] = rand() % 256;

		wave = (char *)&am_noise[0];
	}

	xxs->flg = XMP_SAMPLE_LOOP;
	xxi->sub[0].vol = 0x40;	/* prelude.mod has 0 in instrument */
	xxi->nsm = 1;
	xxi->sub[0].xpo = -12 * am.fq;
	xxi->sub[0].vwf = 0;
	xxi->sub[0].vde = am.v_amp << 2;
	xxi->sub[0].vra = am.v_spd;

	/*
	 * AM synth envelope parameters based on the Startrekker 1.2 docs
	 *
	 * L0    Start amplitude for the envelope
	 * A1L   Attack level
	 * A1S   The speed that the amplitude changes to the attack level, $1
	 *       is slow and $40 is fast.
	 * A2L   Secondary attack level, for those who likes envelopes...
	 * A2S   Secondary attack speed.
	 * DS    The speed that the amplitude decays down to the:
	 * SL    Sustain level. There is remains for the time set by the
	 * ST    Sustain time.
	 * RS    Release speed. The speed that the amplitude falls from ST to 0.
	 */
	if (am.a1s == 0)
		am.a1s = 1;
	if (am.a2s == 0)
		am.a2s = 1;
	if (am.ds == 0)
		am.ds = 1;
	if (am.rs == 0)
		am.rs = 1;

	vol_env->npt = 6;
	vol_env->flg = XMP_ENVELOPE_ON;

	vol_env->data[0] = 0;
	vol_env->data[1] = am.l0 / 4;

	/*
	 * Startrekker increments/decrements the envelope by the stage speed
	 * until it reaches the next stage level.
	 *
	 *         ^ 
	 *         |
	 *     100 +.........o
	 *         |        /:
	 *     A2L +.......o :        x = 256 * (A2L - A1L) / (256 - A1L)
	 *         |      /: :
	 *         |     / : :
	 *     A1L +....o..:.:
	 *         |    :  : :
	 *         |    :x : :
	 *         +----+--+-+----->
	 *              |    |
	 *              |256/|
	 *               A2S
	 */

	if (am.a1l > am.l0) {
		a = am.a1l - am.l0;
		b = 256 - am.l0;
	} else {
		a = am.l0 - am.a1l;
		b = am.l0;
	}
	if (b == 0)
		b = 1;

	vol_env->data[2] = vol_env->data[0] + (256 * a) / (am.a1s * b);
	vol_env->data[3] = am.a1l / 4;

	if (am.a2l > am.a1l) {
		a = am.a2l - am.a1l;
		b = 256 - am.a1l;
	} else {
		a = am.a1l - am.a2l;
		b = am.a1l;
	}
	if (b == 0)
		b = 1;

	vol_env->data[4] = vol_env->data[2] + (256 * a) / (am.a2s * b);
	vol_env->data[5] = am.a2l / 4;

	if (am.sl > am.a2l) {
		a = am.sl - am.a2l;
		b = 256 - am.a2l;
	} else {
		a = am.a2l - am.sl;
		b = am.a2l;
	}
	if (b == 0)
		b = 1;

	vol_env->data[6] = vol_env->data[4] + (256 * a) / (am.ds * b);
	vol_env->data[7] = am.sl / 4;
	vol_env->data[8] = vol_env->data[6] + am.st;
	vol_env->data[9] = am.sl / 4;
	vol_env->data[10] = vol_env->data[8] + (256 / am.rs);
	vol_env->data[11] = 0;

	/*
	 * Implement P.FALL using pitch envelope
	 */

	if (am.p_fall) {
		freq_env->npt = 2;
		freq_env->flg = XMP_ENVELOPE_ON;
		freq_env->data[0] = 0;
		freq_env->data[1] = 0;
		freq_env->data[2] = 1024 / abs(am.p_fall);
		freq_env->data[3] = 10 * (am.p_fall < 0 ? -256 : 256);
	}

	if (libxmp_load_sample(m, NULL, SAMPLE_FLAG_NOLOAD, xxs, wave))
		return -1;

	return 0;
}

static int flt_load(struct module_data *m, HIO_HANDLE * f, const int start)
{
	struct xmp_module *mod = &m->mod;
	int i, j;
	struct xmp_event *event;
	struct mod_header mh;
	uint8 mod_event[4];
	char *tracker;
	char filename[1024];
	char buf[16];
	HIO_HANDLE *nt;
	int am_synth;

	LOAD_INIT();

	/* See if we have the synth parameters file */
	am_synth = 0;
	snprintf(filename, 1024, "%s%s.NT", m->dirname, m->basename);
	if ((nt = hio_open(filename, "rb")) == NULL) {
		snprintf(filename, 1024, "%s%s.nt", m->dirname, m->basename);
		if ((nt = hio_open(filename, "rb")) == NULL) {
			snprintf(filename, 1024, "%s%s.AS", m->dirname,
				 m->basename);
			if ((nt = hio_open(filename, "rb")) == NULL) {
				snprintf(filename, 1024, "%s%s.as", m->dirname,
					 m->basename);
				nt = hio_open(filename, "rb");
			}
		}
	}

	tracker = "Startrekker";

	if (nt) {
		if (hio_read(buf, 1, 16, nt) != 16) {
			goto err;
		}
		if (memcmp(buf, "ST1.2 ModuleINFO", 16) == 0) {
			am_synth = 1;
			tracker = "Startrekker 1.2";
		} else if (memcmp(buf, "ST1.3 ModuleINFO", 16) == 0) {
			am_synth = 1;
			tracker = "Startrekker 1.3";
		} else if (memcmp(buf, "AudioSculpture10", 16) == 0) {
			am_synth = 1;
			tracker = "AudioSculpture 1.0";
		}
	}

	hio_read(&mh.name, 20, 1, f);
	for (i = 0; i < 31; i++) {
		hio_read(&mh.ins[i].name, 22, 1, f);
		mh.ins[i].size = hio_read16b(f);
		mh.ins[i].finetune = hio_read8(f);
		mh.ins[i].volume = hio_read8(f);
		mh.ins[i].loop_start = hio_read16b(f);
		mh.ins[i].loop_size = hio_read16b(f);
	}
	mh.len = hio_read8(f);
	mh.restart = hio_read8(f);
	hio_read(&mh.order, 128, 1, f);
	hio_read(&mh.magic, 4, 1, f);

	if (mh.magic[3] == '4') {
		mod->chn = 4;
	} else {
		mod->chn = 8;
	}

	mod->ins = 31;
	mod->smp = mod->ins;
	mod->len = mh.len;
	mod->rst = mh.restart;
	memcpy(mod->xxo, mh.order, 128);

	for (i = 0; i < 128; i++) {
		if (mod->chn > 4)
			mod->xxo[i] >>= 1;
		if (mod->xxo[i] > mod->pat)
			mod->pat = mod->xxo[i];
	}

	mod->pat++;

	mod->trk = mod->chn * mod->pat;

	strncpy(mod->name, (char *)mh.name, 20);
	libxmp_set_type(m, "%s %4.4s", tracker, mh.magic);
	MODULE_INFO();

	if (libxmp_init_instrument(m) < 0)
		goto err;

	for (i = 0; i < mod->ins; i++) {
		struct xmp_instrument *xxi = &mod->xxi[i];
		struct xmp_sample *xxs = &mod->xxs[i];
		struct xmp_subinstrument *sub;

		if (libxmp_alloc_subinstrument(mod, i, 1) < 0)
			goto err;

		sub = &xxi->sub[0];

		xxs->len = 2 * mh.ins[i].size;
		xxs->lps = 2 * mh.ins[i].loop_start;
		xxs->lpe = xxs->lps + 2 * mh.ins[i].loop_size;
		xxs->flg = mh.ins[i].loop_size > 1 ? XMP_SAMPLE_LOOP : 0;
		sub->fin = (int8) (mh.ins[i].finetune << 4);
		sub->vol = mh.ins[i].volume;
		sub->pan = 0x80;
		sub->sid = i;
		xxi->rls = 0xfff;

		if (xxs->len > 0)
			xxi->nsm = 1;

		libxmp_instrument_name(mod, i, mh.ins[i].name, 22);
	}

	if (libxmp_init_pattern(mod) < 0)
		goto err;

	/* Load and convert patterns */
	D_(D_INFO "Stored patterns: %d", mod->pat);

	/* "The format you are looking for is FLT8, and the ONLY two
	 *  differences are: It says FLT8 instead of FLT4 or M.K., AND, the
	 *  patterns are PAIRED. I thought this was the easiest 8 track
	 *  format possible, since it can be loaded in a normal 4 channel
	 *  tracker if you should want to rip sounds or patterns. So, in a
	 *  8 track FLT8 module, patterns 00 and 01 is "really" pattern 00.
	 *  Patterns 02 and 03 together is "really" pattern 01. Thats it.
	 *  Oh well, I didnt have the time to implement all effect commands
	 *  either, so some FLT8 modules would play back badly (I think
	 *  especially the portamento command uses a different "scale" than
	 *  the normal portamento command, that would be hard to patch).
	 */
	for (i = 0; i < mod->pat; i++) {
		if (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)
			goto err;

		for (j = 0; j < (64 * 4); j++) {
			event = &EVENT(i, j % 4, j / 4);
			hio_read(mod_event, 1, 4, f);
			libxmp_decode_noisetracker_event(event, mod_event);
		}
		if (mod->chn > 4) {
			for (j = 0; j < (64 * 4); j++) {
				event = &EVENT(i, (j % 4) + 4, j / 4);
				hio_read(mod_event, 1, 4, f);
				libxmp_decode_noisetracker_event(event, mod_event);

				/* no macros */
				if (event->fxt == 0x0e)
					event->fxt = event->fxp = 0;
			}
		}
	}

	/* no such limit for synth instruments
	 * mod->flg |= XXM_FLG_MODRNG;
	 */

	/* Load samples */

	D_(D_INFO "Stored samples: %d", mod->smp);

	for (i = 0; i < mod->smp; i++) {
		if (mod->xxs[i].len == 0) {
			if (am_synth && is_am_instrument(nt, i)) {
				if (read_am_instrument(m, nt, i) < 0) {
					D_(D_CRIT "Missing nt file");
					goto err;
				}
			}
			continue;
		}
		if (libxmp_load_sample(m, f, SAMPLE_FLAG_FULLREP, &mod->xxs[i], NULL) <
		    0) {
			goto err;
		}
	}

	if (nt) {
		hio_close(nt);
	}

	return 0;

      err:
	if (nt) {
		hio_close(nt);
	}

	return -1;
}
