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

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

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: August 2019 */


/* This file contains routines for formatting PMW-specific items of data for
printing, often in error messages. */


#include "pmwhdr.h"


static uschar *nlsharp[] = {
  US" C", US"#C", US" D", US"#D", US" E", US" F",
  US"#F", US" G", US"#G", US" A", US"#A", US" B" };

static uschar *nlflat[] = {
  US" C", US"$D", US" D", US"$E", US" E", US" F",
  US"$G", US" G", US"$A", US" A", US"$B", US" B" };

static uschar *octavestring[] = {
  US"```", US"``", US"`", US"", US"'", US"''", US"'''", US"''''" };

static int notelengths[] = {
  len_semibreve, len_minim, len_crotchet, len_quaver, len_squaver,
  len_dsquaver, len_hdsquaver };

static uschar *notenames[] = {
  US"semibreve", US"minim", US"crotchet", US"quaver", US"semiquaver",
  US"demisemiquaver", US"hemidemisemiquaver" };

static uschar notefactor[] = { 2, 3, 5, 7, 11 };



/*************************************************
*            Format a note length                *
*************************************************/

/* This function is used for "%l" formats in error messages. It turns a note
length into text, e.g. "4 crotchets".

Arguments:
  buff        where to put the text
  length      the note length

Returns:      number of bytes placed in the buffer
*/

static int
format_length(uschar *buff, int length)
{
usint i;
int count = 0;
int number = -1;
uschar *name = NULL;

/* Search for a whole number of a particular note type */

for (i = 0; i < sizeof(notelengths)/sizeof(int); i++)
  {
  if (length%notelengths[i] == 0)
    {
    number = length/notelengths[i];
    name = notenames[i];
    break;
    }
  }

/* If integral, it's a simple print */

if (number >= 0)
  count = sprintf(CS buff, "%d %s%s", number, name, (number==1)?"":"s");

/* Otherwise, compute the fraction of a crotchet */

else
  {
  int d = len_crotchet;
  name = US"of a crotchet";

  if (length > len_crotchet)
    {
    count += sprintf(CS buff, "%d ", length/len_crotchet);
    length = length%len_crotchet;
    name = US"crotchets";
    }

  for (i = 0; i < sizeof(notefactor); i++)
    {
    int x = notefactor[i];
    while (d%x == 0 && length%x == 0) { d /= x; length /= x; }
    }

  count += sprintf(CS buff+count, "%d/%d %s", length, d, name);
  }

return count;
}



/*************************************************
*            Format a fixed-point number         *
*************************************************/

/* All dimensions are held in fixed point with 3 decimal places. Some other
numbers are held this way too. This function is used for "%f" formats by
format_vsprintf() below. The number is right-justified to the field width if
the width is sufficiently large.

Arguments:
  buff        where to put the result
  n           the fixed point number
  width       the field width

Returns:      number of bytes placed in the buffer
*/

static int
format_fixed(uschar *buff, int n, int width)
{
int spacecount;
int count = 0;
div_t qr;
uschar tbuff[40];

if (n < 0)
  {
  tbuff[count++] = '-';
  n = -n;
  }

qr = div(n, 1000);
count += sprintf(CS tbuff + count, "%d", qr.quot);
if (qr.rem)
  {
  int r = qr.rem;
  uschar *z;
  if (r < 10) z = US".00";
    else if (r < 100) z = US".0";
      else z = US".";

  if ((r%100) == 0) r /= 100;
    else if ((r%10) == 0) r /= 10;

  count += sprintf(CS tbuff + count, "%s%d", z, r);
  }

spacecount = width - count;
if (spacecount <= 0) spacecount = 0; else Ustrcpy(buff, "          ");
Ustrcpy(buff+spacecount, tbuff);
return count + spacecount;
}



/*************************************************
*             Format a key signature             *
*************************************************/

/* This is used for "%k" format items.

Arguments:
  buff       buffer for the result
  key        the key signature

Returns:     the number of bytes placed in the buffer
*/

static int
format_key(uschar *buff, int key)
{
uschar *a = US"";
uschar *m = US"";
uschar *c = (key > 63)? US" cancel" : US"";

key &= 63;

if (key >= 21) { m = US"m"; key -= 21; }
if (key >= 14) { a = US"$"; key -= 14; }
  else if (key >= 7) { a = US"#"; key -= 7; }

return sprintf(CS buff, "%c%s%s%s", "ABCDEFG"[key], a, m, c);
}



/*************************************************
*             Format a time signature            *
*************************************************/

/* This is used for "%t" format items.

Arguments:
  buff       buffer for the result
  time       the time signature

Returns:     the number of bytes placed in the buffer
*/

static int
format_time(uschar *buff, int time)
{
int count = 0;
int m = (time & 0xFF0000) >> 16;
int n = (time & 0xFF00) >> 8;
int d = time & 255;

if (m != 1) count += sprintf(CS buff, "%d*", m);
if (d == time_common) return count + sprintf(CS buff+count, "C");
if (d == time_cut) return count + sprintf(CS buff+count, "A");
return count + sprintf(CS buff+count, "%d/%d", n, d);
}



/*************************************************
*           Format a stave/page list             *
*************************************************/

/* This is used for "%L" format items.

Arguments:
  buff       buffer for the result
  p          the list pointer

Returns:     the number of bytes placed in the buffer
*/

static int
format_list(uschar *buff, stave_list *p)
{
int count = 0;
uschar *c = US"";

while (p != NULL)
  {
  if (p->first == p->last) count += sprintf(CS buff+count, "%s%d", c, p->first);
    else count += sprintf(CS buff+count, "%s%d-%d", c, p->first, p->last);
  c = US",";
  p = p->next;
  }

return count;
}


/*************************************************
*             Format a bar number                *
*************************************************/

/* PMW keeps "true" bar numbers from 1 onwards. However, the existence of the
[nocount] directive defines a different set of bar numbers as seen by the user.
This procedure prints user bar numbers; for uncounted bars it prints "<n>.<m>".
Before calling format_vsprintf with a "%b" item, the caller should set
format_movt to point to the relevant movement block.

Arguments:
  buff       buffer for the result
  n          the bar number

Returns:     the number of bytes placed in the buffer
*/

static int
format_barnumber(uschar *buff, int n)
{
int a, b, p;

if (format_movt->barnovector[n+1] <= format_movt->barnovector[n])
  return sprintf(CS buff, "%d",
    n - format_movt->barnovector[n] + format_movt->baroffset);

p = n - 1;
while (p > 0 && format_movt->barnovector[p] < format_movt->barnovector[p+1])
  p--;

a = p - format_movt->barnovector[p] + format_movt->baroffset;
b = n - p;

if (a == 0) b--;
if (b == 0) return sprintf(CS buff, "%d", a);
  else return sprintf(CS buff, "%d.%d", a, b);
}



/*************************************************
*             Format a movement phrase           *
*************************************************/

/* This is used for "%M" format items. If there is only one movement in
existence, it returns a null string; otherwise it returns "in movement <n>".
If we can't find the movement (a case that should never happen) return nothing,
as that is less misleading!

Argument:    buffer for the result
Returns:     the number of bytes placed in the buffer
*/

static int
format_movement(uschar *buff)
{
int movtnumber = 1;
if (movement[2] == NULL) return 0;     /* Only movement 1 exists */
while (format_movt != movement[movtnumber])
  { if (movement[++movtnumber] == NULL) return 0; }
return sprintf(CS buff, " in movement %d", movtnumber);
}




/*************************************************
*            Format an absolute pitch            *
*************************************************/

/* This is used for "%P" format items. It is used when printing out information
about a stave. Zero means unset. As for barnumbers, format_movt must point to
the relevant movement. This is simply to have a look at the key signature in
order to decide whether to show "black" notes as sharps or flats.

Arguments:
  buff        buffer for result
  pitch       the pitch

Returns:      the number of bytes placed in the buffer
*/

static int
format_pitch(uschar *buff, int pitch)
{
int c;
if (pitch)
  {
  int octave = (pitch/12);
  int note = pitch%12;
  uschar **letters = (main_keysigtable[format_movt->key] >= 0)?
    nlsharp : nlflat;
  c = sprintf(CS buff, "%s%s", letters[note], octavestring[octave]);
  while (c < 5) sprintf(CS buff+c++, " ");
  }
else c = sprintf(CS buff, "unset");
return c;
}



/*************************************************
*   Format using a format string (ap arguments)  *
*************************************************/

/* This function is a private vsprintf() that recognizes a number of additional
formatting codes (e.g. "%b" for a bar number). It is mainly used for error and
informational output.

Arguments:
  buff        where to put the formatted string
  format      the format string
  ap          the va_list variable for any arguments

Returns:      the number of bytes placed in the buffer
*/

int
format_vsprintf(uschar *buff, const char *format, va_list ap)
{
uschar *p = buff;
while (*format)
  {
  int width = 0;
  BOOL lz = FALSE;

  if (*format == '%')
    {
    if (isdigit(*(++format)))
      {
      lz = *format == '0';
      do { width = width*10 + *format++ - '0'; } while (isdigit(*format));
      }

    switch (*format)
      {
      case 'b':
      p += format_barnumber(p, va_arg(ap, int));
      break;

      case 'B':
      p += sprintf(CS p, "%s", va_arg(ap, int)? "true" : "false");
      break;

      case 'c':
      p += sprintf(CS p, "%c", va_arg(ap, int));
      break;

      case 'd':
      p += sprintf(CS p, lz? "%0*d":"%*d", width, va_arg(ap, int));
      width = 0;
      break;

      case 'f':
      p += format_fixed(p, va_arg(ap, int), width);
      break;

      case 'g':
      p += sprintf(CS p, lz? "%0*g":"%*g", width, va_arg(ap, double));
      width = 0;
      break;

      case 'k':
      p += format_key(p, va_arg(ap, int));
      break;

      case 'l':
      p += format_length(p, va_arg(ap, int));
      break;

      case 'L':
      p += format_list(p, va_arg(ap, stave_list *));
      break;

      case 'M':
      p += format_movement(p);
      break;

      case 'P':
      p += format_pitch(p, va_arg(ap, int));
      break;

      case 'p':
      p += sprintf(CS p, "%p", (void *)va_arg(ap, uschar *));
      break;

      case 's':
      p += sprintf(CS p, "%s", va_arg(ap, uschar *));
      break;

      case 't':
      p += format_time(p, va_arg(ap, int));
      break;

      case 'x':
      p += sprintf(CS p, lz? "%0*x":"%*x", width, va_arg(ap, int));
      break;

      case 'X':
      p += sprintf(CS p, lz? "%0*X":"%*X", width, va_arg(ap, int));
      break;

      default:
      *p++ = *format;
      break;
      }
    format++;
    }
  else *p++ = *format++;
  }

*p = 0;
return p - buff;
}



/*************************************************
*  Format using a format string (... arguments)  *
*************************************************/

/* This function is a private sprintf() that recognizes a number of additional
formatting codes (e.g. "%b" for a bar number). It is mainly used for error and
informational output.

Arguments:
  buff        where to put the formatted string
  format      the format string
  ...         any arguments for the format

Returns:      the number of bytes placed in the buffer
*/

int
format_sprintf(uschar *buff, const char *format, ...)
{
va_list ap;
va_start(ap, format);
return format_vsprintf(buff, format, ap);
}


/* End of format.c */
