/*
**	XModuleHook.c
**
**	Copyright (C) 1994,95,96,97 Bernardo Innocenti
**
**	Internal loader/saver hook for the IFF XMOD module format
*/

#include <exec/memory.h>
#include <libraries/xmoduleclass.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/iffparse.h>
#include <proto/xmodule.h>

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


/* Local functions prototypes */

static HOOKCALL struct XMHook *IdentifyXModule (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags));
static HOOKCALL LONG LoadXModule (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *loader),
	REG(a2, ULONG *tags));
static HOOKCALL LONG SaveXModule (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *saver),
	REG(a2, ULONG *tags));



GLOBALCALL void AddXModuleHooks (void)

/* Adds XModule loader and saver */
{
	xmAddHook (
		XMHOOK_Type,			NT_XMLOADER,
		XMHOOK_Name,			(LONG)"XModule",
		XMHOOK_Priority,		50,
		XMHOOK_Descr,			(LONG)"XModule internal format",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_Flags,			XMHF_INTERNAL,
		XMHOOK_LoadModFunc,		LoadXModule,
		XMHOOK_IdentifyModFunc,	IdentifyXModule,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"XModule",
		XMHOOK_Priority,		50,
		XMHOOK_Descr,			(LONG)"XModule Internal format",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_EXCLUDE_SEQUENCE,
		XMHOOK_SaveModFunc,		SaveXModule,
		TAG_DONE);
}



static HOOKCALL struct XMHook *IdentifyXModule (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags))

/* Determine if the given file is an XModule module.
 * Note: the file position will be changed on exit.
 */
{
	ULONG id[3];

	Seek (fh, 0, OFFSET_BEGINNING);
	if (FRead (fh, &id, 12, 1) != 1)
		return NULL;

	if ((id[0] == ID_FORM) && (id[2] == ID_XMOD))
		return loader;

	return NULL;
}



static HOOKCALL LONG LoadXModule (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *loader),
	REG(a2, ULONG *tags))
{
	struct IFFHandle *iff;
	struct ContextNode *cn;
	LONG err;

	if (iff = AllocIFF())
	{
		iff->iff_Stream = (ULONG) fh;

		InitIFFasDOS (iff);

		if (!(err = OpenIFF (iff, IFFF_READ)))
		{
			struct ModuleHeader mhdr;

			static LONG stopchunks[] =
			{
				ID_XMOD,	ID_NAME,
				ID_XMOD,	ID_MHDR,
				ID_SONG,	ID_FORM
			};

			if (err = StopChunks (iff, stopchunks, 3))
				goto error;

			if (err = StopOnExit (iff, ID_XMOD, ID_FORM))
				goto error;

			/* Scan module */

			while (1)
			{
				if (err = ParseIFF (iff, IFFPARSE_SCAN))
				{
					if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
					break; /* Free resources & exit */
				}

				if (cn = CurrentChunk (iff))
				{
					switch (cn->cn_ID)
					{
						case ID_NAME:
						{
							UBYTE name[128];

							ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
							name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
							SetAttrs (si,
								SNGA_Title,	name,
								TAG_DONE);
							break;
						}

						case ID_MHDR:
							if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
								goto error;
							break;

						case ID_FORM:
							if (cn->cn_Type == ID_SONG)
								if (err = LoadSong (iff, si))
									goto error;
							break;

						default:
							break;
					}
				}
			}
error:
			CloseIFF (iff);
		}

		FreeIFF (iff);
	}
	else err = ERROR_NO_FREE_STORE;

	return err;
}



GLOBALCALL LONG LoadSong (struct IFFHandle *iff, struct SongInfo *si)
{
	LONG err, len;
	struct ContextNode *cn;
	struct SongHeader shdr;

	static LONG stopchunks[] =
	{
		ID_SONG,	ID_NAME,
		ID_SONG,	ID_AUTH,
		ID_SONG,	ID_ANNO,
		ID_SONG,	ID_SHDR,
		ID_SONG,	ID_SEQN,
		ID_PATT,	ID_FORM,
		ID_8SVX,	ID_FORM
	};


	memset (&shdr, 0, sizeof (shdr));

	if (err = StopChunks (iff, stopchunks, 7))
		return err;

	if (err = StopOnExit (iff, ID_SONG, ID_FORM))
		return err;

	/* Scan song */

	while (1)
	{
		if (err = ParseIFF (iff, IFFPARSE_SCAN))
		{
			if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
			break; /* Free resources & exit */
		}

		if (cn = CurrentChunk (iff))
		{
			switch (cn->cn_ID)
			{
				case ID_NAME:
				{
					UBYTE name[128];

					ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
					name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
					SetAttrs (si,
						SNGA_Title, name,
						TAG_DONE);
					break;
				}

				case ID_AUTH:
				{
					UBYTE name[128];

					ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
					name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
					SetAttrs (si,
						SNGA_Author, name,
						TAG_DONE);
					break;
				}

				case ID_ANNO:
				{
					UBYTE name[128];

					ReadChunkBytes (iff, name, min(cn->cn_Size, 127));
					name[min(cn->cn_Size, 127)] = '\0'; /* Ensure string termination */
					SetAttrs (si,
						SNGA_Description, name,
						TAG_DONE);
					break;
				}


				case ID_SHDR:
					if ((err = ReadChunkBytes (iff, &shdr, sizeof (shdr))) == 0)
						return err;

					SetAttrs (si,
						SNGA_GlobalSpeed,		shdr.GlobalSpeed,
						SNGA_GlobalTempo,		shdr.GlobalTempo,
						SNGA_RestartPos,		shdr.RestartPos,
						SNGA_CurrentPatt,		shdr.CurrentPatt,
						SNGA_CurrentPos,		shdr.CurrentPos,
						SNGA_CurrentInst,		shdr.CurrentInst,
						SNGA_DefaultTracks,		shdr.DefNumTracks ? shdr.DefNumTracks : shdr.MaxTracks,
						SNGA_DefaultPattLen,	shdr.DefPattLen ? shdr.DefPattLen : DEF_PATTLEN,
						SNGA_CreationDate,		shdr.CreationDate,
						SNGA_LastChanged,		shdr.LastChanged,
						SNGA_TotalChanges,		shdr.TotalChanges,
						TAG_DONE);
					break;

				case ID_SEQN:
					len	= min (cn->cn_Size / 2, MAXPOSITIONS);

					if (xmSetSongLen (si, len))
					{
						if ((err = ReadChunkBytes (iff, si->Sequence, si->Length * 2))
							!= si->Length * 2)
							return err;
					}
					else return ERROR_NO_FREE_STORE;

					break;

				case ID_FORM:
					if (cn->cn_Type == ID_PATT)
					{
						if (!si->NumPatterns)
							xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
								(APTR)MSG_READING_PATTS, NULL);

						if (xmDisplayProgress (si->NumPatterns, shdr.NumPatterns))
							return ERROR_BREAK;

						if (!LoadPattern (si, si->NumPatterns, iff))
							return IoErr();
					}
					else if (cn->cn_Type == ID_8SVX)
					{
						if (!si->LastInstrument)
							xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
								(APTR)MSG_READING_INSTS, NULL);

						if (xmDisplayProgress (si->LastInstrument, shdr.LastInstrument))
							return ERROR_BREAK;

						if (err = Load8SVXInstrument (si, 0, iff, NULL))
							return err;
					}

					break;

				default:
					break;
			}
		}
	}

	return err;
}



GLOBALCALL struct Pattern *LoadPattern (struct SongInfo *si, ULONG num, struct IFFHandle *iff)
{
	LONG	err, tracksize;
	struct Pattern		*patt = NULL;
	struct ContextNode	*cn;
	struct PatternHeader phdr;
	UWORD	i;
	BYTE	name[64];

	static LONG stopchunks[] =
	{
		ID_PATT, ID_NAME,
		ID_PATT, ID_PHDR,
		ID_PATT, ID_BODY
	};

	name[0] = '\0';

	if (err = StopChunks (iff, stopchunks, 3))
	{
		SetIoErr (err);
		return NULL;
	}

	if (err = StopOnExit (iff, ID_PATT, ID_FORM))
	{
		SetIoErr (err);
		return NULL;
	}

	/* Scan Pattern */

	while (1)
	{
		if (err = ParseIFF (iff, IFFPARSE_SCAN))
		{
			if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
			break; /* Free resources & exit */
		}

		if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_PATT))
		{
			switch (cn->cn_ID)
			{
				case ID_NAME:
				{
					ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
					name[min(cn->cn_Size, 63)] = '\0'; /* Ensure string termination */
					break;
				}

				case ID_PHDR:
					if ((err = ReadChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof(phdr))
					{
						SetIoErr (err);
						return NULL;
					}

					if (!(patt = xmAddPattern (si,
						PATTA_Lines,	phdr.Lines,
						PATTA_Tracks,	phdr.Tracks,
						PATTA_Num,		num,
						TAG_DONE)))
					{
						SetIoErr (ERROR_NO_FREE_STORE);
						return NULL;
					}

					tracksize = phdr.Lines * sizeof (struct Note);

					break;

				case ID_BODY:
				{
					if (!patt)
					{
						SetIoErr (IFFERR_SYNTAX);
						return NULL;
					}

					for (i = 0; i < patt->Tracks; i++)
					{
						if ((err = ReadChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
						{
							SetIoErr (err);
							return NULL;
						}
					}

					break;
				}

				default:
					break;
			}
		}
	}

	if (name[0])
		xmSetPattern (si, si->NumPatterns - 1,
			PATTA_Name,	name,
			TAG_DONE);

	if (!err && !patt)
		err = ERROR_OBJECT_NOT_FOUND;

	if (err) SetIoErr (err);

	return patt;
}



static HOOKCALL LONG SaveXModule (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *saver),
	REG(a2, ULONG *tags))
{
	struct IFFHandle *iff;
	LONG err;

	if (iff = AllocIFF())
	{
		iff->iff_Stream = (ULONG) fh;

		InitIFFasDOS (iff);

		if (!(err = OpenIFF (iff, IFFF_WRITE)))
		{

			/* Write XMOD */
			if (err = PushChunk (iff, ID_XMOD, ID_FORM, IFFSIZE_UNKNOWN))
				goto error;

			/* Write module NAME */
			if (err = WriteStringChunk (iff, FilePart (si->Path), ID_NAME))
				goto error;

			/* Write module ANNO */
			if (err = WriteStringChunk (iff, VERS, ID_ANNO))
				goto error;

			/* Write Module Header (MHDR) */
			{
				struct ModuleHeader mhdr;

				mhdr.XModuleVersion = VERSION;
				mhdr.XModuleRevision = REVISION;
				mhdr.NumSongs	= 1;
				mhdr.ActiveSong	= 1;
				mhdr.MasterVolume = 0xFFFF;
				mhdr.MixingRate = 44100;

				if (err = PushChunk (iff, ID_XMOD, ID_MHDR, sizeof (mhdr)))
					goto error;
				if ((err = WriteChunkBytes (iff, &mhdr, sizeof (mhdr))) != sizeof(mhdr))
					goto error;
				if (err = PopChunk (iff)) goto error;	/* Pop MHDR */
			}

			if (err = SaveSong (iff, si))
				goto error;

			err = PopChunk (iff);		/* Pop FORM XMOD */

error:
			CloseIFF (iff);
		}
		else err = IFFERR_NOTIFF;

		FreeIFF (iff);
	}
	else err = ERROR_NO_FREE_STORE;

	return (UWORD) err;
}



GLOBALCALL LONG SaveSong (struct IFFHandle *iff, struct SongInfo *si)
{
	LONG err;

	if (err = PushChunk (iff, ID_SONG, ID_FORM, IFFSIZE_UNKNOWN))
		return err;

	/* Write Song Name */
	WriteStringChunk (iff, si->Title, ID_NAME);

	/* Write Author Name */
	WriteStringChunk (iff, si->Author, ID_AUTH);

	/* Write Annotations */
	WriteStringChunk (iff, si->Description, ID_ANNO);

	/* Write Song Header (SHDR) */
	{
		struct SongHeader shdr;

		if (err = PushChunk  (iff, ID_SONG, ID_SHDR, IFFSIZE_UNKNOWN))
			return err;


		/* Set last changed date */
		{
			ULONG dummy;
			CurrentTime			(&si->LastChanged, &dummy);
		}

		shdr.Length			= si->Length;
		shdr.MaxTracks		= si->MaxTracks;
		shdr.NumPatterns	= si->NumPatterns;
		shdr.LastInstrument	= si->LastInstrument;
		shdr.GlobalSpeed	= si->GlobalSpeed;
		shdr.GlobalTempo	= si->GlobalTempo;
		shdr.RestartPos		= si->RestartPos;
		shdr.CurrentPatt	= si->CurrentPatt;
		shdr.CurrentLine	= si->CurrentLine;
		shdr.CurrentTrack	= si->CurrentTrack;
		shdr.CurrentPos		= si->CurrentPos;
		shdr.CurrentInst	= si->CurrentInst;
		shdr.DefNumTracks	= si->DefNumTracks;
		shdr.DefPattLen		= si->DefPattLen;
		shdr.TotalChanges	= si->TotalChanges + si->Changes;
		shdr.CreationDate	= si->CreationDate;
		shdr.LastChanged	= si->LastChanged;


		if ((err = WriteChunkBytes (iff, &shdr, sizeof (shdr))) != sizeof (shdr))
			return err;

		if (err = PopChunk (iff))	/* Pop SHDR */
			return err;
	}


	if (err = SaveSequence (iff, si))
		return err;
	if (err = SavePatterns (iff, si))
		return err;
	if (err = SaveInstruments (iff, si))
		return err;

	err = PopChunk (iff);	/* Pop FORM SONG */

	return err;
}



GLOBALCALL LONG SaveSequence (struct IFFHandle *iff, struct SongInfo *si)
{
	LONG	err;

	if (err = PushChunk (iff, 0, ID_SEQN, si->Length * 2))
		return err;

	if ((err = WriteChunkBytes (iff, si->Sequence, si->Length * 2)) != si->Length * 2)
		return err;

	err = PopChunk (iff);

	return err;
}



GLOBALCALL LONG SavePatterns (struct IFFHandle *iff, struct SongInfo *si)
{
	LONG	err;
	ULONG	i;

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_WRITING_PATTS, NULL);


	for (i = 0; i < si->NumPatterns; i++)
	{
		if (xmDisplayProgress (i, si->NumPatterns))
			return ERROR_BREAK;

		if (si->Patt[i])
			if (err = SavePattern (iff, si->Patt[i]))
				return err;
	}

	return RETURN_OK;
}



GLOBALCALL LONG SaveInstruments (struct IFFHandle *iff, struct SongInfo *si)
{
	LONG	err;
	ULONG	i;

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_WRITING_INSTS, NULL);

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

		if (xmDisplayProgress (i - 1, si->LastInstrument))
			return ERROR_BREAK;

		if (err = Save8SVXInstrument (si->Instr[i], i, iff))
			return err;
	}

	return RETURN_OK;
}



GLOBALCALL LONG SavePattern (struct IFFHandle *iff, struct Pattern *patt)
{
	LONG err;

	if (err = PushChunk (iff, ID_PATT, ID_FORM, IFFSIZE_UNKNOWN))
		return err;

	/* Write pattern NAME */

	if (err = WriteStringChunk (iff, patt->Name, ID_NAME))
		return err;

	/* Write pattern header (PHDR) */
	{
		struct PatternHeader phdr;

		phdr.Lines = patt->Lines;
		phdr.Tracks = patt->Tracks;

		if (err = PushChunk (iff, 0, ID_PHDR, sizeof (struct PatternHeader)))
			return err;

		if ((err = WriteChunkBytes (iff, &phdr, sizeof (phdr))) != sizeof (phdr))
			return err;

		if (err = PopChunk (iff))	/* PHDR */
			return err;
	}

	/* Write pattern BODY */
	{
		ULONG tracksize = sizeof (struct Note) * patt->Lines;
		ULONG i;

		if (err = PushChunk (iff, 0, ID_BODY, tracksize * patt->Tracks))
			return err;

		for (i = 0; i < patt->Tracks; i++)
		{
			if ((err = WriteChunkBytes (iff, patt->Notes[i], tracksize)) != tracksize)
				return err;
		}

		if (err = PopChunk (iff))	/* BODY */
			return err;
	}

	err = PopChunk (iff);	/* PATT */

	return err;
}



GLOBALCALL LONG WriteStringChunk (struct IFFHandle *iff, CONST_STRPTR str, ULONG id)
{
	LONG err;
	ULONG len;

	if (!str || !str[0] || !SaveSwitches.SaveNames)
		return RETURN_OK;

	if (err = PushChunk (iff, 0, id, len = strlen (str)))
		return err;

	if ((err = WriteChunkBytes (iff, str, len)) != len)
		return err;

	err = PopChunk (iff);

	return err;
}
