#    /*

    Copyright (C) 2001 Stefan Westerfeld
                       stefan@space.twc.de

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
   
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    */

#include "artsmodulessynth.h"
#include <sys/stat.h>
#include <stdsynthmodule.h>
#include <unistd.h>
#include <math.h>
#include <debug.h>
#include <cache.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

using namespace std;

namespace Arts {

namespace PatchLoader {
	typedef unsigned char byte;
	typedef unsigned short int word;
	typedef unsigned int dword;
	typedef char sbyte;
	typedef short int sword;
	typedef int sdword;

	static int pos = 0;
	static int apos = 0;

	inline void xRead(FILE *file, int len, void *data)
	{
		//	printf("(0x%2x) - 0x%02x  ... reading %d bytes\n",apos,pos,len);
		pos += len;
		apos += len;
		if(fread(data, len, 1, file) != 1)
			fprintf(stdout, "short read\n");
	}

	inline void skip(FILE *file, int len)
	{
		//	printf("(0x%2x) - 0x%02x  ... skipping %d bytes\n",apos,pos,len);
		pos += len;
		apos += len;
		while(len > 0)
		{
			char junk;
			if(fread(&junk, 1, 1, file) != 1)
				fprintf(stdout, "short read\n");
			len--;
		}
	}


	inline void readBytes(FILE *file, unsigned char *bytes, int len)
	{
		xRead(file, len, bytes);
	}

	inline void readString(FILE *file, char *str, int len)
	{
		xRead(file, len, str);
	}

	/* readXXX with sizeof(xxx) == 1 */
	inline void readByte(FILE *file, byte& b)
	{
		xRead(file, 1, &b);
	}

	/* readXXX with sizeof(xxx) == 2 */
	inline void readWord(FILE *file, word& w)
	{
		byte h, l;
		xRead(file, 1, &l);
		xRead(file, 1, &h);
		w = (h << 8) + l;
	}

	inline void readSWord(FILE *file, sword& sw)
	{
		word w;
		readWord(file, w);
		sw = (sword)w;
	}

	/* readXXX with sizeof(xxx) == 4 */
	inline void readDWord(FILE *file, dword& dw)
	{
		byte h, l, hh, hl;
		xRead(file, 1, &l);
		xRead(file, 1, &h);
		xRead(file, 1, &hl);
		xRead(file, 1, &hh);
		dw = (hh << 24) + (hl << 16) + (h << 8) + l;
	}

	struct PatHeader {
		char id[12];					/* ID='GF1PATCH110' */
		char manufacturer_id[10];		/* Manufacturer ID */
		char description[60];			/* Description of the contained Instruments
										   or copyright of manufacturer. */
		byte instruments;				/* Number of instruments in this patch */
		byte voices;					/* Number of voices for sample */
		byte channels;					/* Number of output channels
										   (1=mono,2=stereo) */
		word waveforms;					/* Number of waveforms */
		word mastervolume;				/* Master volume for all samples */
		dword size;						/* Size of the following data */
		char reserved[36];				/* reserved */

		PatHeader(FILE *file)
		{
			readString(file, id, 12);
			readString(file, manufacturer_id, 10);
			readString(file, description, 60);
			/*		skip(file, 2);*/

			readByte(file, instruments);
			readByte(file, voices);
			readByte(file, channels);

			readWord(file, waveforms);
			readWord(file, mastervolume);
			readDWord(file, size);

			readString(file, reserved, 36);
		}
	};

	struct PatInstrument {
		word	number;
		char	name[16];
		dword 	size;					/* Size of the whole instrument in bytes. */
		byte	layers;
		char	reserved[40];

		/* layer? */
		word	layerUnknown;
		dword	layerSize;
		byte	sampleCount;			/* number of samples in this layer (?) */
		char	layerReserved[40];

		PatInstrument(FILE *file)
		{
			readWord(file, number);
			readString(file, name, 16);
			readDWord(file, size);
			readByte(file, layers);
			readString(file, reserved, 40);

			/* layer: (?) */
			readWord(file, layerUnknown);
			readDWord(file, layerSize);
			readByte(file, sampleCount);
			readString(file, reserved, 40);
		}
	};

	struct PatPatch {
		char	filename[7];			/* Wave file name */
		byte	fractions;				/* Fractions */
		dword	wavesize;				/* Wave size.
										   Size of the wave digital data */
		dword	loopStart;
		dword	loopEnd;
		word	sampleRate;
		dword	minFreq;
		dword	maxFreq;
		dword	origFreq;
		sword	fineTune;
		byte	balance;
		byte	filterRate[6];
		byte	filterOffset[6];
		byte	tremoloSweep;
		byte	tremoloRate;
		byte	tremoloDepth;
		byte	vibratoSweep;
		byte	vibratoRate;
		byte	vibratoDepth;
		byte	waveFormat;
		sword	freqScale;
		word	freqScaleFactor;
		char	reserved[36];

		PatPatch(FILE *file)
		{
			readString(file, filename, 7);
			readByte(file, fractions);
			readDWord(file, wavesize);
			readDWord(file, loopStart);
			readDWord(file, loopEnd);
			readWord(file, sampleRate);
			readDWord(file, minFreq);
			readDWord(file, maxFreq);
			readDWord(file, origFreq);
			readSWord(file, fineTune);
			readByte(file, balance);
			readBytes(file, filterRate, 6);
			readBytes(file, filterOffset, 6);
			readByte(file, tremoloSweep);
			readByte(file, tremoloRate);
			readByte(file, tremoloDepth);
			readByte(file, vibratoSweep);
			readByte(file, vibratoRate);
			readByte(file, vibratoDepth);
			readByte(file, waveFormat);
			readSWord(file, freqScale);
			readWord(file, freqScaleFactor);
			readString(file, reserved, 36);
		}
	};
}

class CachedPat : public CachedObject
{
protected:
	struct stat oldstat;
	string filename;
	bool initOk;
	long dataSize;

	CachedPat(Cache *cache, const string& filename);
	~CachedPat();

public:

	struct Data {
		PatchLoader::PatPatch patch;
		mcopbyte *rawdata;
		Data(FILE *file)
			: patch(file)
		{
			rawdata = new mcopbyte[patch.wavesize];
			fread(rawdata, 1, patch.wavesize, file);

			// sign conversion only for 16bit!
			if(patch.waveFormat & (1 << 1))
			{
				for(unsigned int i = 1; i < patch.wavesize; i+=2)
					rawdata[i] ^= 0x80;
			}

			// unfold ping-pong loops
			if(patch.waveFormat & (1 << 3))
			{
				int looplen = patch.loopEnd - patch.loopStart;
				arts_assert(looplen > 0);

				mcopbyte *newdata = new mcopbyte[patch.wavesize + looplen];

				// copy head
				memcpy(&newdata[0], &rawdata[0], patch.loopStart + looplen);

				// 16 bit unfolding only:
				for(int i=0; i<looplen; i+=2)
				{
					newdata[patch.loopStart+looplen+i] =
						newdata[patch.loopStart+looplen-i-2];
					newdata[patch.loopStart+looplen+i+1] =
						newdata[patch.loopStart+looplen-i-1];
				}

				// copy tail:
				memcpy(&newdata[patch.loopStart+2*looplen],
					   &rawdata[patch.loopStart+looplen],
					   patch.wavesize - patch.loopEnd);

				delete[] rawdata;
				rawdata = newdata;

				patch.wavesize += looplen;
				patch.loopEnd += looplen;
				patch.waveFormat &= ~(1 << 3);
			}
		}
		~Data()
		{
			delete[] rawdata;
		}
	};

	list<Data*> dList;

	static CachedPat *load(Cache *cache, const string& filename);
	/**
	 * validity test for the cache - returns false if the object is having
	 * reflecting the correct contents anymore (e.g. if the file on the
	 * disk has changed), and there is no point in keeping it in the cache any
	 * longer
	 */
	bool isValid();
	/**
	 * memory usage for the cache
	 */
	int memoryUsage();
};

CachedPat *CachedPat::load(Cache *cache, const string& filename)
{
	CachedPat *pat;

	pat = (CachedPat *)cache->get(string("CachedPat:")+filename);
	if(!pat) {
		pat = new CachedPat(cache, filename);

		if(!pat->initOk)		// loading failed
		{
			pat->decRef();
			return 0;
		}
	}

	return pat;
}

bool CachedPat::isValid()
{
	if(!initOk)
		return false;

	struct stat newstat;

	lstat(filename.c_str(),&newstat);
	return(newstat.st_mtime == oldstat.st_mtime);
}

int CachedPat::memoryUsage()
{
	return dataSize;
}

CachedPat::CachedPat(Cache *cache, const string& filename)
	: CachedObject(cache), filename(filename), initOk(false), dataSize(0)
{
	setKey(string("CachedPat:")+filename);

	if(lstat(filename.c_str(),&oldstat) == -1)
	{
		arts_info("CachedPat: Can't stat file '%s'", filename.c_str());
		return;
	}

	FILE *patfile = fopen(filename.c_str(), "r");
	if(patfile)
	{
		//PatchLoader::PatHeader header(patfile);
		PatchLoader::PatInstrument ins(patfile);

		for(int i=0;i<ins.sampleCount;i++)
		{
			Data *data = new Data(patfile);
			dList.push_back(data);
			dataSize += data->patch.wavesize;
		}
		fclose(patfile);

		arts_debug("loaded pat %s",filename.c_str());
		arts_debug("  %d patches, datasize total is %d bytes",
											ins.sampleCount, dataSize);

		initOk = true;
	}
}

CachedPat::~CachedPat()
{
	while(!dList.empty())
	{
		delete dList.front();
		dList.pop_front();
	}
}

class Synth_PLAY_PAT_impl : virtual public Synth_PLAY_PAT_skel,
							virtual public StdSynthModule
{
protected:
	string _filename;
	CachedPat *pat;
	CachedPat::Data *selected;
	float fpos;

public:
	Synth_PLAY_PAT_impl()
	{
		pat = 0;
		selected = 0;
	}

	void unload()
	{
		if(pat)
		{
			pat->decRef();
			pat = 0;
		}
	}

	~Synth_PLAY_PAT_impl()
	{
		unload();
	}

	void streamStart()
	{
		fpos = 0.0;
		selected = 0;
	}
	
	void calculateBlock(unsigned long samples)
	{
		/* the normal offset is 60
		   60 = 12*5
		   so we scale with 2^5 = 32
		 */
		float freq = frequency[0]; /* * 32.0 / 2.0; * why /2.0? */
		int ifreq = (int)(freq * 1024.0);
		if(!selected && pat)
		{
			int bestdiff = 20000 * 1024;

			list<CachedPat::Data*>::iterator i;
			for(i = pat->dList.begin(); i != pat->dList.end(); i++)
			{
				int diff = ::abs(double(ifreq - (*i)->patch.origFreq));
				if(diff < bestdiff)
				{
					selected = *i;
					bestdiff = diff;
				}
			}

			/* drums */
			if(selected && selected->patch.freqScaleFactor == 0)
				ifreq = selected->patch.origFreq;
		}
		if(selected)
		{
			const PatchLoader::PatPatch& patch = selected->patch;

			/*
			 * thats just a *guess*, I have no idea how tuning actually
			 * should work
			 */
#if 0
			float tonetune = float(pat.fineTune)/float(pat.freqScaleFactor);
			float tuning = pow(2.0, tonetune/12.0);
			freq *= tuning;
#endif
			//printf("tonetune: %f\n",tonetune);
			//printf("tuning: %f\n",tuning);

			float step = double(patch.sampleRate)/samplingRateFloat *
				  		 float(ifreq) / float(patch.origFreq);
			for(unsigned int i = 0; i < samples; i++)
			{
				// ASSUME 16bit signed, native byte order
				int ifpos = int(fpos)*2;

				//                     looped?      bidi?     backw?
				if((patch.waveFormat & ((1 << 2) + (1 << 3) + (1 << 4)))
							== (1 << 2))
				{
					while(ifpos >= signed(patch.loopEnd))
					{
						ifpos -= (patch.loopEnd - patch.loopStart);
						fpos -= (patch.loopEnd - patch.loopStart)/2;
					}
				}
				
				short int *x = (short int *)&selected->rawdata[ifpos];
				float sample = (ifpos >= 0 && ifpos < signed(patch.wavesize))?
										x[0]/32768.0:0.0;
				float sample1 = ((ifpos+2) >= 0 && (ifpos+2) < signed(patch.wavesize))?
										x[1]/32768.0:0.0;
				float error = fpos - (int)fpos;
				outvalue[i] = sample * (1.0-error) + sample1 * error;

				fpos += step;
			}
		}
		else
		{
			for(unsigned int i = 0; i < samples; i++)
				outvalue[i] = 0.0;
		}
	}

	void clearDList()
	{
		selected = 0;

	}

	string filename() { return _filename; }
	void filename(const string& newFile)
	{
		if(newFile == _filename) return;
	
		unload();
		pat = CachedPat::load(Cache::the(), newFile);

		_filename = newFile;
		filename_changed(newFile);
	}
};

REGISTER_IMPLEMENTATION(Synth_PLAY_PAT_impl);

}
