// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif

#include <stdio.h>
#include <stdbool.h>
#include "ft2_header.h"
#include "ft2_audio.h"
#include "ft2_gui.h"
#include "ft2_mouse.h"
#include "ft2_sample_ed.h"
#include "ft2_module_loader.h"

/* These savers are directly ported, so they should act identical to FT2
** except for some very minor changes. */

static SDL_Thread *thread;

static uint16_t packPatt(uint8_t *pattPtr, uint16_t numRows);

// ft2_replayer.c
extern const char modSig[32][5];
extern const uint16_t amigaPeriod[12*8];

bool saveXM(UNICHAR *filenameU)
{
	uint8_t *pattPtr;
	int16_t ap, ai, i, j, k, a;
	uint16_t b, c;
	size_t result;
	songHeaderTyp h;
	patternHeaderTyp ph;
	instrHeaderTyp ih;
	sampleTyp *srcSmp;
	sampleHeaderTyp *dstSmp;
	FILE *f;

	f = UNICHAR_FOPEN(filenameU, "wb");
	if (f == NULL)
	{
		okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?");
		return false;
	}

	memcpy(h.sig, "Extended Module: ", 17);
	memset(h.name, ' ', 20);
	h.name[20] = 0x1A;
	memcpy(h.name, song.name, strlen(song.name));
	memcpy(h.progName, PROG_NAME_STR, 20);
	h.ver = 0x0104;
	h.headerSize = 20 + 256;
	h.len = song.len;
	h.repS = song.repS;
	h.antChn = song.antChn;
	h.defTempo = song.tempo;
	h.defSpeed = song.speed;

	// count number of patterns
	ap = MAX_PATTERNS;
	do
	{
		if (patternEmpty(ap - 1))
			ap--;
		else
			break;
	}
	while (ap > 0);
	h.antPtn = ap;

	// count number of instruments
	ai = 128;
	while (ai > 0 && getUsedSamples(ai) == 0 && song.instrName[ai][0] == '\0')
		ai--;
	h.antInstrs = ai;

	h.flags = linearFrqTab;
	memcpy(h.songTab, song.songTab, sizeof (song.songTab));

	if (fwrite(&h, sizeof (h), 1, f) != 1)
	{
		fclose(f);
		okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
		return false;
	}

	for (i = 0; i < ap; i++)
	{
		if (patternEmpty(i))
		{
			if (patt[i] != NULL)
			{
				free(patt[i]);
				patt[i] = NULL;
			}

			pattLens[i] = 64;
		}

		ph.patternHeaderSize = sizeof (patternHeaderTyp);
		ph.pattLen = pattLens[i];
		ph.typ = 0;

		if (patt[i] == NULL)
		{
			ph.dataLen = 0;
			if (fwrite(&ph, ph.patternHeaderSize, 1, f) != 1)
			{
				fclose(f);
				okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
				return false;
			}
		}
		else
		{
			c = packPatt((uint8_t *)patt[i], pattLens[i]);
			b = pattLens[i] * TRACK_WIDTH;
			ph.dataLen = c;

			result = fwrite(&ph, ph.patternHeaderSize, 1, f);
			result += fwrite(patt[i], ph.dataLen, 1, f);

			pattPtr = (uint8_t *)patt[i];

			memcpy(&pattPtr[b-c], patt[i], c);
			unpackPatt(pattPtr, b - c, pattLens[i], song.antChn);
			clearUnusedChannels(patt[i], pattLens[i], song.antChn);

			if (result != 2) // write was not OK
			{
				fclose(f);
				okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
				return false;
			}
		}
	}

	for (i = 1; i <= ai; i++)
	{
		if (instr[i] == NULL)
			j = 0;
		else
			j = i;

		a = getUsedSamples(i);

		memset(ih.name, 0, 22);
		memcpy(ih.name, song.instrName[i], strlen(song.instrName[i]));

		ih.typ = 0;
		ih.antSamp = a;
		ih.sampleSize = sizeof (sampleHeaderTyp);

		if (a > 0)
		{
			memcpy(ih.ta, instr[j], INSTR_SIZE);
			ih.instrSize = INSTR_HEADER_SIZE;
			
			for (k = 1; k <= a; k++)
			{
				srcSmp = &instr[j]->samp[k-1];
				dstSmp = &ih.samp[k-1];

				memset(dstSmp->name, ' ', 22);

				memcpy(dstSmp, srcSmp, 12+4+2 + strlen(srcSmp->name));
				if (srcSmp->pek == NULL)
					dstSmp->len = 0;
			}
		}
		else
		{
			ih.instrSize = 22 + 11;
		}

		if (fwrite(&ih, ih.instrSize + (a * sizeof (sampleHeaderTyp)), 1, f) != 1)
		{
			fclose(f);
			okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
			return false;
		}

		for (k = 1; k <= a; k++)
		{
			srcSmp = &instr[j]->samp[k-1];
			if (srcSmp->pek != NULL)
			{
				restoreSample(srcSmp);
				samp2Delta(srcSmp->pek, srcSmp->len, srcSmp->typ);

				result = fwrite(srcSmp->pek, 1, srcSmp->len, f);

				delta2Samp(srcSmp->pek, srcSmp->len, srcSmp->typ);
				fixSample(srcSmp);

				if (result != (size_t)srcSmp->len) // write not OK
				{
					fclose(f);
					okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
					return false;
				}
			}
		}
	}

	removeSongModifiedFlag();

	fclose(f);

	editor.diskOpReadDir = true; // force diskop re-read

	setMouseBusy(false);
	return true;
}

static bool saveMOD(UNICHAR *filenameU)
{
	bool test, tooManyInstr, incompatEfx, noteUnderflow;
	int8_t *srcPtr, *dstPtr;
	uint8_t ton, inst, pattBuff[64*4*32];
	int16_t a, i, ap;
	int32_t j, k, l1, l2, l3, writeLen, bytesToWrite, bytesWritten;
	FILE *f;
	instrTyp *ins;
	sampleTyp *smp;
	tonTyp *t;
	songMOD31HeaderTyp hm;

	tooManyInstr = false;
	incompatEfx = false;
	noteUnderflow = false;

	if (linearFrqTab)
		okBoxThreadSafe(0, "System message", "Linear frequency table used!");

	// sanity checking

	test = false;
	if (song.len > 128)
		test = true;

	for (i = 100; i < 256; i++)
	{
		if (patt[i] != NULL)
		{
			test = true;
			break;
		}
	}
	if (test) okBoxThreadSafe(0, "System message", "Too many patterns!");

	for (i = 32; i <= 128; i++)
	{
		if (getRealUsedSamples(i) > 0)
		{
			okBoxThreadSafe(0, "System message", "Too many instruments!");
			break;
		}
	}

	test = false;
	for (i = 1; i <= 31; i++)
	{
		ins = instr[i];
		if (ins == NULL)
			continue;

		smp = &ins->samp[0];

		j = getRealUsedSamples(i);
		if (j > 1)
		{
			test = true;
			break;
		}

		if (j == 1)
		{
			if (smp->len > 65534 || ins->fadeOut != 0 || ins->envVTyp != 0 || ins->envPTyp != 0 ||
				(smp->typ & 3) == 2 || smp->relTon != 0 || ins->midiOn)
			{
				test = true;
				break;
			}
		}
	}
	if (test) okBoxThreadSafe(0, "System message", "Incompatible instruments!");

	for (i = 0; i < 99; i++)
	{
		if (patt[i] != NULL)
		{
			if (pattLens[i] != 64)
			{
				okBoxThreadSafe(0, "System message", "Unable to convert module. (Illegal pattern length)");
				return false;
			}

			for (j = 0; j < 64; j++)
			{
				for (k = 0; k < song.antChn; k++)
				{
					t = &patt[i][(j * MAX_VOICES) + k];

					if (t->instr > 31)
						tooManyInstr = true;

					if (t->effTyp > 15 || t->vol != 0)
						incompatEfx = true;

					// added security that wasn't present in FT2
					if (t->ton > 0 && t->ton < 10)
						noteUnderflow = true;
				}
			}
		}
	}
	if (tooManyInstr)  okBoxThreadSafe(0, "System message", "Instrument(s) above 31 was found in pattern data!");
	if (incompatEfx)   okBoxThreadSafe(0, "System message", "Incompatible effect(s) was found in pattern data!");
	if (noteUnderflow) okBoxThreadSafe(0, "System message", "Note(s) below A-0 were found in pattern data!");

	// setup header buffer
	memset(&hm, 0, sizeof (hm));
	memcpy(hm.name, song.name, sizeof (hm.name));
	hm.len = (uint8_t)song.len;
	if (hm.len > 128) hm.len = 128;
	hm.repS = (uint8_t)song.repS;
	if (hm.repS > 127) hm.repS = 0;
	memcpy(hm.songTab, song.songTab, song.len);

	// calculate number of patterns
	ap = 0;
	for (i = 0; i < song.len; i++)
	{
		if (song.songTab[i] > ap)
			ap = song.songTab[i];
	}

	if (song.antChn == 4)
		memcpy(hm.sig, (ap > 64) ? "M!K!" : "M.K.", 4);
	else
		memcpy(hm.sig, modSig[song.antChn-1], 4);

	// read sample information into header buffer
	for (i = 1; i <= 31; i++)
	{
		songMODInstrHeaderTyp *modIns = &hm.instr[i-1];

		memcpy(modIns->name, song.instrName[i], sizeof (modIns->name));
		if (instr[i] != NULL && getRealUsedSamples(i) != 0)
		{
			smp = &instr[i]->samp[0];

			l1 = smp->len / 2;
			l2 = smp->repS / 2;
			l3 = smp->repL / 2;

			if (smp->typ & 16)
			{
				l1 /= 2;
				l2 /= 2;
				l3 /= 2;
			}

			if (l1 > 32767)
				l1 = 32767;

			if (l2 > l1)
				l2 = l1;

			if (l2+l3 > l1)
				l3 = l1 - l2;

			// FT2 bug-fix
			if (l3 < 1)
			{
				l2 = 0;
				l3 = 1;
			}

			modIns->len = (uint16_t)(SWAP16(l1));
			modIns->fine = ((smp->fine + 128) >> 4) ^ 8;
			modIns->vol = smp->vol;

			if ((smp->typ & 3) == 0)
			{
				modIns->repS = 0;
				modIns->repL = SWAP16(1);
			}
			else
			{
				modIns->repS = (uint16_t)(SWAP16(l2));
				modIns->repL = (uint16_t)(SWAP16(l3));
			}
		}

		// FT2 bugfix: never allow replen being below 2 (1)
		if (SWAP16(modIns->repL) < 1)
		{
			modIns->repS = SWAP16(0);
			modIns->repL = SWAP16(1);
		}
	}

	f = UNICHAR_FOPEN(filenameU, "wb");
	if (f == NULL)
	{
		okBoxThreadSafe(0, "System message", "Error opening file for saving, is it in use?");
		return false;
	}

	// write header
	if (fwrite(&hm, 1, sizeof (hm), f) != sizeof (hm))
	{
		okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
		goto modSaveError;
	}

	// write pattern data
	for (i = 0; i <= ap; i++)
	{
		if (patt[i] == NULL)
		{
			// empty pattern
			memset(pattBuff, 0, song.antChn * (64 * 4));
		}
		else
		{
			a = 0;
			for (j = 0; j < 64; j++)
			{
				for (k = 0; k < song.antChn; k++)
				{
					t = &patt[i][(j * MAX_VOICES) + k];

					inst = t->instr;
					ton = t->ton;

					// FT2 bugfix: prevent overflow
					if (inst > 31)
						inst = 0;

					// FT2 bugfix: convert note-off into no note
					if (ton == 97)
						ton = 0;

					// FT2 bugfix: clamp notes below 10 (A-0) to prevent 12-bit period overflow
					if (ton > 0 && ton < 10)
						ton = 10;

					if (ton == 0)
					{
						pattBuff[a+0] = inst & 0xF0;
						pattBuff[a+1] = 0;
					}
					else
					{
						pattBuff[a+0] = (inst & 0xF0) | ((amigaPeriod[ton-1] >> 8) & 0x0F);
						pattBuff[a+1] = amigaPeriod[ton-1] & 0xFF;
					}

					// FT2 bugfix: if effect is overflowing (0xF in .MOD), set effect and param to 0
					if (t->effTyp > 0x0F)
					{
						pattBuff[a+2] = (inst & 0x0F) << 4;
						pattBuff[a+3] = 0;
					}
					else
					{
						pattBuff[a+2] = ((inst & 0x0F) << 4) | (t->effTyp & 0x0F);
						pattBuff[a+3] = t->eff;
					}

					a += 4;
				}
			}
		}

		if (fwrite(pattBuff, 1, song.antChn * (64 * 4), f) != (size_t)(song.antChn * (64 * 4)))
		{
			okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
			goto modSaveError;
		}
	}

	// write sample data
	for (i = 0; i < 31; i++)
	{
		if (instr[1+i] == NULL || getRealUsedSamples(1+i) == 0)
			continue;

		smp = &instr[1+i]->samp[0];
		if (smp->pek == NULL || smp->len <= 0)
			continue;

		restoreSample(smp);

		l1 = smp->len / 2;
		if (smp->typ & 16) // 16-bit sample (convert to 8-bit)
		{
			if (l1 > 65534)
				l1 = 65534;

			// let's borrow "pattBuff" here
			dstPtr = (int8_t *)pattBuff;

			writeLen = l1;
			bytesWritten = 0;
			while (bytesWritten < writeLen) // write in 8K blocks
			{
	 			bytesToWrite = sizeof (pattBuff);
				if (bytesWritten+bytesToWrite > writeLen)
					bytesToWrite = writeLen - bytesWritten;

				srcPtr = &smp->pek[(bytesWritten << 1) + 1]; // +1 to align to high byte
				for (j = 0; j < bytesToWrite; j++)
					dstPtr[j] = srcPtr[j << 1];

				if (fwrite(dstPtr, 1, bytesToWrite, f) != (size_t)bytesToWrite)
				{
					fixSample(smp);
					okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
					goto modSaveError;
				}

				bytesWritten += bytesToWrite;
			}
		}
		else
		{
			// 8-bit sample

			if (l1 > 32767)
				l1 = 32767;
			l1 *= 2;

			if (fwrite(smp->pek, 1, l1, f) != (size_t)l1)
			{
				fixSample(smp);
				okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
				goto modSaveError;
			}
		}

		fixSample(smp);
	}

	fclose(f);
	removeSongModifiedFlag();

	editor.diskOpReadDir = true; // force diskop re-read

	setMouseBusy(false);
	return true;

modSaveError:
	fclose(f);
	return false;
}

static int32_t SDLCALL saveMusicThread(void *ptr)
{
	(void)ptr;

	assert(editor.tmpFilenameU != NULL);
	if (editor.tmpFilenameU == NULL)
		return false;

	pauseAudio();

	if (editor.moduleSaveMode == 1)
		saveXM(editor.tmpFilenameU);
	else
		saveMOD(editor.tmpFilenameU);

	resumeAudio();
	return true;
}

void saveMusic(UNICHAR *filenameU)
{
	UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);

	mouseAnimOn();
	thread = SDL_CreateThread(saveMusicThread, NULL, NULL);
	if (thread == NULL)
	{
		okBoxThreadSafe(0, "System message", "Couldn't create thread!");
		return;
	}

	SDL_DetachThread(thread);
}

static uint16_t packPatt(uint8_t *pattPtr, uint16_t numRows)
{
	uint8_t bytes[5], packBits, *writePtr, *firstBytePtr;
	uint16_t totalPackLen;

	totalPackLen = 0;

	if (pattPtr == NULL)
		return 0;

	writePtr = pattPtr;
	for (uint16_t row = 0; row < numRows; row++)
	{
		for (uint16_t chn = 0; chn < song.antChn; chn++)
		{
			bytes[0] = *pattPtr++;
			bytes[1] = *pattPtr++;
			bytes[2] = *pattPtr++;
			bytes[3] = *pattPtr++;
			bytes[4] = *pattPtr++;

			firstBytePtr = writePtr++;

			packBits = 0;
			if (bytes[0] > 0) { packBits |= 1; *writePtr++ = bytes[0]; } // note
			if (bytes[1] > 0) { packBits |= 2; *writePtr++ = bytes[1]; } // instrument
			if (bytes[2] > 0) { packBits |= 4; *writePtr++ = bytes[2]; } // volume column
			if (bytes[3] > 0) { packBits |= 8; *writePtr++ = bytes[3]; } // effect

			if (packBits == 15) // first four bits set?
			{
				// no packing needed, write pattern data as is

				// point to first byte (and overwrite data)
				writePtr = firstBytePtr;

				*writePtr++ = bytes[0];
				*writePtr++ = bytes[1];
				*writePtr++ = bytes[2];
				*writePtr++ = bytes[3];
				*writePtr++ = bytes[4];

				totalPackLen += 5;
				continue;
			}

			if (bytes[4] > 0) { packBits |= 16; *writePtr++ = bytes[4]; } // effect parameter

			*firstBytePtr = packBits | 128; // write pack bits byte
			totalPackLen += (uint16_t)(writePtr - firstBytePtr); // bytes writen
		}

		// skip unused channels
		pattPtr += sizeof (tonTyp) * (MAX_VOICES - song.antChn);
	}

	return totalPackLen;
}
