
/* ItemList.c */

/* Musical Notation Editor for X, Chris Cannam 1994       */
/* Functions for dealing with manipulations of ItemLists. */
/* This is not as easy as just dealing with Lists; the    */
/* presence of Groups within the ItemList, with their     */
/* accompanying pointer inconsistency, makes things much  */
/* more complicated.                                      */

#include "Classes.h"
#include "Lists.h"
#include "ItemList.h"


void CheckItemListGroupingIntegrity(ItemList list)
{
#ifdef REPORT_STAFF_INTEGRITY
  ItemList i, j;
  long n = 0;
  Boolean problems = False;

  Begin("CheckStaffIntegrity");

  for (ItemList_ITERATE(i, (ItemList)First(list))) {

    ++n;

    if (i->item->object_class == GroupClass) {

      for (ItemList_ITERATE_GROUP(j, (Group *)(i->item))) {

	if (j->item->object_class == GroupClass) {
	  fprintf(stderr,
		  "Warning: list %p contains nested group at %p (item %ld)\n"
		  "(group %p, start %p, contained within %p, start %p)\n",
		  list, j, n, j->item, ((Group *)j->item)->group.start,
		  i->item, ((Group *)i->item)->group.start);
	  problems = True;
	}
      }
    }
  }

  if (!problems) fprintf(stderr, "Integrity of list %p okay\n", list);

  End;

#endif
}



/* ItemListEnsureIntegrity: call this on a newly-read itemlist in  */
/* format A.  These are lists in which groups' itemlists are not   */
/* sublists of the main lists, but instead are complete lists with */
/* pointers to NULL at both ends. EnsureItemListIntegrity converts */
/* these to format B, with the groups' itemlists as sublists.      */

ItemList ItemListEnsureIntegrity(ItemList i)
{
  ItemList rtn;

  Begin("ItemListEnsureIntegrity");

  i = rtn = (ItemList)First(i);

  while (i) {

    if (i->item->object_class == GroupClass) {

      if (Next(i)) {

	i = (ItemList)Insert(((Group *)i->item)->group.start, Next(i));

      } else {

	Nconc(i, ((Group *)i->item)->group.start);
	i = iNext(i);
      }

    } else i = iNext(i);
  }

  Return(rtn);
}
  
  
/* Pass grouping type and first and last items in prospective */
/* group's item list.  Returns new (group) item in list.      */

ItemList ItemListEnGroup(GroupTag type, ItemList begin, ItemList end,
			 Boolean rearrange)
{
  ItemList list, sublist, groupa, groupz;
  GroupTag etag;
  Begin("ItemListEnGroup");

  if (!begin || !end)
    Error("ItemListEnGroup called with invalid argument");

  /* An experimental measure.  ItemListExpungeGroups could still leave
     a group completely enclosing the "expunged" area, so as to permit
     things like deleting items from within a group without breaking
     the group up.  If we want to regroup these items, we're gonna
     have to go back and break up the surrounding group.  So check for
     this case. */

  /* We only want to do this if we're explicitly grouping something
     from the menu -- otherwise it's probably overkill for something
     we may already have checked, and it may break things that call
     this method assuming it won't change anything else -- so only do
     it if we've got "rearrange" true */

  if (rearrange) {

    for (list = iPrev(begin);
	 list && list->item->object_class != GroupClass; list = iPrev(list));

    if (list) {

      groupa = ((Group *)list->item)->group.start;
      groupz = ((Group *)list->item)->group.end;
      etag   = ((Group *)list->item)->group.type;

      for (sublist = groupa; sublist;
	   sublist = (sublist == groupz ? NULL : iNext(sublist))) {

	if (sublist == begin) {

	  ItemListUnGroup(list);

	  if (sublist != groupa && iPrev(sublist) != groupa) {
	    ItemListEnGroup(etag, groupa, iPrev(sublist), False);
	  }

	  while (sublist && sublist != groupz) {
	    if (sublist == end && iNext(sublist) && (iNext(sublist) != groupz)){
	      ItemListEnGroup(etag, iNext(sublist), groupz, False);
	      break;
	    }
	    sublist = iNext(sublist);
	  }
	}
      }
    }
  }

  Return((ItemList)Insert
	 (NewItemList((Item *)NewGroup(NULL, type, begin, end)), begin));
}


/* Pass group item in list.  Returns list that was first in */
/* group's itemlist.                                        */

ItemList ItemListUnGroup(ItemList groupList)
{
  Begin("ItemListUnGroup");

  if (groupList->item->object_class != GroupClass)
    Error("ItemListUnGroup called with invalid argument");

  /* accumulate group's Marks onto first group item */

  if (((Group *)groupList->item)->group.start) {
    ((Group *)groupList->item)->group.start->item->item.marks = (MarkList)Nconc
      (groupList->item->item.marks,
       ((Group *)groupList->item)->group.start->item->item.marks);
  }

  groupList->item->methods->destroy(groupList->item);
  Return((ItemList)Remove(groupList));
}



/* This deals with group references in a sublist.  It ensures    */
/* that no group references any object within the sublist, and   */
/* that the sublist contains no group objects (any previously    */
/* beginning within the sublist being moved to just after).      */

/* start and end are the `left' items; start is the item to the  */
/* left of the first item, end the item to the left of the last. */
/* Returns first item in the whole list (which may have changed).*/

ItemList ItemListExpungeGroups(ItemList first, ItemList start, ItemList end)
{
  ItemList list;
  ItemList temp;
  ItemList groupl = NULL;
  ItemList groupa = NULL;
  ItemList groupz = NULL;

  Begin("ItemListExpungeGroups");
  
  /* How do we deal with groups in cutting, &c.?  If any groups start */
  /* and end in the cut section, they are lost.  If any start before  */
  /* and end during, they are truncated.  If any start during and end */
  /* after, they are truncated.  If any start before and end after,   */
  /* their middle sections are (trivially) swiped.                    */

  /* We only need look for the nearest group in either direction,    */
  /* because groups can't contain groups.                            */

  for (list = start; list; list = iPrev(list)) {
    if (list->item->object_class == GroupClass) {
      groupl = list;
      groupa = ((Group *)list->item)->group.start;
      groupz = ((Group *)list->item)->group.end;
      break;
    }
  }

  if (start) list = iNext(start);
  else list = first;

  while (list) {

    if (list == groupa)	{	   /* group item before, starts within? Zap */
      ItemListUnGroup(groupl);
      groupl = groupa = groupz = NULL;
      list = iNext(list);
      continue;
    }

    if (list == groupz) {	   /* starts before, ends within?  Truncate */
      ((Group *)groupl->item)->group.end = start;
      ItemListNotifyGroupChange(groupl);
      groupl = groupa = groupz = NULL;
      list = iNext(list);
      continue;
    }

    if (list->item->object_class == GroupClass) { /* starts within? */

      if (list == first) first = iNext(list);

      for (temp = ((Group *)list->item)->group.start;
	   temp; temp = iNext(temp))

	if (temp == ((Group *)list->item)->group.end) {

	  list = ItemListUnGroup(list);         /* ... ends ... within? Zap */
	  break;

	} else if (temp == end && Next(temp)) { /* without? Zap and regroup */

	  if (Next(end)) ItemListEnGroup
	    (((Group *)list->item)->group.type, iNext(end),
	     ((Group *)list->item)->group.end, False);

	  list = ItemListUnGroup(list);
	  break;

	} else if (temp == end) {
	
	  list = ItemListUnGroup(list);
	  break;
	}

    } else list = iNext(list);

    if (list && Prev(list) && end == iPrev(list)) break;
  }

  Return(first);
}



/* Auto-Beaming Heuristic:                                   */
/*                                                           */
/* First, we need the number of units to try to beam in.  If */
/* we're in 4/4, we probably beam quavers in twos (and semis */
/* in fours and so on); if 6/8, we beam quavers in threes;   */
/* if 2/4, in twos again; if something weird (5/8? 7/8?) we  */
/* probably just beam in fives or sevens.  Seems we just     */
/* want the lowest prime divisor for the number of quavers,  */
/* and to take semis and so on from there.                   */
/*                                                           */
/* Then we skip through the section, accumulating times.     */
/* On finding that the accumulated time is a multiple of the */
/* length of a beamed group (two quavers in 4/4, three in    */
/* 6/8, &c.), we check to see if there's a note ending       */
/* cleanly at the end of another beamed group's length, and  */
/* if so, and if there are at least two beamable items in    */
/* between here and there, we beam them up.  ("...Scotty")   */
/*                                                           */
/* This returns the item list; if `start' was the first item */
/* and has been engrouped, the return value will be the new  */
/* group item; otherwise it'll just be `start'.              */

ItemList ItemListAutoBeamSub(MTime avg, MTime min, MTime max,
			     ItemList start, ItemList end, MTime barLength)
{
  MTime    accumulator;		/* ah, for the days of *real* CPUs */
  MTime    prospective;
  MTime    temp;
  MTime    tmin;
  MTime    beamLength;
  ClassTag oclass;
  MTime    current;
  int      beamable;
  int      longerThanDemi;
  ItemList i, j, k;
  ItemList rtn;
  
  Begin("ItemListAutoBeamSub");

  rtn = start;

  for (i = start, accumulator = zeroTime;
       i && (iPrev(i) != end); i = iNext(i)) {

    oclass = i->item->object_class;

    if (oclass == GroupClass) continue;

    if (MTimeToNumber(accumulator) % MTimeToNumber(barLength) == 0)
      accumulator = zeroTime;

    if ((MTimeToNumber(accumulator) % MTimeToNumber(avg) == 0) &&
	(oclass == ChordClass || oclass ==  RestClass) &&
	(i->item->methods->get_length(i->item) < TagToMTime(Crotchet, False))) {

      k              = NULL;
      tmin           = min;
      temp           = zeroTime;
      beamable       = 0;
      longerThanDemi = 0;

      for (j = i; j && (iPrev(j) != end); j = iNext(j)) {

	oclass  = j->item->object_class;
	current = j->item->methods->get_length(j->item);

	if (oclass == ChordClass) {

	  if (((Chord *)j->item)->chord.visual->type < Crotchet) ++beamable;
	  if (((Chord *)j->item)->chord.visual->type >= Semiquaver)
	    ++longerThanDemi;
	}

	temp = AddMTime(temp, current);

	if (MTimeGreater(temp, zeroTime) &&
	    MTimeToNumber(temp) % MTimeToNumber(tmin) == 0) {
	  k = j;
	  beamLength = temp;

	  /* Will this group have crossed a bar line?  If so, wrap */
	  /* around the prospective accumulator from the bar mark  */

	  prospective =
	    NumberToMTime(MTimeToNumber(AddMTime(accumulator, temp)) %
			  MTimeToNumber(barLength));

	  tmin = NumberToMTime(2 * MTimeToNumber(tmin));
	}

	/* Decide to stop scanning for items to join this beamed group. */
	/* We stop if we've got the maximum length of beamed group, if  */
	/* we've got more than 4 semis or quavers, if we're at the end  */
	/* of the piece or of a bar, if there's a rest ahead or if the  */
	/* next chord is longer than the current one.                   */

	/* All this, of course, results is an absolutely magnificent    */
	/* conditional.  Ha!  I spurn your puny temporary variables.    */

	if (!MTimeLesser(temp, max) || longerThanDemi >= 4 || !Next(j) ||
	    (k && !MTimeToNumber(prospective)) ||
	    (iNext(j))->item->object_class == RestClass ||
	    (oclass == ChordClass &&
	     (iNext(j))->item->object_class == ChordClass &&
	     MTimeGreater
	     ((iNext(j))->item->methods->get_length
	      ((iNext(j))->item), current))) {

	  if (k && beamable >= 2) {

	    if (i == start && !Prev(i))
	      rtn = ItemListEnGroup(GroupBeamed, i, k, False);
	    else
	      (void)ItemListEnGroup(GroupBeamed, i, k, False);
	  }

	  /* If this group is longer than the check threshold (`avg'), */
	  /* its length must be a multiple of the threshold and hence  */
	  /* we can keep scanning from the end of the group without    */
	  /* losing the modulo properties of the accumulator.  Other-  */
	  /* wise, we continue from where we were.  (The latter action */
	  /* must be safe -- we can't get another group starting half- */
	  /* way through the last one, because we know the last one    */
	  /* is shorter than the group start check threshold.)         */

	  if (k && !MTimeLesser(beamLength, avg)) {

	    i = k;
	    accumulator = prospective;

	  } else {

	    accumulator = AddMTime
	      (accumulator,
	       i->item->methods->get_length(i->item));
	  }

	  break;
	}
      }
    } else {

      accumulator = AddMTime
	(accumulator,
	 i->item->methods->get_length(i->item));
    }
  }


  CheckItemListGroupingIntegrity(rtn);

  Return(rtn);
}


ItemList ItemListAutoBeam(TimeSignature *time, ItemList start, ItemList end)
{
  int   num;
  MTime avg;
  MTime min;
  MTime barLength;

  Begin("ItemListAutoBeam");

  if (time) {

    barLength = time->bar_length;

    /* Minimal number for grouping should ideally be smallest prime */
    /* divisor of bar's numerator.  We'll relax the "prime" bit,    */
    /* but ensure that it's at least 2.                             */

    /* Later comment: no, this isn't true.  If the denominator is   */
    /* 2 or 4, we should always beam in twos (in 3/4, 6/2 or stng.) */

    /* (Actually this isn't right either.  I think if the numerator */
    /* is prime, perhaps, we should beam up to the length of one    */
    /* beat at a time?  Something like that, anyway.  Leave it for  */
    /* a rainy day sometime.)                                       */

    if (time->denominator == 2 ||
	time->denominator == 4) {

      if (time->numerator % 3) {

	avg = NumberToMTime(TagToNumber(Quaver,     False));
	min = NumberToMTime(TagToNumber(Semiquaver, False));

      } else avg = min = NumberToMTime(TagToNumber(Semiquaver, False));

    } else {

      /* Special hack for 6/8.  This is getting dodgier by the minute */

      if (time->numerator   == 6 &&
	  time->denominator == 8) {

	avg = NumberToMTime(3 * TagToNumber(Quaver, False));
	min = NumberToMTime(MTimeToNumber(avg) / 2);

      } else {

	for (num = 2; time->numerator % num != 0; ++num);
	
	avg = NumberToMTime(num * TagToNumber(Semiquaver, False));
	min = NumberToMTime(MTimeToNumber(avg) / 2);
      }

      /* older code, works okay for most time signatures:

	 avg =
	 ((time->numerator== 4) ? (2 * TagToMTime(Semiquaver, False)) :
	 (time->numerator == 6) ? (3 * TagToMTime(Semiquaver, False)) :
	 (time->numerator * TagToMTime(Semiquaver, False)));

	 min =
	 ((time->numerator== 4) ? TagToMTime(Semiquaver, False) :
	 (time->numerator == 6) ? TagToMTime(Semiquaver,  True) :
	 TagToMTime(Semiquaver, False));
      */
    }
  } else {

    barLength = 4 * TagToMTime(Crotchet, False);

    avg = TagToMTime(Quaver, False);
    min = TagToMTime(Semiquaver, False);
  }

  if (time->denominator > 4) avg /= 2;

  Return(ItemListAutoBeamSub(avg, min,
			     NumberToMTime(4 * MTimeToNumber(avg)),
			     start, end, barLength));
}


/* An element in an ItemList has changed, so notify any containing
   group that its beam/layout cache is no longer valid */

void ItemListNotifyGroupChange(ItemList list)
{
  Begin("ItemListNotifyGroupChange");

  /* be lazy: just scan back and invalidate cache on the first group
     we meet -- that particular group may actually be supposed to be
     unaffected, but at least we're not going to miss it */

  while (list) {

    if (list->item->object_class == GroupClass) {
      ((Group *)list->item)->group.cache_valid = False;
      End;
    }

    list = iPrev(list);
  }

  End;
}

