/* LoadSaveNoteVectors.c */
/*****************************************************************************/
/*                                                                           */
/*    Out Of Phase:  Digital Music Synthesis on General Purpose Computers    */
/*    Copyright (C) 1994  Thomas R. Lawrence                                 */
/*                                                                           */
/*    This program is free software; you can redistribute it and/or modify   */
/*    it under the terms of the GNU General Public License as published by   */
/*    the Free Software Foundation; either version 2 of the License, or      */
/*    (at your option) any later version.                                    */
/*                                                                           */
/*    This program 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 General Public License for more details.                           */
/*                                                                           */
/*    You should have received a copy of the GNU General Public License      */
/*    along with this program; if not, write to the Free Software            */
/*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                           */
/*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
/*                                                                           */
/*****************************************************************************/

#include "MiscInfo.h"
#include "Audit.h"
#include "Debug.h"
#include "Definitions.h"

#include "LoadSaveNoteVectors.h"
#include "Array.h"
#include "FrameObject.h"
#include "NoteObject.h"
#include "Memory.h"
#include "BufferedFileInput.h"
#include "BufferedFileOutput.h"


/* Note Vector Subblock Format: */
/*   1-byte format version number */
/*       should be 1 or 2 */
/*   4-byte little endian number of frames in the vector */
/*   * for each frame: */
/*       4-byte little endian number of notes in the frame */
/*       n-bytes of data for all of the notes (see note object format) */
/*   4-byte little endian number of records in the tie matrix */
/*   * for each tie matrix entry: */
/*       4-byte little endian index of the source frame */
/*       4-byte little endian index of the source note in the frame */
/*       4-byte little endian index of the target frame */
/*       4-byte little endian index of the target note in the frame */


/* this reads in notes from the file and the tie matrix and builts a note */
/* vector from the information. */
FileLoadingErrors		ReadNoteVector(struct ArrayRec** FrameArrayOut,
											struct BufferedInputRec* Input)
	{
		signed long				NumberOfFrames;
		long							FrameScan;
		ArrayRec*					FrameArray;
		FileLoadingErrors	Error;
		signed long				NumberOfTieRecords;
		long							TieScan;
		unsigned char			UnsignedChar;
		short							FormatVersionNumber;

		CheckPtrExistence(Input);

		/*   1-byte format version number */
		/*       should be 1 or 2 */
		if (!ReadBufferedUnsignedChar(Input,&UnsignedChar))
			{
				Error = eFileLoadDiskError;
			 FailurePointneg1:
				return Error;
			}
		if ((UnsignedChar != 1) && (UnsignedChar != 2))
			{
				Error = eFileLoadBadFormat;
			 FailurePoint0:
				goto FailurePointneg1;
			}
		FormatVersionNumber = UnsignedChar;

		FrameArray = NewArray();
		if (FrameArray == NIL)
			{
				Error = eFileLoadOutOfMemory;
			 FailurePoint1:
				goto FailurePoint0;
			}

		/*   4-byte little endian number of frames in the vector */
		if (!ReadBufferedSignedLongLittleEndian(Input,&NumberOfFrames))
			{
				long							FrameLimit;

				Error = eFileLoadDiskError;
			 FailurePoint2:
				FrameLimit = ArrayGetLength(FrameArray);
				for (FrameScan = 0; FrameScan < FrameLimit; FrameScan += 1)
					{
						DisposeFrameAndContents((FrameObjectRec*)ArrayGetElement(
							FrameArray,FrameScan));
					}
				DisposeArray(FrameArray);
				goto FailurePoint1;
			}
		if (NumberOfFrames < 0)
			{
				Error = eFileLoadBadFormat;
			 FailurePoint3:
				goto FailurePoint2;
			}

		/*   * for each frame: */
		/*       4-byte little endian number of notes in the frame */
		/*       n-bytes of data for all of the notes */
		for (FrameScan = 0; FrameScan < NumberOfFrames; FrameScan += 1)
			{
				signed long					NumberOfNotes;
				long								NoteScan;
				FrameObjectRec*			FrameObject;

				if (!ReadBufferedSignedLongLittleEndian(Input,&NumberOfNotes))
					{
						Error = eFileLoadDiskError;
					 FailurePoint4:
						goto FailurePoint3;
					}

				FrameObject = NewFrame();
				if (FrameObject == NIL)
					{
						Error = eFileLoadOutOfMemory;
					 FailurePoint4a:
						goto FailurePoint4;
					}

				for (NoteScan = 0; NoteScan < NumberOfNotes; NoteScan += 1)
					{
						NoteObjectRec*			Note;

						Error = NoteObjectNewFromFile(&Note,Input,FormatVersionNumber);
						if (Error != eFileLoadNoError)
							{
							 FailurePoint4b:
								DisposeFrameAndContents(FrameObject);
								goto FailurePoint4a;
							}

						if (((NumNotesInFrame(FrameObject) > 1) && IsItACommand(Note))
							|| IsThisACommandFrame(FrameObject))
							{
								Error = eFileLoadBadFormat;
							 FailurePoint4ba:
								DisposeNote(Note);
								goto FailurePoint4b;
							}

						if (!AppendNoteToFrame(FrameObject,Note))
							{
								Error = eFileLoadOutOfMemory;
							 FailurePoint4bb:
								goto FailurePoint4ba;
							}
					}

				if (!ArrayAppendElement(FrameArray,FrameObject))
					{
						Error = eFileLoadOutOfMemory;
						goto FailurePoint4b;
					}
			}

		/*   4-byte little endian number of records in the tie matrix */
		if (!ReadBufferedSignedLongLittleEndian(Input,&NumberOfTieRecords))
			{
				Error = eFileLoadDiskError;
			 FailurePoint5:
				goto FailurePoint4;
			}
		if (NumberOfTieRecords < 0)
			{
				Error = eFileLoadBadFormat;
			 FailurePoint6:
				goto FailurePoint5;
			}

		/*   * for each tie matrix entry: */
		/*       4-byte little endian index of the source frame */
		/*       4-byte little endian index of the source note in the frame */
		/*       4-byte little endian index of the target frame */
		/*       4-byte little endian index of the target note in the frame */
		for (TieScan = 0; TieScan < NumberOfTieRecords; TieScan += 1)
			{
				signed long					SourceFrameIndex;
				signed long					SourceNoteIndex;
				signed long					TargetFrameIndex;
				signed long					TargetNoteIndex;
				FrameObjectRec*			SourceFrame;
				FrameObjectRec*			TargetFrame;
				NoteObjectRec*			SourceNote;
				NoteObjectRec*			TargetNote;

				if (!ReadBufferedSignedLongLittleEndian(Input,&SourceFrameIndex))
					{
						Error = eFileLoadDiskError;
					 FailurePoint7:
						goto FailurePoint6;
					}
				if (SourceFrameIndex < 0)
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7a:
						goto FailurePoint7;
					}
				if (!ReadBufferedSignedLongLittleEndian(Input,&SourceNoteIndex))
					{
						Error = eFileLoadDiskError;
					 FailurePoint7b:
						goto FailurePoint7a;
					}
				if (SourceNoteIndex < 0)
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7c:
						goto FailurePoint7b;
					}
				if (!ReadBufferedSignedLongLittleEndian(Input,&TargetFrameIndex))
					{
						Error = eFileLoadDiskError;
					 FailurePoint7d:
						goto FailurePoint7c;
					}
				if (TargetFrameIndex < 0)
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7e:
						goto FailurePoint7d;
					}
				if (!ReadBufferedSignedLongLittleEndian(Input,&TargetNoteIndex))
					{
						Error = eFileLoadDiskError;
					 FailurePoint7f:
						goto FailurePoint7e;
					}
				if (TargetNoteIndex < 0)
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7g:
						goto FailurePoint7f;
					}
				if ((TargetFrameIndex <= SourceFrameIndex)
					|| (TargetFrameIndex >= ArrayGetLength(FrameArray))
					|| (SourceFrameIndex >= ArrayGetLength(FrameArray)))
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7h:
						goto FailurePoint7g;
					}
				SourceFrame = (FrameObjectRec*)ArrayGetElement(FrameArray,SourceFrameIndex);
				CheckPtrExistence(SourceFrame);
				TargetFrame = (FrameObjectRec*)ArrayGetElement(FrameArray,TargetFrameIndex);
				CheckPtrExistence(TargetFrame);
				if ((SourceNoteIndex >= NumNotesInFrame(SourceFrame))
					|| (TargetNoteIndex >= NumNotesInFrame(TargetFrame)))
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7i:
						goto FailurePoint7h;
					}
				SourceNote = GetNoteFromFrame(SourceFrame,SourceNoteIndex);
				CheckPtrExistence(SourceNote);
				TargetNote = GetNoteFromFrame(TargetFrame,TargetNoteIndex);
				CheckPtrExistence(TargetNote);
				if (IsItACommand(SourceNote) || IsItACommand(TargetNote))
					{
						Error = eFileLoadBadFormat;
					 FailurePoint7j:
						goto FailurePoint7i;
					}
				PutNoteTieTarget(SourceNote,TargetNote);
			}

		*FrameArrayOut = FrameArray;
		return eFileLoadNoError;
	}


/* this writes out the information for each note and then writes the tie matrix */
/* to the file. */
FileLoadingErrors		WriteNoteVector(struct ArrayRec* ArrayOfFrames,
											struct BufferedOutputRec* Output)
	{
		long							NumberOfFrames;
		long							FrameScan;
		long							NumberOfTieRecords;

		CheckPtrExistence(ArrayOfFrames);
		CheckPtrExistence(Output);

		/*   1-byte format version number */
		/*       should be 1 or 2 */
		if (!WriteBufferedUnsignedChar(Output,2))
			{
				return eFileLoadDiskError;
			}

		/*   4-byte little endian number of frames in the vector */
		NumberOfFrames = ArrayGetLength(ArrayOfFrames);
		if (!WriteBufferedSignedLongLittleEndian(Output,NumberOfFrames))
			{
				return eFileLoadDiskError;
			}

		/*   * for each frame: */
		/*       4-byte little endian number of notes in the frame */
		/*       n-bytes of data for all of the notes */
		NumberOfTieRecords = 0;
		for (FrameScan = 0; FrameScan < NumberOfFrames; FrameScan += 1)
			{
				FrameObjectRec*		Frame;
				long							NumberOfNotes;
				long							NoteScan;
				FileLoadingErrors	Error;

				Frame = (FrameObjectRec*)ArrayGetElement(ArrayOfFrames,FrameScan);
				CheckPtrExistence(Frame);
				NumberOfNotes = NumNotesInFrame(Frame);
				if (!WriteBufferedSignedLongLittleEndian(Output,NumberOfNotes))
					{
						return eFileLoadDiskError;
					}
				for (NoteScan = 0; NoteScan < NumberOfNotes; NoteScan += 1)
					{
						NoteObjectRec*		Note;

						Note = GetNoteFromFrame(Frame,NoteScan);
						if (!IsItACommand(Note) && (GetNoteTieTarget(Note) != NIL))
							{
								NumberOfTieRecords += 1;
							}
						Error = NoteObjectWriteDataOut(Note,Output);
						if (Error != eFileLoadNoError)
							{
								return Error;
							}
					}
			}

		/*   4-byte little endian number of records in the tie matrix */
		if (!WriteBufferedSignedLongLittleEndian(Output,NumberOfTieRecords))
			{
				return eFileLoadDiskError;
			}

		/*   * for each tie matrix entry: */
		/*       4-byte little endian index of the source frame */
		/*       4-byte little endian index of the source note in the frame */
		/*       4-byte little endian index of the target frame */
		/*       4-byte little endian index of the target note in the frame */
		for (FrameScan = 0; FrameScan < NumberOfFrames; FrameScan += 1)
			{
				FrameObjectRec*		Frame;
				long							NumberOfNotes;
				long							NoteScan;

				Frame = (FrameObjectRec*)ArrayGetElement(ArrayOfFrames,FrameScan);
				CheckPtrExistence(Frame);
				NumberOfNotes = NumNotesInFrame(Frame);
				for (NoteScan = 0; NoteScan < NumberOfNotes; NoteScan += 1)
					{
						NoteObjectRec*		Note;

						Note = GetNoteFromFrame(Frame,NoteScan);
						if (!IsItACommand(Note))
							{
								NoteObjectRec*		TieTarget;

								TieTarget = GetNoteTieTarget(Note);
								if (TieTarget != NIL)
									{
										long							SubFrameScan;

										NumberOfTieRecords -= 1;
										for (SubFrameScan = FrameScan + 1; SubFrameScan < NumberOfFrames;
											SubFrameScan += 1)
											{
												FrameObjectRec*		SearchFrame;
												long							SubNumNotes;
												long							SubNoteScan;

												SearchFrame = (FrameObjectRec*)ArrayGetElement(ArrayOfFrames,
													SubFrameScan);
												CheckPtrExistence(SearchFrame);
												SubNumNotes = NumNotesInFrame(SearchFrame);
												for (SubNoteScan = 0; SubNoteScan < SubNumNotes; SubNoteScan += 1)
													{
														NoteObjectRec*		SearchNote;

														SearchNote = GetNoteFromFrame(SearchFrame,SubNoteScan);
														if (!IsItACommand(SearchNote))
															{
																if (SearchNote == TieTarget)
																	{
																		if (!WriteBufferedSignedLongLittleEndian(
																			Output,FrameScan))
																			{
																				return eFileLoadDiskError;
																			}
																		if (!WriteBufferedSignedLongLittleEndian(
																			Output,NoteScan))
																			{
																				return eFileLoadDiskError;
																			}
																		if (!WriteBufferedSignedLongLittleEndian(
																			Output,SubFrameScan))
																			{
																				return eFileLoadDiskError;
																			}
																		if (!WriteBufferedSignedLongLittleEndian(
																			Output,SubNoteScan))
																			{
																				return eFileLoadDiskError;
																			}
																		goto DoneSearchingForTieTargetPoint;
																	}
															}
													}
											}
										EXECUTE(PRERR(ForceAbort,
											"WriteNoteVector:  tie target couldn't be found"));
										/* jump out here when tie target has been found */
									 DoneSearchingForTieTargetPoint:
										;
									}
							}
					}
			}
		ERROR(NumberOfTieRecords != 0,PRERR(ForceAbort,
			"WriteNoteVector:  tie record count inconsistency"));

		return eFileLoadNoError;
	}
