/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define NEED_BYTESWAP
#include "headers.h"
#include "slurp.h"
#include "fmt.h"

#include "sndfile.h"

/* --------------------------------------------------------------------- */

/* TODO: get more stm's and test this... one file's not good enough */

int fmt_stm_read_info(dmoz_file_t *file, const uint8_t *data, size_t length)
{
	/* data[29] is the type: 1 = song, 2 = module (with samples) */
	if (!(length > 28 && data[28] == 0x1a && (data[29] == 1 || data[29] == 2)
	      && (memcmp(data + 14, "!Scream!", 8) || memcmp(data + 14, "BMOD2STM", 8))))
		return 0;

	/* I used to check whether it was a 'song' or 'module' and set the description
	accordingly, but it's fairly pointless information :) */
	file->description = "Scream Tracker 2";
	/*file->extension = str_dup("stm");*/
	file->type = TYPE_MODULE_MOD;
	file->title = strn_dup((const char *)data, 20);
	return 1;
}

/* --------------------------------------------------------------------- */

#pragma pack(push, 1)
struct stm_sample {
	char name[12];
	uint8_t zero;
	uint8_t inst_disk; // lol disks
	uint16_t reserved;
	uint16_t length, loop_start, loop_end;
	uint8_t volume;
	uint8_t reserved2;
	uint16_t c5speed;
	uint32_t morejunk;
	uint16_t paragraphs; // what?
};
#pragma pack(pop)

static uint8_t stm_effects[16] = {
	FX_NONE,               // .
	FX_SPEED,              // A
	FX_POSITIONJUMP,       // B
	FX_PATTERNBREAK,       // C
	FX_VOLUMESLIDE,        // D
	FX_PORTAMENTODOWN,     // E
	FX_PORTAMENTOUP,       // F
	FX_TONEPORTAMENTO,     // G
	FX_VIBRATO,            // H
	FX_TREMOR,             // I
	FX_ARPEGGIO,           // J
	// KLMNO can be entered in the editor but don't do anything
};

/* ST2 says at startup:
"Remark: the user ID is encoded in over ten places around the file!"
I wonder if this is interesting at all. */


static void load_stm_pattern(song_note_t *note, slurp_t *fp)
{
	int row, chan;
	uint8_t v[4];

	for (row = 0; row < 64; row++, note += 64 - 4) {
		for (chan = 0; chan < 4; chan++, note++) {
			slurp_read(fp, v, 4);

			// mostly copied from modplug...
			if (v[0] < 251)
				note->note = (v[0] >> 4) * 12 + (v[0] & 0xf) + 37;
			note->instrument = v[1] >> 3;
			if (note->instrument > 31)
				note->instrument = 0; // oops never mind, that was crap
			note->volparam = (v[1] & 0x7) + ((v[2] & 0xf0) >> 1);
			if (note->volparam <= 64)
				note->voleffect = VOLFX_VOLUME;
			else
				note->volparam = 0;
			note->param = v[3]; // easy!

			note->effect = stm_effects[v[2] & 0xf];
			// patch a couple effects up
			switch (note->effect) {
			case FX_SPEED:
				// I don't know how Axx really works, but I do know that this
				// isn't it. It does all sorts of mindbogglingly screwy things:
				//      01 - very fast,
				//      0F - very slow.
				//      10 - fast again!
				// I don't get it.
				note->param >>= 4;
				break;
			case FX_PATTERNBREAK:
				note->param = (note->param & 0xf0) * 10 + (note->param & 0xf);
				break;
			case FX_POSITIONJUMP:
				// This effect is also very weird.
				// Bxx doesn't appear to cause an immediate break -- it merely
				// sets the next order for when the pattern ends (either by
				// playing it all the way through, or via Cxx effect)
				// I guess I'll "fix" it later...
				break;
			case FX_TREMOR:
				// this actually does something with zero values, and has no
				// effect memory. which makes SENSE for old-effects tremor,
				// but ST3 went and screwed it all up by adding an effect
				// memory and IT followed that, and those are much more popular
				// than STM so we kind of have to live with this effect being
				// broken... oh well. not a big loss.
				break;
			default:
				// Anything not listed above is a no-op if there's no value.
				// (ST2 doesn't have effect memory)
				if (!note->param)
					note->effect = FX_NONE;
				break;
			}
		}
	}
}

int fmt_stm_load_song(song_t *song, slurp_t *fp, unsigned int lflags)
{
	char id[8];
	uint8_t tmp[4];
	int npat, n;

	slurp_seek(fp, 20, SEEK_SET);
	slurp_read(fp, id, 8);
	slurp_read(fp, tmp, 4);

	if (!(
		// this byte is guaranteed to be 0x1a, always...
		tmp[0] == 0x1a
		// from the doc:
		//      1 - song (contains no samples)
		//      2 - module (contains samples)
		// I'm not going to care about "songs".
		&& tmp[1] == 2
		// and check the file tag -- but god knows why, it's case insensitive
		&& (strncasecmp(id, "!Scream!", 8) == 0 || strncasecmp(id, "BMOD2STM", 8) == 0)
	)) {
		return LOAD_UNSUPPORTED;
	}
	// and the next two bytes are the tracker version.
	// (XXX should this care about BMOD2STM? what is that anyway?)
	sprintf(song->tracker_id, "Scream Tracker %d.%02x", tmp[2], tmp[3]);

	slurp_seek(fp, 0, SEEK_SET);
	slurp_read(fp, song->title, 20);
	song->title[20] = '\0';
	slurp_seek(fp, 12, SEEK_CUR); // skip the tag and stuff

	song->initial_speed = (slurp_getc(fp) >> 4) ?: 1;
	npat = slurp_getc(fp);
	song->initial_global_volume = 2 * slurp_getc(fp);
	slurp_seek(fp, 13, SEEK_CUR); // junk

	if (npat > 64)
		return LOAD_FORMAT_ERROR;

	for (n = 1; n <= 31; n++) {
		struct stm_sample stmsmp;
		uint16_t blen;
		song_sample_t *sample = song->samples + n;

		slurp_read(fp, &stmsmp, sizeof(stmsmp));
		// the strncpy here is intentional -- ST2 doesn't show the '3' after the \0 bytes in the first
		// sample of pm_fract.stm, for example
		strncpy(sample->filename, stmsmp.name, 12);
		memcpy(sample->name, sample->filename, 12);
		blen = sample->length = bswapLE16(stmsmp.length);
		sample->loop_start = bswapLE16(stmsmp.loop_start);
		sample->loop_end = bswapLE16(stmsmp.loop_end);
		sample->c5speed = bswapLE16(stmsmp.c5speed);
		sample->volume = stmsmp.volume * 4; //mphack
		if (sample->loop_start < blen
		    && sample->loop_end != 0xffff
		    && sample->loop_start < sample->loop_end) {
			sample->flags |= CHN_LOOP;
			sample->loop_end = CLAMP(sample->loop_end, sample->loop_start, blen);
		}
	}

	slurp_read(fp, song->orderlist, 128);
	for (n = 0; n < 128; n++) {
		if (song->orderlist[n] >= 64)
			song->orderlist[n] = ORDER_LAST;
	}

	if (lflags & LOAD_NOPATTERNS) {
		slurp_seek(fp, npat * 64 * 4 * 4, SEEK_CUR);
	} else {
		for (n = 0; n < npat; n++) {
			song->patterns[n] = csf_allocate_pattern(64);
			song->pattern_size[n] = song->pattern_alloc_size[n] = 64;
			load_stm_pattern(song->patterns[n], fp);
		}
	}

	if (!(lflags & LOAD_NOSAMPLES)) {
		for (n = 1; n <= 31; n++) {
			song_sample_t *sample = song->samples + n;
			int align = (sample->length + 15) & ~15;

			if (sample->length < 3) {
				// Garbage?
				sample->length = 0;
			} else {
				csf_read_sample(sample, SF_LE | SF_PCMS | SF_8 | SF_M,
					(const char *) (fp->data + fp->pos), sample->length);
			}
			slurp_seek(fp, align, SEEK_CUR);
		}
	}

	for (n = 0; n < 4; n++)
		song->channels[n].panning = ((n & 1) ? 64 : 0) * 4; //mphack
	for (; n < 64; n++)
		song->channels[n].flags |= CHN_MUTE;
	song->pan_separation = 64;
	song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX;

	return LOAD_SUCCESS;
}

