/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2020 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: April 2020 */


/* This file contains part VI of the code for reading a PMW score file -
routines for setting stem flags on notes and chords, and sorting chords. */


#include "pmwhdr.h"
#include "readhdr.h"



typedef struct {
  uschar  pitch;
  uschar  inverted;
  uschar  acc;
  short int accleft;
  short int orig_accleft;
} accstr;


static uschar tuckoffset[] = { 100, 6, 6, 6, 8, 100,
                             100, 6, 6, 6, 6, 100 }; /* when bottom is a flat */



/*************************************************
*             Sort the notes in a chord          *
*************************************************/

/* For a chord, quite a lot of work must be done once the stem direction is
known. We must sort the notes into the correct order, so that the first one is
the one that gets the stems, we must arrange for certain notes to be printed
on the "wrong" side of the stem, and we must arrange the positioning of any
accidentals. If we are sorting the last chord that has been read, we must sort
stave_tiedata along with it. We must NOT do this when sorting other chords
(those that were stacked up).

Arguments:
  w             point to the first note of the chord
  upflag        TRUE for stem up

Returns:        nothing
*/

void
read_sortchord(b_notestr *w, int upflag)
{
usint dynamics = 0;
int SecondsExist = FALSE;
int fuq = 0;

int acc_count = 0;
int acc_explicit = FALSE;

b_notestr *ww;
b_notestr *www = NULL;
b_notestr *sorttop;
b_notestr sortvec[MAX_CHORDSIZE];

tiedata *tt;
tiedata sorttievec[MAX_CHORDSIZE];

sorttop = sortvec;      /* End of list pointer */
ww = w;                 /* Working pointer */
ww->type = b_chord;     /* Ensure all are flagged chord pro tem */
tt = stave_tiedata;     /* Working tiedata pointer */

/* Get the notes of the chord into sortvec, in ascending order, by using a
simple insertion (there won't be many of them). Collect the dynamics flags as
we go, and set the stem direction flag on each note. */

while (ww->type == b_chord)
  {
  b_notestr *insertptr = sortvec;
  tiedata *tieinsertptr = sorttievec;

  int pitch = ww->spitch;
  int flags = ww->flags;
  usint acflags = ww->acflags;

  if (ww->acc) acc_count++;
  if ((flags & nf_accleft) != 0) acc_explicit = TRUE;

  dynamics |= acflags & (af_dynamics | af_opposite);
  fuq |= flags & nf_fuq;

  ww->flags = upflag | (flags & ~(nf_dotright | nf_invert | nf_stemup));
  ww->acflags = acflags & ~(af_dynamics | af_opposite);

  while (insertptr < sorttop)
    {
    if (pitch < insertptr->spitch) break;
    insertptr++;
    tieinsertptr++;
    }

  memmove(tieinsertptr+1, tieinsertptr, (sorttop-insertptr)*sizeof(tiedata));
  *tieinsertptr = *tt++;

  memmove(insertptr+1, insertptr, (sorttop-insertptr)*sizeof(b_notestr));
  *insertptr = *ww;

  sorttop++;
  mac_advancechord(ww);
  }


/*********************/
#ifdef SORTCHORD
ww = w;
debug_printf("\n");
  {
  b_notestr *p;
  for (p = sortvec; p < sorttop; p++)
    {
    debug_printf("%d %d    %d %d\n",
      ww->acc, ww->spitch,
        p->acc, p->spitch);
    mac_advancechord(ww);
    }
  }
#endif
/*********************/


/* Now we can scan the sorted notes to see if any of them need to be printed
with their heads on the "wrong" side of their stems. (The same logic works for
stemless notes.) At the same time, force the flags for augmentation dots for
notes forming intervals of a second. */

ww = sortvec;

while (ww < sorttop - 1)
  {
  b_notestr *wwA = ww + 1;

  /* Check for an interval of a second. It doesn't count if one note is coupled
  and the other isn't. We can also cope with two notes at the same horizontal
  level (usually these will have different accidentals). */

  if (wwA->spitch - ww->spitch <= P_1S - P_1L &&
    (wwA->flags & nf_couple) == (ww->flags & nf_couple))

    {
    BOOL samelevel = wwA->spitch == ww->spitch;
    int count = 1;
    int increment, i;

    b_notestr *wwB = wwA + 1;
    b_notestr *wwL;

    /* Find the number of successive seconds; if the one pair are actually at
    the same level, we can't handle any more. */

    while (wwB < sorttop && wwB->spitch - wwA->spitch <= P_1S - P_1L &&
      (wwB->flags & nf_couple) == (wwA->flags & nf_couple))
        {
        if (wwB->spitch == wwA->spitch) samelevel = TRUE;
        count++;
        wwA = wwB++;
        }

    if (count > 1 && samelevel) error_moan(ERR80);

    count = (count + 1)/2;   /* number of pairs to consider */

    /* Now process all the intervals, working up if the stem is up, and down if
    the stem is down. This ensures that the note at the end of the stem is on
    the normal side of the stem. */

    if (upflag)
      {
      increment = 2;
      wwL = ww;
      }
    else
      {
      increment = -2;
      wwL = wwA - 1;
      }

    /* Loop through the pairs */

    for (i = 0; i < count; i++)
      {
      b_notestr *wwH = wwL + 1;

      /* Flag higher note of a 2nd for inverting if stem up. Note that the note
      may not exist if there were an even number of intervals. */

      if (upflag) { if (wwH < sorttop) wwH->flags |= nf_invert; }

      /* Flag lower note of a 2nd for inverting if stem down. Count this as an
      accidental, so as to cause accidental positioning to happen if there is
      at least one accidental on the chord. */

      else if (wwL >= sortvec)
        {
        wwL->flags |= nf_invert;
        acc_count++;
        }

      /* Flag bottom note for dot lowering and top for not so, provided the
      notes exist. (The forcible removal is for the case of re-processing after
      failure to print on two sides of a beam.) However, if the upper one is
      already flagged for dot raising, don't do this. */

      if ((wwH->flags & nf_highdot) == 0)
        {
        if (wwL >= sortvec) wwL->flags |= nf_lowdot;
        if (wwH <  sorttop) wwH->flags &= ~nf_lowdot;
        }

      /* Advance to the next pair of notes */
      wwL += increment;
      }

    /* Advance to check the rest of the chord */
    ww = wwB;

    /* Note that intervals of a 2nd exist in this chord */
    SecondsExist = TRUE;
    }

  /* This interval is not a second */
  else ww = wwA;
  }

/* If found any seconds, flag all the notes in the chord to print with any dots
moved right if the stem is up. */

if (SecondsExist && upflag)
  {
  ww = sortvec;
  while (ww < sorttop)
    {
    ww->flags |= nf_dotright;
    ww++;
    }
  }

/* Now we scan the chord to arrange the positioning of the accidentals. This is
done by using a matrix of positions which are filled in as the chord is scanned
from top to bottom. We do this only if there were no explicitly positioned
accidentals anywhere in the chord and there is more than one accidental (or at
least one accidental and one inverted note). */

if (!acc_explicit && acc_count > 1)
  {
  int state = 0;
  accstr a_matrix[MAX_CHORDSIZE];
  accstr *row = a_matrix;
  accstr *a_end;

  /* First initialize the matrix, in descending order. Copy only those notes
  that have accidentals or inverted noteheads. */

  for (ww = sorttop-1; ww >= sortvec; ww--)
    {
    int flags = ww->flags;
    if (ww->acc == 0 && (flags & nf_invert) == 0) continue;

    row->pitch = ww->spitch;

    if ((flags & nf_couple) != 0)
      row->pitch += ((flags & nf_coupleU) != 0)? 16 : -16;

    row->inverted = !upflag && ((flags & nf_invert) != 0);
    row->acc = ww->acc;

    row->accleft = row->orig_accleft = ww->accleft;
    row++;
    }
  a_end = row;


/*********************/
  #ifdef SORTCHORD
  debug_printf("Initialized matrix\n");
  row = a_matrix;
  while (row < a_end)
    {
    debug_printf("%d %d %d %f %f\n", row->pitch, row->inverted,
      row->acc, row->accleft, row->orig_accleft);
    row++;
    }
  #endif
/*********************/


  /* Now scan from top to bottom and determine offset. This algorithm works in
  two states. In state 0, there is clear space above, while in state 1 there
  may be clashes. */

  row = a_matrix;
  while (row < a_end)
    {
    accstr *nrow = row + 1;    /* pointer to next row */

    #ifdef SORTCHORD
    debug_printf("STATE=%d row->acc=%d\n", state, row->acc);
    #endif

    /* Deal with the case when all is clear above. If there is no accidental we
    just have an inverted note. */

    /* ---- STATE = 0 ---- */

    if (state == 0)
      {
      if (row->acc != 0)    /* 0 => no accidental */
        {
        /* If note is inverted, just position the accidental to clear it.
        Otherwise, search down for the next inversion and see if it is clear.
        */

        if (row->inverted) row->accleft += 6000; else /* add for invert */
          {
          accstr *nnrow = nrow;
          while (nnrow < a_end)
            {
            if (nnrow->inverted)
              {
              if ((row->pitch - nnrow->pitch) < ((row->acc <= ac_dflat)? 6:8))
                {
                row->accleft += ((row->pitch - nnrow->pitch) <= 4)?
                  ((row->acc <= ac_dflat)? 4500 : 6000) : 4500;
                }
              break;
              }
            nnrow++;
            }
          }
        }

      /* Change to state 1 if the next note is close enough */

      if (nrow < a_end &&
        (row->pitch - nrow->pitch) < ((row->acc <= ac_dflat)? 10:12))
          state = 1;
      }

    /* ---- STATE = 1 ---- */

    /* Deal with the case when not clear above. If there is no accidental we
    are at an inverted note. Accidentals above should have been positioned
    clear of it. We merely need to change state if we can. */

    else if (row->acc == 0)
      {
      if (nrow < a_end && (row->pitch - nrow->pitch) >= 10) state = 0;
      }

    /* There is an accidental -- we have to scan up and move it clear of
    previous accidentals where necessary. There will always be at least one
    previous row, as we can't get into state 1 when row is pointing to
    a_matrix. */

    else
      {
      int OK = FALSE;
      int offset = row->accleft;           /* basic offset */
      if (row->inverted) offset += 6000;   /* plus extra if inverted note */

      while (!OK)
        {
        accstr *prow = row - 1;  /* previous row */

        /* Loop, checking previous accidental positions for any overlap
        with the current accidental. */

        for(;;)   /* inner loop */
          {
          int thistop = row->pitch +
            ((row->acc < ac_flat)? 3 : (row->acc > ac_dflat)? 6 : 7);
          int thatbot = prow->pitch - ((prow->acc < ac_natural)? 3 : 6);
          int thisleft = offset;
          int thisright = offset - row->orig_accleft;
          int thatleft = prow->accleft;
          int thatright = thatleft - prow->orig_accleft;

          #ifdef SORTCHORD
          debug_printf("thistop=%d thatbot=%d\n", thistop, thatbot);
          debug_printf("thisleft=%f thisright=%f\n", thisleft, thisright);
          debug_printf("thatleft=%f thatright=%f\n", thatleft, thatright);
          #endif

          if (thistop > thatbot &&
              ((thatleft >= thisleft && thisleft > thatright) ||
               (thatleft > thisright && thisright >= thatright)))

          /* There is an overlap. Adjust the offset and break from the inner
          loop with OK still set FALSE. This will cause a repeat of the outer
          loop to check the new position. Note we insert an extra quarter point
          over and above the specified width. */

            { offset = thatleft + row->orig_accleft + 250; break; }

          /* We are clear of the accidental on the previous note, but need to
          check if we are clear of an inverted notehead. */

          if (prow->inverted)
            {
            thatbot = prow->pitch - 2;
            thatleft = 4500;           /* extra for notehead */
            thatright = 0;

            if (thistop > thatbot &&
                ((thatleft >= thisleft && thisleft > thatright) ||
                 (thatleft > thisright && thisright >= thatright)))

              { offset = thatleft + row->orig_accleft; break; }
            }

          /* Go back one more row; if no more, or if we have gone far enough,
          all is well, so break the inner loop with OK set TRUE. */

          if (--prow < a_matrix ||
            prow->pitch - row->pitch > ((prow->acc <= ac_dflat)? 10 : 12))
              { OK = TRUE; break; }
          }

        /* If we have come out with OK set, we are clear above, but this ain't
        enough. If the offset is small, we must check that the accidental will
        clear any subsequent inverted notehead. */

        if (OK && offset < row->orig_accleft + 4500)
          {
          accstr *nnrow = nrow;

          #ifdef SORTCHORD
          debug_printf("check invert below: offset=%f row->orig_accleft=%f\n",
            offset, row->orig_accleft);
          #endif

          while (nnrow < a_end)
            {
            if (nnrow->inverted)
              {
              if ((row->pitch - nnrow->pitch) < ((row->acc <= ac_dflat)? 6:10))
                {
                offset = row->orig_accleft +
                  ((row->pitch - nnrow->pitch <= 4)?
                    ((row->acc <= ac_dflat)? 4500 : 6000) : 4500);
                OK = FALSE;  /* unset OK so that the outer loops once more */
                }
              break;
              }
            nnrow++;
            }
          }
        }   /* End of while NOT OK loop */

      /* We have now positioned the accidental successfully. Check to see
      whether the next note is far down, and if so, reset the state. */

      row->accleft = offset;

      if (nrow < a_end &&
        (row->pitch - nrow->pitch) >= ((row->acc <= ac_dflat)? 10 : 12))
          state = 0;
      }

    /* Move on to next (accidentalized or inverted) note */

    row++;
    }

  /* We now have the basic positioning, but there is still a little
  optimization that can be helpful. If a natural or a (double) flat is to the
  left of another natural or (double) flat that is a bit above, and there is
  nothing in the way to the right below, we can move the accidental (and
  everything below it) a bit right, to "tuck it in". This code does not cope
  with all cases, but it catches the most common. */

  row = a_matrix;
  while (row < a_end)
    {
    if (row->accleft > row->orig_accleft + 250 &&
      (row->acc == ac_flat || row->acc == ac_natural || row->acc == ac_dflat))

      /* Check no inverted notes or rightwards accidentals here or below */
      {
      int OK = TRUE;
      accstr *nrow = row;
      while (nrow < a_end)
        {
        if (nrow->inverted || nrow->accleft < row->accleft)
          { OK = FALSE; break; }
        nrow++;
        }

      /* If clear below, find the rightwards accidental above */
      if (OK)
        {
        accstr *prow = row - 1;
        while (prow >= a_matrix)
          {
          int x;
          if (prow->pitch - row->pitch > 10) break;
          x = row->accleft - prow->accleft;

          if (
              /* Check for the nearest rightwards accidental above */
              (prow->acc != 0 && x > 0 && x < 10000) ||
              /* Check if it's an inverted note just above */
              (prow->inverted && row->accleft < 9500))
            {
            int flatbottom = (row->acc == ac_flat)? 6 : 0;
            if (prow->pitch - row->pitch >= tuckoffset[prow->acc + flatbottom])
              {
              accstr *xrow = row;
              while (xrow < a_end)
                {
                xrow->accleft = xrow->accleft - 2000;
                xrow++;
                }
              }
            break;
            }
          prow--;
          }
        }
      }

    /* Advance to check next accidental */
    row++;
    }


/*********************/
  #ifdef SORTCHORD
  debug_printf("Modified matrix\n");
  row = a_matrix;
  while (row < a_end)
    {
    debug_printf("%d %d %d %f %f\n", row->pitch, row->inverted,
      row->acc, row->accleft, row->orig_accleft);
    row++;
    }
  #endif
/*********************/


  /* Now set the information in the accleft byte */

  row = a_matrix;
  for (ww = sorttop-1; ww >= sortvec; ww--)
    {
    int flags = ww->flags;
    if (ww->acc == 0 && (flags & nf_invert) == 0) continue;
    ww->accleft = (row++)->accleft;
    }
  }

/* Now restore the data in the correct order -- ascending for stem down, and
descending for stem up. Adjust stave_tiedata if we are dealing with the
last-read chord. */

ww = w;
tt = stave_tiedata;

if (upflag)
  {
  b_notestr *wwP;
  tiedata *ttP = sorttievec + (sorttop - sortvec - 1);
  for (wwP = sorttop-1; wwP >= sortvec; wwP--)
    {
    *ww = *wwP;
    www = ww;   /* save last */
    mac_advancechord(ww);
    if (w == stave_firstnoteptr) *tt++ = *ttP--;
    }
  }
else
  {
  b_notestr *wwP;
  tiedata *ttP = sorttievec;
  for (wwP = sortvec; wwP < sorttop; wwP++)
    {
    *ww = *wwP;
    www = ww;   /* save last */
    mac_advancechord(ww);
    if (w == stave_firstnoteptr) *tt++ = *ttP++;
    }
  }

/* Dynamics to non-stem end in normal case; to stem end if flagged */

if ((dynamics & af_opposite) == 0) www->acflags |= dynamics;
  else w->acflags |= dynamics;

/* First note is "true" note; ensure it has the fuq bit if any of the notes in
the chord had it. */

w->type = b_note;
w->flags |= fuq;
}


/*************************************************
*      Reset stem direction for note or chord    *
*************************************************/

/* This reset procedure is called when PMW discovers that it cannot print a
beam with notes on both sides of it, in order to reset the stem direction of
some of the notes before trying again. That is why we clear out the stemup flag
before resetting.

Before we reset the flag for a chord, we must reset the offsets of any
accidentals, unless there is an explicit setting, because when the stem of a
chord is set, the accidental positions are calculated, assuming that what is
there already is the basic width of the accidental. As this is an error
situation, we don't have to get it perfect.

Arguments:
  noteptr        pointer
  flag           TRUE for stem up

Returns:         nothing
*/

void
read_resetstemflag(b_notestr *noteptr, int flag)
{
b_notestr *p = noteptr;

do
  {
  if (p->acc != 0)
    {
    p->accleft += curmovt->accspacing[p->acc] -
      curmovt->accadjusts[p->notetype];
    if ((p->flags & (nf_accrbra+nf_accsbra)) != 0)
      p->accleft += (p->acc == ac_dflat)? 6800 : 5300;
    }
  p->flags &= ~nf_stemup;
  mac_advancechord(p);
  }
  while (p->type == b_chord);

mac_setstemflag(noteptr, flag);
}


/*************************************************
*        Set stem directions for unforced beam   *
*************************************************/

/* This function is called at the end of a beam in all cases. For beams whose
stem direction is forced, there is nothing on the beam stack. This procedure is
even called for single notes that might have been the start of a beam, so we
use the call to set the fuq flag when the stem direction is known.

If the option for the stem swap level is "right", we can't take a decision
here, so the notes are transferred on to the ordinary note pending stack.

Arguments:  none
Returns:    nothing
*/

void
read_setbeamstems(void)
{
if (stave_beamstackptr > 0)
  {
  int i;
  int flag = 0;

  if (stave_maxaway == stave_stemswaplevel[curstave])
    {
    switch (curmovt->stemswaptype)
      {
      case stemswap_default:
      case stemswap_left:
      if (stave_laststemup) flag = nf_stemup;
      break;

      case stemswap_up:
      flag = nf_stemup;
      break;

      case stemswap_down:
      break;

      case stemswap_right:
      for (i = 0; i < stave_beamstackptr; i++)
        stave_stemstack[stave_stemstackptr++] = stave_beamstack[i];
      stave_beamstackptr = 0;
      return;
      }
    }
  else if (stave_maxaway < stave_stemswaplevel[curstave]) flag = nf_stemup;

  for (i = 0; i < stave_beamstackptr; i++)
    {  /* NB */
    mac_setstemflag(stave_beamstack[i], flag);
    }

  stave_beamstackptr = 0;
  stave_laststemup = flag != 0;

  mac_setstackedstems(flag);
  }

stave_beaming = FALSE;
if (stave_beamcount == 1 && (stave_beamfirstnote->flags & nf_stemup) != 0)
  stave_beamfirstnote->flags |= nf_fuq;
}


/* End of read6.c */
