/*
**	Operators.c
**
**	Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
**
**	General pourpose module handling/processing functions.
*/

#include <exec/memory.h>

#include <proto/exec.h>
#include <proto/xmodule.h>

#include "XModulePriv.h"
#include "Gui.h"


/* Local function prototypes */

static void	BreakPattern	(struct Note **arr, UWORD rows, UWORD tracks);



GLOBALCALL ULONG InsertPattern (struct SongInfo *si, struct Pattern *patt, UWORD patnum)

/* Inserts a pattern at any song position.  Patterns >= patnum will be moved
 * ahead one slot.  The position table is updated inserting references to
 * the new pattern immediately before each occurence of patnum, so that the
 * two patterns are always played together.
 *
 * RESULT
 *	0 to mean succes,
 *	any other value means failure.
 */
{
	ULONG i, k;


	if (xmAddPattern (si,
		PATTA_Num,		patnum,
		PATTA_Pattern,	patt,
		TAG_DONE))
	{
		xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
			(APTR)MSG_CANT_INSERT_PATT, NULL);
		return 1;
	}

	/* Adjust position table */
	for (i = 0 ; i < si->Length ; i++)
	{
		/* Song can't grow bigger than MAXPOSITIONS */
		if (si->Length >= MAXPOSITIONS)
			return 2;	/* TODO: better error handling */

		/* Fix pattern numbers */
		if (si->Sequence[i] > patnum) si->Sequence[i]++;

		/* Insert references to the new pattern in the position table */
		if (si->Sequence[i] == patnum)
		{
			/* Grow song */
			if (xmSetSongLen (si, si->Length + 1))
			{
				/* Shift subsequent positions ahead 1 slot */
				for (k = si->Length - 1; k > i ; k--)
					si->Sequence[k] = si->Sequence[k-1];

				si->Sequence[i+1] = patnum+1;	/* Restore old pattern */

				i++;			/* Avoid processing this pattern again */

				/* TODO: It would be nice to fix Pattern Jump commands too... */
			}
			else return 1;
		}
	}

	return 0;
}



GLOBALCALL void DiscardPatterns (struct SongInfo *si)

/* Discard patterns beyond the last pattern referenced
 * in the song sequence.
 */
{
	ULONG i, j;
	UBYTE *used;
	struct Pattern *patt;

	if (!(used = AllocVecPooled (Pool, si->NumPatterns)))
		return;

	/* Flag patterns REALLY used in the song */
	for (i = 0; i < si->Length ; i++)
		used[si->Sequence[i]]++;


	for (i = 0; i < si->NumPatterns; i++)
	{
		if (!(patt = si->Patt[i])) continue;

		if (!used[i] && patt)
		{
			xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
				(APTR)MSG_PATT_UNUSED, i);

			xmRemPattern (si, i, 0);

			/* Shift all subsequent patterns one position back */
			for (j = i; j < si->NumPatterns; j++)
				used[j] = used[j+1];

			/* Rearrange Position Table */
			for (j = 0; j < si->Length; j++)
				if (si->Sequence[j] > i) si->Sequence[j]--;

			/* TODO: It would be nice to fix Pattern Jump commands too... */

			i--; /* Process this pattern # again, since it's now another pattern */
		}
	}

	FreeVecPooled (Pool, used);
}



GLOBALCALL void CutPatterns (struct SongInfo *si)

/* Find out what patterns are breaked (effect D) and resize them */
{
	ULONG i, j, k, l;
	struct Pattern *patt, *newpatt;

	for (i = 0; i < si->NumPatterns; i++)
	{
		if (!(patt = si->Patt[i])) continue;

		for (j = 0; j < patt->Lines; j++)
			for (k = 0; k < patt->Tracks; k++)
				if (patt->Notes[k][j].EffNum == EFF_PATTERNBREAK)
				{
					xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
						(APTR)MSG_PATT_CUT, i, j+1);

					if (newpatt = xmAddPattern (si,
						PATTA_Name,		patt->Name,
						PATTA_Tracks,	patt->Tracks,
						PATTA_Lines,	j + 1,
						PATTA_Num,		-1,			/* Do not add it */
						TAG_DONE))
					{
						/* Remove break command */
						patt->Notes[k][j].EffNum = 0;
						patt->Notes[k][j].EffVal = 0;

						/* Copy notes */
						for (l = 0; l < patt->Tracks; l++)
							CopyMem (patt->Notes[l], newpatt->Notes[l], sizeof (struct Note) * (j+1));

						/* Replace with new pattern */
						xmAddPattern (si,
							PATTA_Pattern,	patt,
							PATTA_Num,		i,
							PATTA_Replace,	TRUE,
							TAG_DONE);

						/* stop the loop on this pattern */
						patt = newpatt;
						j = patt->Lines;
						k = patt->Tracks;
					}
				}
	}
}



GLOBALCALL void RemDupPatterns (struct SongInfo *si)

/* Find out identical patterns and cut them out */
{
	ULONG i, j, k;
	struct Pattern *patta, *pattb;

	if (si->NumPatterns < 2) return;

	for (i = 0; i < si->NumPatterns-1; i++)
	{
		if (!(patta = si->Patt[i])) continue;

		for (j = i+1; j < si->NumPatterns; j++)
		{
			if (!(pattb = si->Patt[j])) continue;

			if ((patta->Lines == pattb->Lines) && (patta->Tracks == pattb->Tracks))
			{
				for (k = 0; k < patta->Tracks; k++)
					if (memcmp (patta->Notes[k], pattb->Notes[k], sizeof (struct Note) * patta->Lines))
						break;

				if (k == patta->Tracks)
				{
					xmRemPattern (si, j, i);
					xmDisplayMessage (XMDMF_NOTE | XMDMF_USECATALOG,
						(APTR)MSG_PATT_DUPE, i, j);
					j--;
				}
			}
		}
	}
}



static void BreakPattern (struct Note **arr, UWORD row, UWORD tracks)

/* Put a break command at the end of a pattern */
{
	ULONG i;

	/* Try to find a free effect slot in the row... */
	for (i = 0 ; i < tracks ; i++)
		if (arr[i][row].EffNum == 0 && arr[i][row].EffVal == 0)
			break;

	if (i == tracks) i = 0;

	arr[i][row].EffNum = EFF_PATTERNBREAK; /* ...and break the pattern */
	arr[i][row].EffVal = 0;
}



GLOBALCALL LONG ResizePatterns (struct SongInfo *si, ULONG min, ULONG max)

/* Find out what patterns are less than <min> and more than <max>
 * lines long and either grow them or split them in shorter
 * patterns.  Pattern splitting will automatically recurse when
 * more than one split is required.
 *
 * RESULT
 *	0 to mean succes, any other value means failure.
 */
{
	struct Pattern	*patt, *newpatt, *newpatt2;
	ULONG i, j;
	UWORD len;
	BOOL do_update = FALSE;

	for (i = 0 ; i < si->NumPatterns ; i++)
	{
		if (!(patt = si->Patt[i])) continue;

		if ((len = patt->Lines) < min)
		{
			/* BREAK PATTERN */

			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
				(APTR)MSG_PATT_WILL_GROW, i, len);

			if (!(newpatt = xmAddPattern (si,
				PATTA_Num,		-1,
				PATTA_Lines,	min,
				PATTA_Tracks,	patt->Tracks,
				TAG_DONE)))
				return ERROR_NO_FREE_STORE;


			/* Copy the old notes in the new (longer) pattern */
			for (j = 0 ; j < patt->Tracks ; j++)
				memcpy (newpatt->Notes[j], patt->Notes[j],
						(sizeof (struct Note)) * len);

			/* Break the new pattern */
			BreakPattern (newpatt->Notes, len-1, newpatt->Tracks);

			xmAddPattern (si,
				PATTA_Num, i,
				PATTA_Pattern, newpatt,
				PATTA_Replace, TRUE,
				TAG_END);
		}
		else if (len > max)
		{
			/* SPLIT PATTERN */

			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_COMMENT,
				(APTR)MSG_SPLITTING_PATT, i, len);

			if (!(newpatt = xmAddPattern (si,
				PATTA_Num,		-1,
				PATTA_Lines,	max,
				PATTA_Tracks,	patt->Tracks,
				TAG_DONE)))
				return ERROR_NO_FREE_STORE;


			/* If len - max is still above max, this pattern
			 * will be splitted or breaked once again in the
			 * next loop.
			 */

			if (!(newpatt2 = xmAddPattern (si,
				PATTA_Num,		-1,
				PATTA_Lines,	len - max,
				PATTA_Tracks,	patt->Tracks,
				TAG_DONE)))
			{
				xmRemPattern (si, -1, (ULONG)newpatt);
				return ERROR_NO_FREE_STORE;
			}

			/* Copy first <max> rows of the pattern */
			for (j = 0 ; j < si->MaxTracks ; j++)
				memcpy (newpatt->Notes[j], patt->Notes[j],
						(sizeof (struct Note)) * max);

			/* Copy the rest of the pattern */
			for (j = 0 ; j < si->MaxTracks ; j++)
				memcpy (newpatt2->Notes[j], patt->Notes[j] + max,
						(sizeof (struct Note)) * (len - max));


			/* Make room for the new pattern */
			if (InsertPattern (si, newpatt, i))
			{
				xmRemPattern (si, -1, (ULONG)newpatt);
				xmRemPattern (si, -1, (ULONG)newpatt2);
				continue;
			}

			/* Substitute old pattern */
			if (!(xmAddPattern (si,
				PATTA_Num,		i + 1,
				PATTA_Pattern,	newpatt2,
				TAG_DONE)))
			{
				xmRemPattern (si, -1, (ULONG)newpatt2);
				continue;
			}
		}
	}	/* End for(i) */

	if (do_update)
		UpdateSongInfo();

	return 0;

}	/* End ResizePatterns */



GLOBALCALL struct Pattern *CopyPattern (struct SongInfo *si, struct Pattern *src, ULONG destNum)

/* Makes a copy of the notes and attributes of the <src>
 * pattern to the <dest> pattern.  The destination pattern is
 * created with xmAddPattern().
 */
{
	struct Pattern *dest;
	ULONG i;

	if (!src) return NULL;

	if (dest = xmAddPattern (si, destNum,
		PATTA_Lines, src->Lines,
		PATTA_Tracks, src->Tracks,
		PATTA_Name, src->Name))
	{
		for (i = 0; i < src->Tracks; i++)
			CopyMem (src->Notes[i], dest->Notes[i],
				sizeof (struct Note) * src->Lines);
	}

	return dest;
}



GLOBALCALL struct SongInfo *MergeSongs (struct SongInfo *songa, struct SongInfo *songb)

/* Merges <songa> with <songb> in a new song where the notes of the
 * two sources are played at the same time.
 */
{
	struct SongInfo		*songc;
	struct Pattern		*patta, *pattb, *pattc;
	struct Note 		*note;
	struct Instrument	*source;
	BYTE				*newsample;
	LONG				 slen, plen, ntrk, i, j, k;


	if (songa->Length != songb->Length)
		xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
			(APTR)MSG_SONG_LEN_DIFFERENT, NULL);

	/* Create new song */
	{
		UBYTE newtitle[64], newauthor[64];

		/* Make new title */
		newtitle[63] = '\0';
		strncpy (newtitle, songa->Title, 63);
		strncat	(newtitle, " + ", 63);
		strncat	(newtitle, songb->Title, 63);

		/* Make new author */

		newauthor[63] = '\0';
		if (songa->Author && songa->Author[0])
			strncpy (newauthor, songa->Author, 63);
		if (songb->Author && songb->Author[0])
		{
			if (songa->Author && songa->Author[0])
			{
				strncat (newauthor, " & ", 63);
				strncat (newauthor, songb->Author, 63);
			}
			else strncpy (newauthor, songb->Author, 63);
		}

		if (!(songc = xmCreateSong (
			SNGA_GlobalSpeed,	songa->GlobalSpeed,
			SNGA_GlobalTempo,	songa->GlobalTempo,
			SNGA_RestartPos,	songa->RestartPos,
			SNGA_Title,			newtitle,
			SNGA_Author,		newauthor,
			TAG_DONE)))
			return NULL;
	}

	slen = min (songa->Length, songb->Length);
	xmSetSongLen (songc, slen);


	for (i = 0; i < slen; i++)
	{
		UBYTE pattname[64];

		patta = songa->Patt[songa->Sequence[i]];
		pattb = songb->Patt[songb->Sequence[i]];

		if (patta->Lines != pattb->Lines)
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_PATT_LEN_DIFFERENT, i);

		plen = min (patta->Lines, pattb->Lines);
		ntrk = patta->Tracks + pattb->Tracks;

		if (ntrk > MAXTRACKS)
		{
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_PATT_TOO_MANY_TRACKS, MAXTRACKS);

			ntrk = MAXTRACKS;
		}

		/* Set pattern name */
		if (patta->Name && pattb->Name)
		{
			pattname[63] = '\0';
			strncpy (pattname, patta->Name, 63);
			strncat	(pattname, " + ", 63);
			strncat	(pattname, pattb->Name, 63);
		}
		else
			SPrintf (pattname, "%ld + %ld", songa->Sequence[i], songb->Sequence[i]);

		if (!(pattc = xmAddPattern (songc,
			PATTA_Lines,	plen,
			PATTA_Tracks,	ntrk,
			PATTA_Name,		pattname,
			TAG_DONE)))
		{
			xmDeleteSong (songc);
			LastErr = ERROR_NO_FREE_STORE;
			return NULL;
		}

		songc->Sequence[i] = i;

		/* Copy tracks from source A */
		for (j = 0; j < patta->Tracks; j++)
			CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);

		/* Copy tracks from source B */
		for (j = 0; j < pattb->Tracks; j++)
		{
			if (j + patta->Tracks >= MAXTRACKS) break;

			note = pattc->Notes[j + patta->Tracks];
			CopyMem (pattb->Notes[j], note, sizeof (struct Note) * plen);

			for (k = 0; k < plen; k++)
				if (note[k].Note)
				{
					if ((note[k].Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
					{
						xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
							(APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
						xmDeleteSong (songc);
						return NULL;
					}

					note[k].Inst += songa->LastInstrument - 1;
				}
		}
	}


	/* Copy instruments */

	for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
	{
		if (i <= songa->LastInstrument)
			source = songa->Instr[i];
		else
			source = songb->Instr[i - songa->LastInstrument + 1];

		if (source)
		{
			if (source->Sample)
			{
				if (!(newsample = AllocVec (source->Length, MEMF_SAMPLE)))
				{
					xmDeleteSong (songc);
					LastErr = ERROR_NO_FREE_STORE;
					return NULL;
				}
				CopyMem (source->Sample, newsample, source->Length);
			}
			else
				newsample = NULL;

			if (!xmAddInstrument (songc, i,
				INSTRA_Type,		source->Type,
				INSTRA_Name, 		source->Name,
				INSTRA_Volume,		source->Volume,
				INSTRA_Sample,		newsample,
				INSTRA_Length,		source->Length,
				INSTRA_LoopStart,	source->Repeat,
				INSTRA_LoopLen,		source->Replen,
				INSTRA_FineTune,	source->FineTune,
				TAG_DONE))
			{
				FreeVec (newsample);
				xmDeleteSong (songc);
				LastErr = ERROR_NO_FREE_STORE;
				return NULL;
			}
		}
	}

	return songc;
}



GLOBALCALL struct SongInfo *JoinSongs (struct SongInfo *songa, struct SongInfo *songb)

/* Joins <songa> with <songb> in a new song where the notes of the
 * two sources are played one after the oder.
 */
{
	struct SongInfo		*songc;
	struct Pattern		*patta, *pattb, *pattc;
	struct Note			*note;
	struct Instrument	*inst, *source;
	BYTE				*newsample;
	ULONG				 plen, ntrk, i, j, k;
	UWORD				 songclen;


	/* Check maximum values */

	if ((songa->Length + songb->Length) > MAXPOSITIONS)
	{
		xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
			(APTR)MSG_SONG_TOO_LONG, NULL);
		songclen = MAXPOSITIONS;
	}
	else songclen = songa->Length + songb->Length;

	if ((songa->NumPatterns + songb->NumPatterns) > MAXPATTERNS)
		xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
			(APTR)MSG_SONG_HAS_TOO_MANY_PATT, NULL);


	/* Create new song */
	{
		UBYTE newtitle[64], newauthor[64];

		/* Make new title */
		newtitle[63] = '\0';
		strncpy (newtitle, songa->Title, 63);
		strncat	(newtitle, " & ", 63);
		strncat	(newtitle, songb->Title, 63);

		/* Make new author */

		newauthor[63] = '\0';
		if (songa->Author && songa->Author[0])
			strncpy (newauthor, songa->Author, 63);
		if (songb->Author && songb->Author[0])
		{
			if (songa->Author && songa->Author[0])
			{
				strncat (newauthor, " & ", 63);
				strncat (newauthor, songb->Author, 63);
			}
			else strncpy (newauthor, songb->Author, 63);
		}

		if (!(songc = xmCreateSong (
			SNGA_GlobalSpeed,	songa->GlobalSpeed,
			SNGA_GlobalTempo,	songa->GlobalTempo,
			SNGA_RestartPos,	songa->RestartPos,
			SNGA_Title,			newtitle,
			SNGA_Author,		newauthor,
			TAG_DONE)))
			return NULL;
	}

	if (xmSetSongLen (songc, songclen))
	{
		/* Copy position table of song A */
		memcpy (songc->Sequence, songa->Sequence, songa->Length * sizeof (UWORD));

		/* Append position table of song B */
		for (i = 0; i < songb->Length; i++)
		{
			if (i + songa->Length >= songc->Length) break;
			songc->Sequence[i + songa->Length] = (songb->Sequence[i] + songa->NumPatterns > MAXPATTERNS) ?
				songb->Sequence[i] : (songb->Sequence[i] + songa->NumPatterns);
		}
	}
	else
	{
		xmDeleteSong (songc);
		LastErr = ERROR_NO_FREE_STORE;
		return NULL;
	}


	/* Copy song A patterns */

	for (i = 0; i < songa->NumPatterns; i++)
	{
		patta = songa->Patt[i];
		plen = patta->Lines;
		ntrk = patta->Tracks;

		if (!(pattc = xmAddPattern (songc,
			PATTA_Lines,	plen,
			PATTA_Tracks,	ntrk,
			PATTA_Name,		patta->Name,
			TAG_DONE)))
		{
			xmDeleteSong (songc);
			LastErr = ERROR_NO_FREE_STORE;
			return NULL;
		}

		/* Copy tracks from source A */

		for (j = 0; j < ntrk; j++)
			CopyMem (patta->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);
	}


	/* Append song B patterns */

	for (i = 0; i < songb->NumPatterns; i++)
	{
		pattb = songb->Patt[i];
		plen = pattb->Lines;
		ntrk = pattb->Tracks;

		if (!(pattc = xmAddPattern (songc,
			PATTA_Lines,	plen,
			PATTA_Tracks,	ntrk,
			PATTA_Name,		pattb->Name,
			TAG_DONE)))
		{
			xmDeleteSong (songc);
			LastErr = ERROR_NO_FREE_STORE;
			return NULL;
		}

		/* Copy tracks from source B */

		for (j = 0; j < ntrk; j++)
			CopyMem (pattb->Notes[j], pattc->Notes[j], sizeof (struct Note) * plen);

		/* Adjust instruments references */

		for (j = 0; j < ntrk; j++)
			for (k = 0; k < plen; k++)
			{
				note = &pattc->Notes[j][k];

				if (note->Inst)
				{
					if ((note->Inst + songa->LastInstrument) >= MAXINSTRUMENTS)
					{
						xmDisplayMessageA (XMDMF_ERROR | XMDMF_USECATALOG,
							(APTR)MSG_ERR_INSTR_OVERFLOW, NULL);
						xmDeleteSong (songc);
						return NULL;
					}

					note->Inst += songa->LastInstrument - 1;
				}
			}
	}



	/* Copy instruments */

	for (i = 1; i < songa->LastInstrument + songb->LastInstrument; i++)
	{
		if (i <= songa->LastInstrument)
			source = songa->Instr[i];
		else
			source = songb->Instr[i - songa->LastInstrument + 1];

		if (source)
		{
			if (source->Sample)
			{
				if (!(newsample = AllocVec (source->Length, MEMF_ANY)))
				{
					xmDeleteSong (songc);
					LastErr = ERROR_NO_FREE_STORE;
					return NULL;
				}
				CopyMem (source->Sample, newsample, source->Length);
			}
			else
				newsample = NULL;

			if (!(inst = xmAddInstrument (songc, i,
				INSTRA_Type,		source->Type,
				INSTRA_Name, 		source->Name,
				INSTRA_Volume,		source->Volume,
				INSTRA_Sample,		newsample,
				INSTRA_Length,		source->Length,
				INSTRA_Repeat,		source->Repeat,
				INSTRA_Replen,		source->Replen,
				INSTRA_FineTune,	source->FineTune,
				TAG_DONE)))
			{
				FreeVec (newsample);
				xmDeleteSong (songc);
				LastErr = ERROR_NO_FREE_STORE;
				return NULL;
			}
		}
	}

	return songc;
}



GLOBALCALL void FixSong (struct SongInfo *si)

/* Fixes dangerous errors in song structure */
{
	struct Instrument *instr;
	ULONG i;

	/* Check instruments */

	for (i = 1 ; i < si->LastInstrument ; i++)
	{
		if (!(instr = si->Instr[i])) continue;

		/* Filter instrument names */
		FilterName (instr->Name);

		/* Some sensible sanity checks on loops */

		if (instr->Repeat && (instr->Repeat >= instr->Length))
		{
			instr->Repeat = instr->Replen = 0;
			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
				(APTR)MSG_INVALID_LOOP_REMOVED, i);
		}
		else if (instr->Repeat + instr->Replen > instr->Length)
		{
			instr->Replen = instr->Length - instr->Repeat;
			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
				(APTR)MSG_INVALID_LOOP_FIXED, i);
		}
	}

	/* Check patterns */
	for (i = 0; i < si->NumPatterns; i++)
		if (si->Patt[i]) FilterName (si->Patt[i]->Name);

	/* Can't have a song with no patterns!!! */
	if (si->NumPatterns == 0)
	{
		xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
			(APTR)MSG_SONG_HAS_NO_PATTS, NULL);
		xmAddPattern (si, NULL);
	}

	/* Better forcing the song to a minimum length of 1. */
	if (si->Length == 0)
	{
		xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
			(APTR)MSG_SONG_HAS_NO_SEQ, NULL);
		xmSetSongLen (si, 1);
	}

	/* Check sequence */
	for (i = 0; i < si->Length; i++)
	{
		if (si->Sequence[i] >= si->NumPatterns)
		{
			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
				(APTR)MSG_INVALID_SONG_POS, i, si->Sequence[i]);
			si->Sequence[i] = si->NumPatterns - 1;
		}
	}

	FilterName (si->Title);
	FilterName (si->Author);
}

