
/* Format.c */

/* Musical Notation Editor for X, Chris Cannam 1994 */

/* File containing methods specific to the working  */
/* of the Bar class: formatting notes into bars.    */

#include "General.h"
#include "Notes.h"
#include "Classes.h"
#include "Format.h"
#include "ItemList.h"
#include "StavePrivate.h"

static void SplitChord  (ItemList, MTime, MTime);
static void SplitRest   (ItemList, MTime, MTime);
static void SplitGroup  (ItemList, MTime, MTime,
			 /* TimeSignature **, */ Clef **, Key **);

static void MergeChords (ItemList);
static void MergeRests  (ItemList);
static void MergeGroups (ItemList);

static Key *prevKey = NULL;
static KeyTag prevKeyTag = KeyC;
static NoteMods accidentalTable[7];
static NoteMods newAccidentalTable[7];



void ResetStaffKey(Key *key)
{
  Begin("ResetStaffKey");
  prevKey = key;
  if (key) prevKeyTag = key->key.visual->key;
  else prevKeyTag = KeyC;
  End;
}


void InitialiseAccidentalTable(Key *key)
{
  int     n;
  Pitch   pitch;
  Boolean sharps;

  Begin("InitialiseAccidentalTable");

  if (prevKey != key) key->key.changing_from = prevKeyTag;
  prevKeyTag = key->key.key;
  prevKey = key;

  sharps = key->key.visual->sharps;
  pitch  = sharps ? 8 : 4;

  for (n = 0; n < 7; ++n) accidentalTable[n] = ModNone;

  for (n = 0; n < key->key.visual->number; ++n) {

    if (sharps) accidentalTable[(pitch + 70) % 7] |= ModSharp;
    else        accidentalTable[(pitch + 70) % 7] |= ModFlat;

    if (sharps) pitch -= 3;
    else        pitch += 3;
  }

  End;
}


void UpdateAccidentalTable(Chord *chord)
{
  int        n;
  int        p;
  int        mods;
  NoteMods   cmods;
  NoteVoice *v;
  Boolean    changed = False;

  Begin("UpdateAccidentalTable");

  /* need newAccidentalTable for double-buffering to prevent one
     accidental in achord aliasing another one on another octave in
     the same chord -- whole chord must be aliased according to the
     accidentalTable in force when it starts */

  for (n = 0; n < 7; ++n) newAccidentalTable[n] = accidentalTable[n];

  chord->chord.note_modifier_width = 0;

  for (n = 0, v = chord->chord.voices; n < chord->chord.voice_count; ++n, ++v) {

    p = (v->pitch + 70) % 7;

    if (accidentalTable[p] != ModNone) {

      if (v->modifiers == ModNone) {

	v->display_mods = ModNatural;
	newAccidentalTable[p] = ModNone;
	changed = True;

      } else if (v->modifiers & ModNatural) {

	v->display_mods = v->modifiers;
	newAccidentalTable[p] = v->modifiers ^ ModNatural;
	changed = True;

      } else if (v->modifiers == accidentalTable[p]) {

	v->display_mods = ModNone;

      } else {

	v->display_mods = v->modifiers | ModNatural;
	newAccidentalTable[p] = v->modifiers;
	changed = True;
      }

    } else {

      v->display_mods = v->modifiers;
      newAccidentalTable[p] |= v->modifiers;
      changed = True;
    }

    for (mods = 0, cmods = v->display_mods; cmods; cmods >>= 1)
      if (cmods & 1) ++mods;

    if (mods * NoteModWidth > chord->chord.note_modifier_width)
      chord->chord.note_modifier_width = mods * NoteModWidth;
  }

  chord->chord.note_modifier_width = chord->chord.note_modifier_width * 2 / 3;

  if (changed) {
    for (n = 0; n < 7; ++n) accidentalTable[n] = newAccidentalTable[n];
  }

  End;
}


static void SplitChord(ItemList items, MTime chordLength, MTime needed)
{
  int        i;
  Chord     *a, *b;
  NoteTag    tag1, tag2;
  Boolean    dotted1, dotted2;
  Boolean    tiedBack;
  MTime      secondLength;
  MarkList   marks;
  NoteVoice *voices;

  Begin("SplitChord");
  
  if (!(MTimeGreater(chordLength, needed))) End;
  secondLength = SubtractMTime(chordLength, needed);

  tag1 = MTimeToTag(needed,       &dotted1);
  tag2 = MTimeToTag(secondLength, &dotted2);

  if (!MTimeEqual(TagToMTime(tag1, dotted1), needed) ||
      !MTimeEqual(TagToMTime(tag2, dotted2), secondLength)) End;

  a = (Chord *)(items->item);
  tiedBack = a->phrase.tied_backward;
  marks = a->item.marks;

  (void)NewChord(a,
		 a->chord.voices,
		 a->chord.voice_count,
		 a->chord.modifiers,
		 tag1, dotted1);

  a->item.marks = marks;
  voices = (NoteVoice *)XtMalloc(a->chord.voice_count * sizeof(NoteVoice));
  for (i = 0; i < a->chord.voice_count; ++i) voices[i] = a->chord.voices[i];

  b   = NewChord(NULL,
		 voices,
		 a->chord.voice_count,
		 a->chord.modifiers,
		 tag2, dotted2);

  a->phrase.tied_forward  = True;
  b->phrase.tied_backward = True;
  a->phrase.tied_backward = tiedBack;

  if (Next(items)) Insert(NewItemList((Item *)b), Next(items));
  else       Nconc(items, NewItemList((Item *)b));

  End;
}


static void SplitRest(ItemList items, MTime restLength, MTime needed)
{
  Rest      *a, *b;
  NoteTag    tag;
  Boolean    dotted;
  Boolean    tiedBack;
  MTime      secondLength;
  MarkList   marks;

  Begin("SplitRest");

  if (!(MTimeGreater(restLength, needed))) End;
  secondLength = SubtractMTime(restLength, needed);

  tag = MTimeToTag(needed, &dotted);
  a   = (Rest *)(items->item);

  tiedBack = a->phrase.tied_backward;
  marks = a->item.marks;

  (void)NewRest(a, tag, dotted);

  a->item.marks = marks;
  tag = MTimeToTag(secondLength, &dotted);
  b   = NewRest(NULL, tag, dotted);

  a->phrase.tied_forward  = True;
  b->phrase.tied_backward = True;
  a->phrase.tied_backward = tiedBack;

  if (Next(items)) Insert(NewItemList((Item *)b), Next(items));
  else       Nconc(items, NewItemList((Item *)b));

  End;
}


static void SplitGroup(ItemList items, MTime length, MTime needed,
		       /* TimeSignature **timeReturn,*/ Clef **clefReturn,
		       Key **keyReturn)
{
  MTime    thisLength;
  Item    *item;
  Group   *group = (Group *)items->item;
  ClassTag oclass;

  Begin("SplitGroup");

  if (group->object_class != GroupClass ||
      group->group.type != GroupBeamed) End; /* don't split tuplets, 3/96 */

  for (items = iNext(items); items;
       items = (items == group->group.end) ? NULL : iNext(items)) {

    item   = items->item;
    oclass = item->object_class;

    if (oclass == ChordClass || oclass == RestClass) {

      if (((Phrase *)items->item)->phrase.tied_forward) {

	if (Next(items)) {

	  switch(oclass) {
	  case ChordClass: MergeChords(items); break;
	  case  RestClass: MergeRests (items); break;
	  }
	} else ((Phrase *)items->item)->phrase.tied_forward = False;
      }

      if (oclass == ChordClass) UpdateAccidentalTable((Chord *)items->item);

    } else {

      switch(oclass) {

      case ClefClass:
	if (clefReturn) *clefReturn = (Clef *)item; break;
      case KeyClass:
	InitialiseAccidentalTable((Key *)item);
	if (keyReturn) *keyReturn = (Key *)item;
	break;
      default: break;
      }
    }

    thisLength = item->methods->get_length((MusicObject)item);

    if (MTimeGreater(thisLength, needed)) {

      switch(oclass) {
	
      case ChordClass: SplitChord(items, thisLength, needed); break;
      case  RestClass: SplitRest (items, thisLength, needed); break;
      }

      if (items == group->group.end)
	ItemListEnGroup
	  (group->group.type, iNext(items), iNext(items), False);
      else
	ItemListEnGroup
	  (group->group.type, iNext(items), group->group.end, False);

      group->group.end = items;
      group->phrase.tied_forward = True;
      ((Group *)((iNext(items))->item))->phrase.tied_backward = True;
      End;
    }

    needed = SubtractMTime(needed, thisLength);
    
    if (!(MTimeGreater(needed, zeroTime))) {

      if (items == group->group.end)
	ItemListEnGroup
	  (group->group.type, iNext(items), iNext(items), False);
      else
	ItemListEnGroup
	  (group->group.type, iNext(items), group->group.end, False);

      group->group.end = items;
      group->phrase.tied_forward = True;
      ((Group *)((iNext(items))->item))->phrase.tied_backward = True;
      End;
    }
  }

  group->group.cache_valid = False;

  End;
}


static void MergeChords(ItemList items)
{
  NoteTag  tag;
  Boolean  dotted;
  Chord   *a, *b;
  MTime    atime, btime, total;
  MarkList marks;

  Begin("MergeChords");

  if (!Next(items)) {
    ((Chord *)(items->item))->phrase.tied_forward = False;
    End;
  }

  a = (Chord *)(items->item);
  b = (Chord *)((iNext(items))->item);

  if (b->object_class != ChordClass) return;

  atime = a->methods->get_length((MusicObject)a);
  btime = a->methods->get_length((MusicObject)b);
  total = AddMTime(atime, btime);

  if (MTimeGreater(total, longestChord.chord.length)) return;
  tag = MTimeToTag(total, &dotted);
  marks = a->item.marks;

  (void)NewChord(a,
		 a->chord.voices,
		 a->chord.voice_count,
		 a->chord.modifiers,
		 tag, dotted);

  a->item.marks = marks;
  DestroyChord((MusicObject)b);
  Remove(Next(items));

  End;
}


static void MergeRests(ItemList items)
{
  NoteTag  tag;
  Boolean  dotted;
  Rest    *a, *b;
  MTime    atime, btime, total;
  MarkList marks;

  Begin("MergeRests");

  if (!Next(items)) {
    ((Rest *)(items->item))->phrase.tied_forward = False;
    End;
  }

  a = (Rest *)(items->item);
  b = (Rest *)((iNext(items))->item);

  if (b->object_class != RestClass) End;

  atime = a->methods->get_length((MusicObject)a);
  btime = a->methods->get_length((MusicObject)b);
  total = AddMTime(atime, btime);

  if (MTimeGreater(total, longestChord.chord.length)) End;
  tag = MTimeToTag(total, &dotted);
  marks = a->item.marks;

  (void)NewRest(a, tag, dotted);

  a->item.marks = marks;
  DestroyRest((MusicObject)b);
  Remove(Next(items));

  End;
}


static void MergeGroups(ItemList items)
{
  Group   *a, *b;
  ItemList aa;
  ItemList bz;
  GroupTag type;

  Begin("MergeGroups");

  a = (Group *)(items->item);

  if (!Next(a->group.end)) {
    a->phrase.tied_forward = False;
    End;
  }

  b = (Group *)((iNext(a->group.end))->item);

  if (b->object_class != GroupClass) {
    a->phrase.tied_forward = False; /* is this right? [added cc,2/96] */
    End;
  }

  aa   = a->group.start;
  bz   = b->group.end;
  type = a->group.type;

  ItemListUnGroup(iNext(a->group.end));
  (void)NewGroup(a, type, aa, bz);

#ifdef DEBUG
  fprintf(stderr,"Unformatting list, 0x%p [0x%p] -> 0x%p [0x%p]\n",
	  aa, aa->item, bz, bz->item);
#endif

  UnformatItemList(&a->group.start, &a->group.end);

  End;
}
 


/* I know much of this is the same as SplitGroup.  I'll consider  */
/* merging them later.  I just don't like the idea of SplitGroup  */
/* being (potentially) recursive, because of the problems you can */
/* run into when you have groups within groups.                   */

ItemList FormatBar(Bar            *bar,
		   Boolean         tiedBackward,
		   Boolean        *tiedForwardReturn,
		   ItemList        items,
		   Clef           *clef,
		   Clef          **clefReturn,
		   Key            *key,
		   Key           **keyReturn,
		   MTime          *delta)
{
  ItemList  i, j;
  Item     *item;
  ClassTag  oclass;
  MTime     length;
  MTime     thisLength;
  int       introState = 0;
  Boolean   intro = True;

  Begin("FormatBar");
  
  bar->bar.clef = clef;
  bar->bar.key  = key;
  bar->bar.start_time = *delta;

  bar->phrase.tied_backward = tiedBackward;

  bar->group.type  = GroupNoDecoration;
  bar->group.start = items;

  if (tiedForwardReturn) *tiedForwardReturn = False;
  if (clefReturn)        *clefReturn = clef;
  if (keyReturn)         *keyReturn  = key;

  length = bar->bar.time.bar_length;
  InitialiseAccidentalTable(key);

  *delta = AddMTime(*delta, length);

  for (ItemList_ITERATE(i, items)) {              /* see ItemList.h */

#ifdef DEBUG
    fprintf(stderr, "0x%p --\t\t\t\tprev 0x%p next 0x%p\n",
	    i, Prev(i), Next(i));

    i->item->methods->write((MusicObject)(i->item), stderr, 0);
#endif

    bar->group.end = i;

    item   = i->item;
    oclass = item->object_class;

    if (introState == 0) {
      if (oclass == ClefClass) introState = 1;
      else if (oclass == ChordClass || oclass == RestClass ||
	       oclass == GroupClass) introState = 2;
    }

    if (intro) {
      if (oclass == ChordClass || oclass == RestClass ||
	  oclass == GroupClass) intro = False;
    }

    if (oclass == ChordClass || oclass == RestClass || oclass == GroupClass) {

      if (((Phrase *)i->item)->phrase.tied_forward) {
	
	if (Next(i)) {

	  switch(oclass) {
	  case ChordClass: MergeChords(i); break;
	  case  RestClass: MergeRests (i); break;
	  case GroupClass: MergeGroups(i); break;
	  }
	} else ((Phrase *)i->item)->phrase.tied_forward = False;
      }

      if (oclass == ChordClass) UpdateAccidentalTable((Chord *)i->item);

    } else {

      switch(oclass) {

      case ClefClass:
	if (clefReturn) *clefReturn = (Clef *)item;
	break;

      case KeyClass:
	InitialiseAccidentalTable((Key *)item);
	if (keyReturn) *keyReturn = (Key *)item;
	break;

      default: break;
      }
    }

    thisLength = item->methods->get_length((MusicObject)item);

    if (MTimeGreater(thisLength, length)) {

      switch(oclass) {
	
      case ChordClass: SplitChord(i, thisLength, length); break;
      case  RestClass: SplitRest (i, thisLength, length); break;
      case GroupClass: SplitGroup(i, thisLength, length,
				  clefReturn, keyReturn); break;
      }

      bar->phrase.tied_forward = True;
      if (tiedForwardReturn) *tiedForwardReturn = True;
      if (oclass != GroupClass) Return(iNext(i));
      else Return(iNext(((Group *)i->item)->group.end));

    } else if (oclass == GroupClass) {

      for (j = ((Group *)item)->group.start;
	   j && (Prev(j) != (List)((Group *)item)->group.end);
	   j = iNext(j)) {

	switch (j->item->object_class) {

	case ClefClass:
	  if (clefReturn) *clefReturn = (Clef *)j->item; break;

	case KeyClass:
	  InitialiseAccidentalTable((Key *)j->item);
	  if (keyReturn) *keyReturn = (Key *)j->item;
	  break;

	case ChordClass:
	  UpdateAccidentalTable((Chord *)j->item);
	  break;

	default: break;
	}

	if (j->item->item.bar_tag.precedes != NoFixedBar ||
	    (iNext(j) && iNext(j)->item->item.bar_tag.follows != NoFixedBar)) {
	  Return(iNext(j));
	}
      }
    }

    length = SubtractMTime(length, thisLength);
    
    if (!(MTimeGreater(length, zeroTime))) {
      if (oclass != GroupClass) Return(iNext(i));
      else Return(iNext(((Group *)i->item)->group.end));
    }

    if (i->item->item.bar_tag.precedes != NoFixedBar ||
	(iNext(i) && iNext(i)->item->item.bar_tag.follows != NoFixedBar)) {
      Return(iNext(i));
    }
  }

  Return(NULL);
}



void UnformatItemList(ItemList *begin, ItemList *end) /* these are also rtns */
{
  Item    *item;
  ItemList items;
  ClassTag oclass;
  Boolean  ending = False;

  Begin("UnformatItemList");

  for (ItemList_ITERATE(items, *begin)) {

    item = items->item;
    oclass = item->object_class;

#ifdef DEBUG
    fprintf(stderr,"Inspecting item at 0x%p [0x%p]\n", items, item);
#endif

    if (end && items == *end) ending = True;

    if ((oclass==GroupClass || oclass==ChordClass || oclass==RestClass) &&
	((Phrase *)(items->item))->phrase.tied_forward)

      if (Next(items)) {

	if (end && Next(items) == (List)*end) {
	  ending = True; *end = (ItemList)items;
	}

	switch (oclass) {
	case GroupClass: MergeGroups(items); break;
	case ChordClass: MergeChords(items); break;
	case  RestClass: MergeRests(items);  break;
	}

      } else ((Phrase *)(items->item))->phrase.tied_forward = False;

    if (ending) break;
  }

#ifdef DEBUG
  fprintf(stderr,"\n\nUnformatted item list, items are\n\n");
  PrintItemList(*begin);
#endif
  
  End;
}


void PrintItemList(ItemList items)
{
  ItemList i;

  Begin("PrintItemList");

  for (ItemList_ITERATE(i, items)) {

    fprintf(stderr, "0x%p --\t\t\t\tprev 0x%p next 0x%p\n",
	    i, Prev(i), Next(i));

    i->item->methods->write((MusicObject)(i->item), stderr, 0);
  }

  End;
}


