
/* IO.c */

/*
   Musical Notation Editor, Chris Cannam 1994

   File I/O Functions and Methods
*/


/*
   We only need methods for reading in and writing out
   classes which there can actually be instances of --
   Metronome, Clef, Key, TimeSignature, Text, Rest,
   Group and Chord.  We don't need to be able to write
   out the Bar as it's a transient class that can be
   deduced from the others (using the Stave's format).
*/

#ifndef DEBUG_PLUS_PLUS
#undef DEBUG
#endif

#include <stdio.h>
#include <ctype.h>
#include <stddef.h>

#include "General.h"
#include "Tags.h"
#include "Classes.h"
#include "Notes.h"
#include "Stave.h"
#include "StaveEdit.h"
#include "Widgets.h"
#include "Menu.h"
#include "Format.h"
#include "ItemList.h"
#include "IO.h"
#include "IO_1.0-2.0a.h"
#include "Marks.h"

#include "Yawn.h"

#define TAG_LEN    50
#define STRING_LEN 500

static int            dc;

static int            rInt;
static unsigned int   rUInt;
static unsigned long  rULong;
static short          rShort;
static char           rTag[TAG_LEN];
static char           rString[STRING_LEN];

String                musicIOErrorMessage = NULL;

static Boolean        ReadWS(FILE *);
static Boolean        ReadInt(FILE *);
static Boolean        ReadUnsignedInt(FILE *);
static Boolean        ReadHexUnsignedInt(FILE *);
static Boolean        ReadHexUnsignedLong(FILE *);
static Boolean        ReadShort(FILE *);
static Boolean        ReadTag(FILE *);
static Boolean        ReadConstTag(String, FILE *);
static Boolean        ReadString(FILE *);

static Boolean        ReadMark(ItemList, FILE *);
static Boolean        ReadFixedBar(String, ItemList, FILE *);

static Boolean        BuildFileError(FILE *, String);
static Boolean        BuildFileError2s(FILE *, String, String);
static Boolean        BuildFileError3s(FILE *, String, String, String);

static Boolean        ReadItemList(ItemList *, FILE *);

static Boolean        readingGroup = False;

static struct {
  String     tag;
  size_t     size;
  Boolean (* read)(MusicObject, FILE *);
} itemTags[] = {
  { ":",             sizeof(Chord),         ReadChord,         },
  { "Group",         sizeof(Group),         ReadGroup,         },
  { "Rest",          sizeof(Rest),          ReadRest,          },
  { "Text",          sizeof(Text),          ReadText,          },
  { "Clef",          sizeof(Clef),          ReadClef,          },
  { "Key",           sizeof(Key),           ReadKey,           },
  { "Metronome",     sizeof(Metronome),     ReadMetronome,     },
  /*  { "TimeSignature", sizeof(TimeSignature), ReadTimeSignature, },*/
  { "Chord",         sizeof(Chord),         ReadChord,         },
};


#define Write(x) do{for(dc=0;dc<depth;++dc)fputc('\t',file);fprintf x;}while(0)



void WriteNothing(MusicObject obj, FILE *file, int depth)
{
  Begin("WriteNothing");
  End;
}


void WriteNoteVoice(NoteVoice *a, FILE *file, int depth)
{
  Begin("WriteNoteVoice");

  fprintf(file, " %hd %x", a->pitch, (int)(a->modifiers));

  End;
}


Boolean ReadNoteVoice(NoteVoice *a, FILE *file)
{
  Begin("ReadNoteVoice");

  if (!ReadShort(file))
    Return(BuildFileError(file, "First argument of Voice lost or corrupted"));

  a->pitch = rShort;

  if (!ReadHexUnsignedInt(file))
    Return(BuildFileError(file, "Second argument of Voice lost or corrupted"));

  a->modifiers = (NoteMods)(rUInt);

  Return(True);
}


void WriteMetronome(MusicObject obj, FILE *file, int depth)
{
  MetronomePart m = ((Metronome *)obj)->metronome;

  Begin("WriteMetronome");

  Write((file, "Metronome\n"));
  Write((file, "\t%s %u\n", m.beat.chord.visual->name, m.setting));

  End;
}


Boolean ReadMetronome(MusicObject obj, FILE *file)
{
  int     i;
  Boolean dotted;
  NoteTag tag;

  Begin("ReadMetronome");

  if (!ReadTag(file))
    Return(BuildFileError(file,
			  "Couldn't read Metronome Type or Dotted segment"));

  if (!strcasecmp(rTag, "Dotted")) {

    dotted = True;
    if (!ReadTag(file))
      Return(BuildFileError(file, "Couldn't read Metronome Type segment"));

  } else dotted = False;

  for (i = 0; i < noteVisualCount; ++i)
    if (!strncasecmp(noteVisuals[i].undotted.name, rTag, strlen(rTag))) {
      tag = noteVisuals[i].undotted.type;
      break;
    }

  if (!ReadUnsignedInt(file))
    Return(BuildFileError(file, "Couldn't read Metronome Setting segment"));

  (void)NewMetronome((Metronome *)obj, tag, dotted, rUInt);

  Return(True);
}


void WriteClef(MusicObject obj, FILE *file, int depth)
{
  ClefPart c = ((Clef *)obj)->clef;

  Begin("WriteClef");

  Write((file, "Clef\n"));
  Write((file, "\tClef Name %s\n", c.visual->name));

  End;
}


Boolean ReadClef(MusicObject obj, FILE *file)
{
  int i;

  Begin("ReadClef");

  if (!ReadConstTag("Clef", file) || !ReadConstTag("Name", file))
    Return(BuildFileError(file, "Expected Clef Name segment not found"));

  if (!ReadTag(file))
    Return(BuildFileError(file, "Couldn't read Clef Name segment"));

  for (i = 0; i < clefVisualCount; ++i)

    if (!strncasecmp(rTag, clefVisuals[i].name, strlen(rTag))) {

      (void)NewClef((Clef *)obj, clefVisuals[i].type);
      break;
    }

  if (i >= clefVisualCount)
    Return(BuildFileError2s(file, "Unknown Clef Name `%s'", rTag));

  Return(True);
}


void WriteKey(MusicObject obj, FILE *file, int depth)
{
  KeyPart k = ((Key *)obj)->key;

  Begin("WriteKey");

  Write((file, "Key\n"));
  Write((file, "\tKey Name %s\n", k.visual->name));

  End;
}


Boolean ReadKey(MusicObject obj, FILE *file)
{
  int i;

  Begin("ReadKey");

  if (!ReadConstTag("Key", file) || !ReadConstTag("Name", file))
    Return(BuildFileError(file, "Expected Key Name segment not found"));

  if (!ReadString(file))
    Return(BuildFileError(file, "Couldn't read Key Name segment"));

  for (i = 0; i < keyVisualCount; ++i)
    if (!strcasecmp(rString, keyVisuals[i].name)) {
      (void)NewKey((Key *)obj, keyVisuals[i].key);
      break;
    }

  if (i >= keyVisualCount)
    Return(BuildFileError2s(file, "Unknown Key Name `%s'", rString));

  Return(True);
}


void WriteText(MusicObject obj, FILE *file, int depth)
{
  TextPart t = ((Text *)obj)->text;

  Begin("WriteText");

  Write((file, "Text\n"));
  Write((file, "\tString %s\n", t.text));
  Write((file, "\tPosition %d\n", (int)(t.position)));

  End;
}


Boolean ReadText(MusicObject obj, FILE *file)
{
  Begin("ReadText");

  if (!ReadConstTag("String", file))
    Return(BuildFileError(file, "Expected Text String segment not found"));

  if (!ReadString(file))
    Return(BuildFileError(file, "Couldn't read Text String segment"));

  if (!ReadConstTag("Position", file))
    Return(BuildFileError(file, "Expected Text Position segment not found"));

  if (!ReadInt(file))
    Return(BuildFileError(file, "Couldn't read Text Position segment"));

  (void)NewText((Text *)obj, rString, (TextPosnTag)rInt);

  Return(True);
}


void WriteRest(MusicObject obj, FILE *file, int depth)
{
  RestPart r = ((Rest *)obj)->rest;

  Begin("WriteRest");

  Write((file, "Rest\n"));
  Write((file, "\t%s\n", r.visual->name));

#ifdef DEBUG
  if (file == stderr) {
    if (((Rest *)obj)->phrase.tied_forward)  Write((file,"\tTied Forward\n"));
    if (((Rest *)obj)->phrase.tied_backward) Write((file,"\tTied Backward\n"));
  }
#endif

  End;
}


Boolean ReadRest(MusicObject obj, FILE *file)
{
  int i;

  Begin("ReadRest");

  if (!ReadWS(file))
    Return(BuildFileError(file,
			  "Unexpected EOF (expecting Rest Name segment)"));
  
  if (!ReadString(file))
    Return(BuildFileError(file, "Couldn't read Rest Name segment"));

  for (i = 0; i < restVisualCount; ++i) {
    if (!strcasecmp(restVisuals[i].undotted.name, rString)) {
      (void)NewRest((Rest *)obj, restVisuals[i].undotted.type, False);
      break;
    }
    if (!strcasecmp(restVisuals[i].dotted.name, rString)) {
      (void)NewRest((Rest *)obj, restVisuals[i].dotted.type, True);
      break;
    }
  }

  if (i >= restVisualCount)
    Return(BuildFileError2s(file, "Unknown Rest Name `%s'", rString));

  Return(True);
}


void WriteChord(MusicObject obj, FILE *file, int depth)
{
  int       i;
  ChordPart c = ((Chord *)obj)->chord;

  Begin("WriteChord");

  Write((file, ": %s %lx %hd",
	 c.visual->name, c.modifiers, c.voice_count));
  for (i = 0; i < c.voice_count; ++i)
    WriteNoteVoice(&(c.voices[i]), file, depth+1);

  fputc('\n', file);

#ifdef DEBUG
  if (file == stderr) {
    if (((Chord *)obj)->phrase.tied_forward) Write((file,"\tTied Forward\n"));
    if (((Chord *)obj)->phrase.tied_backward)Write((file,"\tTied Backward\n"));
  }
#endif

  End;
}


Boolean ReadChord(MusicObject obj, FILE *file)
{
  int        i;
  short      n;
  NoteVoice *v;
  ChordMods  m;
  NoteTag    tag;
  Boolean    dotted;

  Begin("ReadChord");

  if (!ReadTag(file))
    Return(BuildFileError(file, "Couldn't read Chord Type or Dotted segment"));

  if (!strcasecmp(rTag, "Dotted")) {

    dotted = True;
    if (!ReadTag(file))
      Return(BuildFileError(file, "Couldn't read Chord Type segment"));

  } else dotted = False;

  for (i = 0; i < noteVisualCount; ++i)
    if (!strncasecmp(noteVisuals[i].undotted.name, rTag, strlen(rTag))) {
      tag = noteVisuals[i].undotted.type;
      break;
    }

  if (i > noteVisualCount)
    Return(BuildFileError2s(file, "Unknown Chord Type `%s'", rTag));

  if (!ReadHexUnsignedLong(file))
    Return(BuildFileError(file, "Expected Chord Modifier segment not found"));

  if (!ReadShort(file))
    Return(BuildFileError(file,
			  "Expected Chord Voice Count segment not found"));

  m = rULong;
  n = rShort;
  v = (NoteVoice *)XtMalloc(n * sizeof(NoteVoice));

  for (i = 0; i < n; ++i)
    if (!ReadNoteVoice(&(v[i]), file)) {
      XtFree((void *)v);
      Return(BuildFileError(file, "Too few Note Voices in Chord segment?"));
    }

  (void)NewChord((Chord *)obj, v, n, m, tag, dotted);

  Return(True);
}


void WriteGroup(MusicObject obj, FILE *file, int depth)
{
  Group   *g = (Group *)obj;
  GroupTag t = ((Group *)obj)->group.type;
  ItemList i;

  Begin("WriteGroup");

  Write((file, "Group\n"));
  Write((file, "\t%s",
	 t == GroupBeamed       ? "Beamed" :
	 t == GroupTupled       ? "Tupled" :
	 t == GroupDeGrace      ? "Grace"  : "Ordinary"));

  if (t == GroupTupled) {
    fprintf(file, " %hd %hd\n", g->group.tupled_length, g->group.tupled_count);
  } else {
    fputc('\n', file);
  }

#ifdef DEBUG
  if (file == stderr) {
    if (g->phrase.tied_forward)  Write((file,"\tTied Forward\n"));
    if (g->phrase.tied_backward) Write((file,"\tTied Backward\n"));
  }
#endif

  for (i = g->group.start; i; i = (i == g->group.end) ? NULL : iNext(i)) {
    i->item->methods->write((MusicObject)(i->item), file, depth+1);
    WriteMarks(i, file, depth+1); WriteFixedBars(i, file, depth+1);
  }

  Write((file, "\tEnd\n"));

  End;
}


Boolean ReadGroup(MusicObject obj, FILE *file)
{
  ItemList       items;
  GroupTag       type; 
  short          tupled_length = 0;
  short          tupled_count = 0;

  Begin("ReadGroup");

  if (readingGroup) Return
    (BuildFileError
     (file, "This implementation does not support groups within groups"));

  if (!ReadTag(file))
    Return(BuildFileError(file, "Expected Group Type tag not found"));

  if      (!strcasecmp(rTag, "Beamed"  )) type = GroupBeamed;
  else if (!strcasecmp(rTag, "Tupled"  )) type = GroupTupled;
  else if (!strcasecmp(rTag, "Grace"   )) type = GroupDeGrace;
  else if (!strcasecmp(rTag, "Ordinary")) type = GroupNoDecoration;
  else Return(BuildFileError2s(file, "Unknown Group Type tag `%s'", rTag));

  if (type == GroupTupled) {

    if (!ReadInt(file))
      Return(BuildFileError(file, "Expected Group Tupled Length not found"));
    tupled_length = NumberToMTime(rInt);

    if (!ReadInt(file))
      Return(BuildFileError(file, "Expected Group Tupled Count not found"));
    tupled_count = NumberToMTime(rInt);
  }

  readingGroup = True;

  if (!ReadItemList(&items, file))
    Return(BuildFileError(file, "Couldn't read Item List for Group"));

  (void)NewGroup((Group *)obj, type, items, (ItemList)Last(items));
  ((Group *)obj)->group.tupled_length = tupled_length;
  ((Group *)obj)->group.tupled_count  = tupled_count;

  readingGroup = False;

  Return(True);
}


/* ReadItem is the only Read function to actually allocate its own
   space, as we don't know how big the Item will be beforehand -- this
   is a polymorphic function.  (Apart from ReadChord, that is, which
   allocates space for the NoteVoices itself, and ReadItemList, which
   calls for NewItemLists left, right and centre.) */

#define ReadItemFailed 0
#define ReadItemEndFound 1
#define ReadItemSucceeded 2

int ReadItemOrMark(IO MusicObject *obj, INPUT ItemList items, FILE *file)
{
  int         i;
  MusicObject item;

  Begin("ReadItemOrMark");

  if (!ReadTag(file))
    Return(BuildFileError(file,"Expected Item Discrimination Tag not found"));

  for (i = 0; i < XtNumber(itemTags); ++i)
    if (!strcmp(rTag, itemTags[i].tag)) break;

  if (i >= XtNumber(itemTags)) {

    if (!strcasecmp(rTag, "Mark")) {

      if (!items) {
	BuildFileError
	  (file,"Mark indicated before any items read, or on Group item");
	Return(ReadItemFailed);
      }

      if (!ReadMark(items, file)) {
	BuildFileError(file, "Couldn't read Mark associated with Item");
	Return(ReadItemFailed);
      }

      *obj = NULL;
      Return(ReadItemSucceeded);

    } else if (!strcasecmp(rTag, "Follows") || !strcasecmp(rTag, "Precedes")) {

      if (!items) {
	BuildFileError
	  (file,"Fixed Bar indicated before any items read, or on Group item");
	Return(ReadItemFailed);
      }

      if (!ReadFixedBar(rTag, items, file)) {
	BuildFileError(file, "Couldn't read Fixed Bar associated with Item");
	Return(ReadItemFailed);
      }

      *obj = NULL;
      Return(ReadItemSucceeded);

    } else if (!strcasecmp(rTag, "End")) {

      Return(ReadItemEndFound);

    } else {

      BuildFileError2s(file, "Unknown Item Discrimination Tag `%s'", rTag);
      Return(ReadItemFailed);
    }
  } else {

    item = (MusicObject)XtMalloc(itemTags[i].size);

    if (!itemTags[i].read(item, file)) {
      XtFree(item);
      BuildFileError2s(file, "Couldn't read indicated %s Item",itemTags[i].tag);
      Return(ReadItemFailed);
    }

    *obj = item;
    Return(ReadItemSucceeded);
  }
}

 

static Boolean ReadItemList(ItemList *list, FILE *file)
{
  ItemList items = NULL;
  Item    *item = NULL;
  int      rtn;

  Begin("ReadItemList");

  while ((rtn = ReadItemOrMark((MusicObject *)&item, items, file)) ==
	 ReadItemSucceeded) {

    if (item) items = (ItemList)Nconc(items, NewItemList(item));
    /* otherwise it must have been a mark */
  }

  if (rtn == ReadItemFailed) {
    for (items = (ItemList)First(items);
	 items; items = iNext(items)) XtFree((char *)(items->item));
    DestroyList(items);
    Return(BuildFileError(file,"Couldn't read up to ItemList end tag"));
  }

  *list = (ItemList)First(items);
  (void)ItemListEnsureIntegrity(*list);

  Return(True);
}



MajorStave LoadStaveFromFile(FILE *file)
{
  int         i;
  char        t;
  int         staves;
  ItemList  * music = NULL;
  String    * names = NULL;
  static char message[256];
  Boolean     havePreamble = False;
  MajorStave  sp = NULL;

  Begin("LoadStaveFromFile");

  StaveBusyStartCount(0);
  StaveBusyMakeCount(0);
  readingGroup = False;

  if (musicIOErrorMessage) {
    XtFree(musicIOErrorMessage);
    musicIOErrorMessage = NULL;
  }

  if (fscanf(file,
	     "#!Rosegarden\n#\n#  Musical Notation File\n%c\n\n", &t) != 1 ||
      t != '#') {

    BuildFileError(file, "File Identifier Preamble missing or corrupted");
    goto failed;
  } else havePreamble = True;

  if (!ReadConstTag("RV20", file)) {
    if (YQuery(XtParent(musicViewport),
	       "Not a Rosegarden v2.0 file - import as old format?", 2, 0, 1,
	       "Yes", "No", "Editor Tools - Import Old Rosegarden File") == 0) {
      rewind(file);
      Return(old_LoadStaveFromFile(file));
    } else {
      BuildFileError(file, "Not a Rosegarden Version 2.0 file");
      goto failed;
    }
  }

  if (!ReadConstTag("Staves", file)) {
    BuildFileError(file, "Expected Number-of-Staves Counter not found");
    goto failed;
  }

  if (!ReadInt(file)) {
    BuildFileError(file, "Couldn't read Number-of-Staves Counter value");
    goto failed;
  }

  staves = rInt;
  music  = (ItemList *)XtMalloc(staves * sizeof(ItemList));
  names  = (String   *)XtMalloc(staves * sizeof(String));

  StaveBusyStartCount(staves + 3);

  for (i = 0; i < staves; ++i) {
    music[i] = NULL;
    names[i] = NULL;
  }

  for (i = 0; i < staves; ++i) {

    StaveBusyMakeCount(i + 1);

    if (!ReadConstTag("Name", file)) {
      BuildFileError(file, "Expected Stave Name segment not found");
      goto failed;
    }

    if (!ReadString(file)) {
      BuildFileError(file, "Couldn't read Stave Name");
      goto failed;
    }

    names[i] = XtNewString(rString);

    if (!ReadItemList(&(music[i]), file)) {
      BuildFileError(file, "Couldn't read Item List for contents of Stave");
      goto failed;
    }
  }

  StaveBusyMakeCount(staves + 1);
  for (i = 0; i < staves; ++i) EnsureMarkIntegrity(music[i]);
  sp = NewStave(staves, music);
  for (i = 0; i < staves; ++i) {
    StaveRenameStave(sp, i, names[i]);
    StaveResetFormatting(sp, i);
    StaveFormatBars(sp, i, -1);
  }

 barStyles:

  StaveBusyMakeCount(staves + 2);

  if (!ReadTag(file)) {
    BuildFileError(file, "End or Bar Style marker apparently missing");
    goto failed;
  }

  if (!strcasecmp(rTag, "End")) goto succeeded;
  if (!strcasecmp(rTag, "Stave")) goto haveStaveTag;

  if (strcasecmp(rTag, "Bar")) {
    BuildFileError2s
      (file, "Unexpected tag `%s' (expecting `Stave', `Bar' or `End')", rTag);
    goto failed;
  }

  {
    int sno, num;

    if (!ReadInt(file)) {
      BuildFileError(file, "Couldn't read Bar Time staff number segment");
      goto failed;
    }
    sno = rInt;

    if (!ReadHexUnsignedLong(file)) {
      BuildFileError(file, "Couldn't read Bar Time bar number segment");
      goto failed;
    }

    if (!ReadConstTag("time", file) || !ReadInt(file)) {
      BuildFileError(file, "Couldn't read Bar Time numerator segment");
      goto failed;
    }
    num = rInt;

    if (!ReadInt(file)) {
      BuildFileError(file, "Couldn't read Bar Time denominator segment");
      goto failed;
    }

    StaveAddBarTime(sp, sno, rULong, num, rInt);
    goto barStyles;
  }

 haveStaveTag:

  {
    int sno, type;

    if (!ReadInt(file)) {
      BuildFileError(file, "Couldn't read Stave Style staff number segment");
      goto failed;
    }
    sno = rInt;
    
    if (!ReadConstTag("tags", file) || !ReadInt(file)) {
      BuildFileError(file, "Couldn't read Stave Style Precede marker");
      goto failed;
    }
    type = rInt;

    if (!ReadInt(file)) {
      BuildFileError(file, "Couldn't read Stave Style Follow marker");
      goto failed;
    }

    StaveSetEndBarTags(sp, sno, type, rInt);
  }

  goto barStyles;

 succeeded:

  fclose(file);
  StaveBusyFinishCount();
  Return(sp);

 failed:

  StaveBusyFinishCount();

  if (sp) StaveDestroy(sp, True);
  else {

    if (music) {
      for (i = 0; i < staves && music[i]; ++i) DestroyItemList(music[i]);
      XtFree((void *)music);
    }

    if (names) {
      for (i = 0; i < staves && names[i]; ++i) XtFree(names[i]);
      XtFree((void *)names);
    }
  }

  if (!havePreamble) {
    if (YFileGetLastFilename(False) == NULL)
      sprintf(message, "File is not a Rosegarden notation file.");
    else
      sprintf(message, "``%s'' is not a Rosegarden notation file.",
	      YFileGetLastFilename(False));
  } else {
    if (YFileGetLastFilename(False) == NULL)
      sprintf(message, "Cannot load file, sorry.");
    else
      sprintf(message, "Cannot load ``%s'', sorry.",
	      YFileGetLastFilename(False));
  }

  if (musicIOErrorMessage) {

    if (YQuery(XtParent(musicViewport), message, 2, 0, 0,
	       "Continue", "Show Error Report", NULL) == 1)
      (void)YQuery(XtParent(musicViewport),
		   musicIOErrorMessage, 1, 0, 0, "Continue", NULL);
  } else {

    (void)YQuery(XtParent(musicViewport), message, 1, 0, 0, "Continue", NULL);
  }

  fclose(file);
  Return(NULL);
}


unsigned int markIOIndex;
static char *markIONames[] = { "Tie", "Slur", "Crescendo", "Decrescendo" };

void WriteMarks(ItemList i, FILE *file, int depth)
{
  MarkList mlist;
  Begin("WriteMarks");

  for (mlist = (MarkList)First(i->item->item.marks); mlist;
       mlist = (MarkList)Next(mlist)) {

    if (!mlist->mark->other_end) {
      fprintf(stderr, "Warning: Found mark (type %d) with invalid other-end "
	      "field; ignoring\n", mlist->mark->type);
      continue;
    }

    if (mlist->mark->start) {
    
      Write((file, "Mark start %u %s\n",
	     markIOIndex, markIONames[(int)mlist->mark->type]));
      mlist->mark->ilist = (MusicObject)markIOIndex;
      ++markIOIndex;

    } else {
      Write((file, "Mark end %u\n", (int)mlist->mark->other_end->ilist));
    }
  }

  End;
}


void WriteFixedBars(ItemList i, FILE *file, int depth)
{
  int t;
  Begin("WriteFixedBars");

  t = i->item->item.bar_tag.follows;

  if (t != NoFixedBar)
    Write((file, "Follows %s\n", t == 1 ? "Double" : "Repeat"));

  t = i->item->item.bar_tag.precedes;

  if (t != NoFixedBar)
    Write((file, "Precedes %s\n", t == 1 ? "Double" : "Repeat"));  

  End;
}


/* This one assumes the Mark tag has already been read */

static Boolean ReadMark(ItemList i, FILE *file)
{
  int type;
  Mark *newMark;
  Begin("ReadMarks");

  if (!ReadTag(file))
    Return(BuildFileError(file, "Couldn't read Mark Start/End segment"));

  if (!ReadUnsignedInt(file))
    Return(BuildFileError(file, "Couldn't read Mark Index segment"));

  if (!strcasecmp(rTag, "start")) {

    if (!ReadTag(file))
      Return(BuildFileError(file, "Couldn't read Mark Type segment"));

    for (type = 0; type < XtNumber(markIONames); ++type) {
      if (!strcasecmp(markIONames[type], rTag)) break;
    }

    if (type >= XtNumber(markIONames))
      Return(BuildFileError2s(file, "Unknown Mark Type tag %s", rTag));
    
    newMark = NewMark(NULL, (MarkTag)type, True, NULL);
    newMark->ilist = (MusicObject)rUInt;

    i->item->item.marks = (MarkList)Nconc
      (i->item->item.marks, NewMarkList(newMark));

  } else {

    newMark = NewMark(NULL, (MarkTag)1 /* ie. don't know yet */, False, NULL);
    newMark->ilist = (MusicObject)rUInt;

    i->item->item.marks = (MarkList)Nconc
      (i->item->item.marks, NewMarkList(newMark));
  }

  Return(True);
}


Boolean ReadFixedBar(String tag, ItemList i, FILE *file)
{
  Boolean follows;
  Begin("ReadFixedBar");

  follows = !strcasecmp(tag, "Follows");

  if (!ReadTag(file)) {
    Return(BuildFileError(file, "Couldn't read Fixed Bar Type segment"));
  }
  
  if (!strcasecmp(rTag, "Double")) {
    if (follows) i->item->item.bar_tag.follows  = DoubleBar;
    else         i->item->item.bar_tag.precedes = DoubleBar;
  } else if (!strcasecmp(rTag, "Repeat")) {
    if (follows) i->item->item.bar_tag.follows  = RepeatBar;
    else         i->item->item.bar_tag.precedes = RepeatBar;
  } else {
    Return(BuildFileError2s(file,"Unknown or unexpected Fixed Bar segment `%s'",
			    rTag));
  }

  Return(True);
}



/* Utility error and reading functions. */


static Boolean ReadWS(FILE *file)
{
  Begin("ReadWS");

  (void)fscanf(file, " ");

  if (ferror(file))
    Return(BuildFileError(file, "File error while consuming whitespace"));

  if (feof(file))
    Return(BuildFileError(file, "Unexpected EOF"));

  Return(True);
}


static Boolean ReadInt(FILE *file)
{
  int i, j;

  Begin("ReadInt");

  i = fscanf(file, " %d", &j);

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting integer)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting integer)"));

  rInt = j;
  Return(True);
}


static Boolean ReadUnsignedInt(FILE *file)
{
  int          i;
  unsigned int j;

  Begin("ReadUnsignedInt");

  i = fscanf(file, " %u", &j);

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting unsigned int)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting unsigned int)"));

  rUInt = j;
  Return(True);
}


static Boolean ReadHexUnsignedInt(FILE *file)
{
  int          i;
  unsigned int j;

  Begin("ReadHexUnsignedInt");

  i = fscanf(file, " %x", &j);

  if (i == EOF)
    Return(BuildFileError(file,
			  "Unexpected EOF (expecting hex unsigned int)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting hex unsigned int)"));

  rUInt = j;
  Return(True);
}


static Boolean ReadHexUnsignedLong(FILE *file)
{
  int           i;
  unsigned long j;

  Begin("ReadHexUnsignedLong");

  i = fscanf(file, " %lx", &j);

  if (i == EOF)
    Return(BuildFileError(file,
			  "Unexpected EOF (expecting hex unsigned long int)"));
  if (i != 1)
    Return(BuildFileError(file,
			  "Syntax Error (expecting hex unsigned long int)"));

  rULong = j;
  Return(True);
}


static Boolean ReadShort(FILE *file)
{
  int   i;
  short j;

  Begin("ReadShort");

  i = fscanf(file, " %hd", &j);

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting short int)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting short int)"));

  rShort = j;
  Return(True);
}


static Boolean ReadTag(FILE *file)
{
  int         i;
  static char s[TAG_LEN];

  Begin("ReadTag");

  i = fscanf(file, " %s", s);

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting tag)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting tag)"));

  strcpy(rTag, s);
  Return(True);
}


static Boolean ReadConstTag(String tag, FILE *file)
{
  int         i;
  static char s[TAG_LEN];

  Begin("ReadTag");

  i = fscanf(file, " %s", s);

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting a tag)"));
  if (i != 1)
    Return(BuildFileError(file, "Syntax Error (expecting a tag)"));

  if (strcasecmp(s, tag) != 0)
    Return(BuildFileError3s(file, "Wrong tag (expecting `%s', got `%s')",
			    tag, s));

  Return(True);
}


static Boolean ReadString(FILE *file)
{
  int         i = 0;
  int         n = 0;
  static char s[STRING_LEN];

  Begin("ReadString");

  do { i = getc(file); } while (i != EOF && (i == ' ' || i == '\t'));

  if (i == EOF)
    Return(BuildFileError(file, "Unexpected EOF (expecting a string)"));

  if (i == '\n') { rString[0] = '\0'; Return(True); }
  if (i) { s[0] = (char)i; ++n; }

  if (fgets(s + n, STRING_LEN - 1 - n, file) == NULL)
    Return(BuildFileError(file, "Unexpected EOF (expecting a string)"));

  for (i = strlen(s); i > 0 && s[i-1] == '\n'; s[--i] = '\0');

  strcpy(rString, s);
  Return(True);
}



static Boolean BuildFileError(FILE *file, String message)
{
  int i;

  Begin("BuildFileError");

  if (musicIOErrorMessage) {

    i = strlen(musicIOErrorMessage);

    musicIOErrorMessage =
      (String)XtRealloc(musicIOErrorMessage, i + strlen(message) + 20);
    sprintf(musicIOErrorMessage+i-1, "[%ld]  %s\n\n", ftell(file), message);

  } else {

    musicIOErrorMessage = (String)XtMalloc(strlen(message) + 100);
    sprintf(musicIOErrorMessage, "\nRead Error Report\n\n[%ld]  %s\n\n",
	    ftell(file), message);
  }

  Return(False);
}


static Boolean BuildFileError2s(FILE *file, String message, String c)
{
  String s;

  Begin("BuildFileError2s");

  s = (String)XtMalloc(strlen(message) + strlen(c) + 3);
  sprintf(s, message, c);
  (void)BuildFileError(file, s);
  XtFree(s);

  Return(False);
}


static Boolean BuildFileError3s(FILE *file, String message, String c, String d)
{
  String s;

  Begin("BuildFileError3s");

  s = (String)XtMalloc(strlen(message) + strlen(c) + strlen(d) + 3);
  sprintf(s, message, c, d);
  (void)BuildFileError(file, s);
  XtFree(s);

  Return(False);
}

