#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "module.h"

/**********************************************************************
The following list summarizes several MOD formats. All formats except
the original are basically the same.  They can be distiguinshed using
the MOD signature. Protracker extended format (M!K!) allows more than
64 tracks. StarTrekker and TakeTracker allow more than 4 channels.

The original format has no signature (dumb). I assume this format when
all other possibilites fail. This is potentially dangerous so you have
been warned. The original contains 15 samples where all others contain
31 samples. This makes the internal layout completely different.

Signatures are always 4 bytes. This conveniently means you can store
signatures with one 32-bit integer. Comparing the file signature with
known signatures then becomes an equality test. Storing the signatures
as 4-byte strings wouldnt be nearly as elegant.
**********************************************************************/

struct signatureinfo
{
	int patternoffset;
	int layoutoffset;
	int numchannels;
	int numsamples;
	int signature;
}
KnownSignatures[] =
{
	1084, 950, 4,  31, 0x2e4b2e4d,		/* M.K. */
	1084, 950, 4,  31, 0x214b214d,		/* M!K! */
	1084, 950, 4,  31, 0x34544c46,		/* FLT4 */
	1084, 950, 8,  31, 0x38544c46,		/* FLT8 */
	1084, 950, 4,  31, 0x4e484334,		/* 4CHN */
	1084, 950, 6,  31, 0x4e484336,		/* 6CHN */
	1084, 950, 8,  31, 0x4e484338,		/* 8CHN */
	1084, 950, 16, 31, 0x48433631,		/* 16CH */
	1084, 950, 32, 31, 0x48433233,		/* 32CH */
	600,  470, 4,  15, 0x00000000		/* original */
};

/**********************************************************************
ModLoad() will read the MOD file from disk into memory. The file is
read into one contiguous chunk of memory. Then analysis is performed
and indexes into the chunk created. This makes loading fast and simple
while allowing later routines to have predetermined information.
**********************************************************************/

void ModLoad(moduleinfo * m, char * filename)
{
	//////////////////////////////////////////////////////////////
	// The file is opened and the length determined. The memory
	// then required is allocated for MOD storage. The file then
	// gets rewound and completely read. Closing the file then
	// ceases all necessary disk i/o.
	//////////////////////////////////////////////////////////////
	int mfd, mlen;
	if ((mfd = open(filename, O_RDONLY, 0)) == -1)
	{
		printf("fatal: cannot open %s\n", filename);
		exit(1);
	}
	if ((mlen = lseek(mfd, 0L, 2)) == -1)
	{
		printf("fatal: cannot seek %s\n", filename);
		exit(1);
	}
	if (lseek(mfd, 0L, 0) == -1)
	{
		printf("fatal: cannot seek %s\n", filename);
		exit(1);
	}
	if ((m->rawtitle = (char *) malloc(mlen)) == NULL)
	{
		printf("fatal: cannot allocate memory\n");
		exit(1);
	}
	if (read(mfd, m->rawtitle, mlen) == -1)
	{
		printf("fatal: cannot read %s\n", filename);
		exit(1);
	}
	if (close(mfd) == -1)
	{
		printf("fatal: cannot close %s\n", filename);
		exit(1);
	}

	//////////////////////////////////////////////////////////////
	// The signature is 1080 bytes into the file. The scanning
	// loop will terminate with either the correct signature or
	// the default original if no signature is found. In either
	// case the signatureinfo pointer references the MODs layout.
	//////////////////////////////////////////////////////////////
	for (signatureinfo * si = KnownSignatures; si->signature != 0; si++)
		if (si->signature == *(long *)(m->rawtitle + 1080))
			break;

	//////////////////////////////////////////////////////////////
	// The rawtable is 128 bytes long and located 952 (or 602)
	// bytes into the file. The number of unique patterns equals
	// the largest entry. Patterns begin from 0 so entries are
	// incremented before comparison and storage.
	//////////////////////////////////////////////////////////////
	for (int i = m->numpatterns = 0; i < 128; i++)
		if (m->numpatterns < m->rawtitle[si->layoutoffset+2+i] + 1)
			m->numpatterns = m->rawtitle[si->layoutoffset+2+i] + 1;

	//////////////////////////////////////////////////////////////
	// Information about the MODs layout is now stored into the
	// moduleinfo pointer. Most of the information is determined
	// from the signature. The raw sample data (noise) begins
	// after the pattern data. This gets calculated using the
	// number and size of patterns.
	//////////////////////////////////////////////////////////////
	m->numpositions = m->rawtitle[si->layoutoffset];
	m->numchannels = si->numchannels;
	m->numsamples = si->numsamples;
	m->rawsample = m->rawtitle + 20;
	m->rawtable = m->rawtitle + si->layoutoffset + 2;
	m->rawpattern = m->rawtitle + si->patternoffset;
	m->rawnoise = m->rawpattern + m->numpatterns * m->numchannels * 256;

	//////////////////////////////////////////////////////////////
	// Information stored about samples is modified. Reasons are
	// both technical and aesthetic.
	//     1) values are big-endian and I need little-endian
	//     2) values are in words and I prefer bytes
	//     3) several arrays looks neater
	//
	// The starting points of the various sample noises are
	// determined and stored here. The noiseptr pointer gets
	// incremented after each sample. This works because each
	// samples start follows the previous samples end.
	//
	// I experimented with struct/field relationships, but found
	// the "portable" C code inelegant and _still_ unportable. I
	// therefore use direct bit manipulation to extract values.
	// This will cause problems with non-32 bit compilers and
	// little-endian machines. You have been warned.
	//////////////////////////////////////////////////////////////
	unsigned char * noiseptr = m->rawnoise;
	for (int j = i = 0; i < m->numsamples; i++, j += 30)
	{
		m->samplelength[i] = m->rawsample[j+22]<<9 | m->rawsample[j+23]<<1;
		m->samplereppnt[i] = m->rawsample[j+26]<<9 | m->rawsample[j+27]<<1;
		m->samplereplen[i] = m->rawsample[j+28]<<9 | m->rawsample[j+29]<<1;
		m->samplefntune[i] = m->rawsample[j+24];
		m->samplevolume[i] = m->rawsample[j+25];
		m->samplenoise[i] = noiseptr;
		noiseptr += m->samplelength[i];
	}

	//////////////////////////////////////////////////////////////
	// A check is made to ensure the MOD isnt corrupt. If the
	// file contains less data than the header indicates then
	// continuing is dangerous. We could read (or worse write)
	// memory which isnt ours. The noiseptr currently contains
	// where we think the file should have ended.
	//////////////////////////////////////////////////////////////
	if (noiseptr - m->rawtitle != mlen)
	{
		printf("fatal: corrupt MOD %s\n", filename);
		exit(1);
	}

	//////////////////////////////////////////////////////////////
	// Noise data on Amigas are signed characters. Noise data on
	// my machine is unsigned characters. This short loop runs
	// backwards performing the conversion on every sample. This
	// isnt portable but I havent seen any machine besides Amiga
	// use signed characters.
	//////////////////////////////////////////////////////////////
	while (--noiseptr >= m->rawnoise)
		*noiseptr ^= 0x80;

	//////////////////////////////////////////////////////////////
	// The first word of sample noise gets used for default
	// repeating. Unset repeats can cause nasty clicking noises
	// which many MOD implementors avoid by skipping the first
	// 2 bytes. My solution just makes the first word silent.
	//////////////////////////////////////////////////////////////
	for (i = 0; i < m->numchannels; i++)
		if (m->samplelength[i] >= 2)
			m->samplenoise[i][0] = m->samplenoise[i][1] = 0x80;

	//////////////////////////////////////////////////////////////
	// Some MODs incorrectly allow the sample repeat space to
	// exceed the sample data space. This causes mysterious notes
	// or complete garbage to play. Incorrect MODs Ive found have
	// been corrected by clipping the sample repeat space.
	//////////////////////////////////////////////////////////////
	for (i = 0; i < m->numchannels; i++)
	{
		if (m->samplereppnt[i] > m->samplelength[i])
			m->samplereppnt[i] = m->samplelength[i];
		if (m->samplereplen[i] > m->samplelength[i] - m->samplereppnt[i])
			m->samplereplen[i] = m->samplelength[i] - m->samplereppnt[i];
	}
}
