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

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

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: December 2010 */


/* This file contains initializing code, including the main program, which is
the entry point to PMW.


This comment is historical, left here for nostalgia purposes only:

------------------------------------------------------------------------------
PMW can be run in an windowing environment, or as a command-line program.
The windowing environments are of course system-specific, but have the
characteristic that they are event-driven. Thus in such an environment
we hand over control to a system routine, and only get it back (if at all)
when the program is finishing.

For the moment, we are thinking only of the RISC OS environment, but
writing as flexibly as possible, so that future ports are easier.
------------------------------------------------------------------------------

The future has arrived. This port of PMW for Unix-like systems runs only as a
single command, processing a single input file (though that may include 
other files). Much of the old apparatus for the previous windowing version has
been tidied away, but there are still a few quirks in the way the code works
that hark back to the original, event-driven implementation. */


#include "rdargs.h"
#include "pmwhdr.h"
#include "outhdr.h"


/* Keywords for the command line. If you increase the number of keys, make sure
that the keyoffset field in rdargs.c is big enough. */

static char *arg_pattern =
  ","
  "a4ona3/s,"
  "a4sideways/s,"
  "a5ona4/s,"
  "c/k/n,"
  "debug/s,"
  "dsb/k,"
  "dtp/k/n,"
  "duplex/s,"
  "eps/s,"
  "F/k,"
  "f/k,"
  "H/k,"
  "-help=help/s,"
  "includefont/s,"
  "MF/k," 
  "MP/k,"
  "MV/k,"
  "manualfeed/s,"
  "midi/k,"
  "mb=midibars/k,"
  "mm=midimovement/k/n,"
  "nr=norepeats/s,"
  "nw=nowidechars/s,"
  "o/k,"
  "p/k,"
  "pamphlet/s,"
  "printadjust/k/2,"
  "printgutter/k,"
  "printscale/k,"
  "printside/k/n,"
  "reverse/s,"
  "s/k,"
  "t/k/n,"
  "tumble/s,"
  "V/s,"
  "v/s";

/* Offsets for command line keys */

enum {
  arg_aa_input,    /* The only unkeyed possibility */
  arg_a4ona3,
  arg_a4sideways,
  arg_a5ona4,
  arg_c,
  arg_debug,
  arg_dsb,
  arg_dtp, 
  arg_duplex,
  arg_eps,
  arg_F,
  arg_f,
  arg_H,
  arg_help,
  arg_includefont,
  arg_MF, 
  arg_MP,
  arg_MV,
  arg_manualfeed,
  arg_midi,
  arg_midibars,
  arg_midimovement,
  arg_norepeats,
  arg_nowidechars,
  arg_o,
  arg_p,
  arg_pamphlet,
  arg_printadjustx,
  arg_printadjusty,
  arg_printgutter,
  arg_printscale,
  arg_printside,
  arg_reverse,
  arg_s,
  arg_t,
  arg_tumble,
  arg_V,
  arg_v
};


/* Parameters for debugging with -dsb option */

static int dsb_bar = -1;
static int dsb_movement = -1;
static int dsb_stave = -1;



/*************************************************
*          Given help on command syntax          *
*************************************************/

static void 
givehelp(void)
{
printf("\nPMW version %s\n%s\n", version_string, copyright);

printf("\n            OPTIONS\n\n");
printf("-a4ona3               print A4 images 2-up on A3\n");
printf("-a5ona4               print A5 images 2-up on A4\n");
printf("-a4sideways           assume A4 paper fed sideways\n");
printf("-c <number>           set number of copies\n");
printf("-debug                write debugging info to stderr\n");
printf("-dsb <m>,<s>,<b>      write debugging bar data (movement, stave, bar) \n");
printf("-dtp <bar>            write debugging position data (-1 for all bars)\n");
printf("-duplex               set duplex printing in the PostScript\n");
printf("-eps                  output encapsulated PostScript\n");
printf("-F <directory>        specify fontmetrics directory\n");
printf("-f <name>             specify format name\n");
printf("-H <file>             specify PostScript header file\n");
printf("-help                 output this information\n");
printf("-includefont          include PMW font in the output\n");
printf("-MF <directory>       specify PostScript music fonts directory\n");
printf("-MP <file>            specify MIDIperc file\n");
printf("-MV <file>            specify MIDIvoices file\n");
printf("-manualfeed           set manualfeed in the PostScript\n");
printf("-mb <range>           synonym for -midibars\n");
printf("-midi <file>          specify MIDI output file\n");
printf("-midibars <range>     limit MIDI output to given bar range\n");
printf("-midimovement <n>     specifies movement for MIDI output\n");
printf("-mm <n>               synonym for -midimovement\n");
#ifndef NO_PMWRC
printf("-norc                 don't read .pmwrc (must be first option)\n");
#endif
printf("-norepeats            do not play repeats in MIDI output\n");
printf("-nowidechars          don't use 100-point stave chars\n");
printf("-nr                   synonym for -norepeats\n");
printf("-nw                   synonym for -nowidechars\n");
printf("-o <file>             specify output file ('-' for stdout)\n");
printf("-p <list>             select pages\n");
printf("-pamphlet             print pages in pamphlet order\n");
printf("-printadjust <x> <y>  move on page by (x,y)\n");
printf("-printgutter <x>      move recto/verso pages by x/-x\n");
printf("-printscale <n>       scale the image by n\n");
printf("-printside <n>        print only odd or even sides\n");
printf("-reverse              output pages in reverse order\n");
printf("-s <list>             select staves\n");
printf("-t <number>           set transposition\n");
printf("-tumble               set tumble for duplex printing\n");
printf("-V                    output PMW version number\n");
printf("-v                    output verification information\n");

printf("\nDefault output is <input>.ps when a file name is given.\n");
printf("Default output is stdout if no file name is given.\n");

printf("\n          EXAMPLES\n\n");
printf("pmw myscore\n");
printf("pmw -s 1,2-4 -p 3,6-10,11 -f small -c 2 k491.pmw\n");
printf("pmw -pamphlet -a5ona4 scorefile\n");
printf("pmw -s 1 -midi zz.mid -mm 2 -mb 10-20 sonata\n");
}



/*************************************************
*           Print routine for info display       *
*************************************************/

/* This could just be replaced by fprintf() to stderr nowadays, but we keep the
separate function just in case in the future we want do so something else with
all this output. The function is global because it is also called from 
setdraw.c to show the contents of the draw stack.

Arguments:
  format      a format
  ...         data for the format
  
Returns:      nothing  
*/

void 
info_printf(char *format, ...)
{
uschar buff[256];
va_list ap;
va_start(ap, format);
format_vsprintf(buff, format, ap);
fprintf(stderr, "%s", CS buff);
va_end(ap);
}



/*************************************************
*        Display information about music         *
*************************************************/

/* This function is called after pagination if the -v option is present.

Arguments:  none
Returns:    nothing
*/

static void 
display_info(void)
{
pagestr *p = main_pageanchor;
int movt;
int laststave = -1;
int toppitch[max_stave+1];
int botpitch[max_stave+1];
int totalpitch[max_stave+1];
int notecount[max_stave+1];

info_printf("Data store used = ");
if (main_storetotal < 10000) info_printf("%d", main_storetotal);
  else info_printf("%dK", main_storetotal/1024);
info_printf(" (stave data ");
if (main_storestaves < 10000) info_printf("%d", main_storestaves);
  else info_printf("%dK", main_storestaves/1024);
info_printf(")\n");

/* Display information about the staves in each movement */

for (movt = 1; movt <= main_lastmovement; movt++)
  {
  int stave;
  movtstr *m = movement[movt];

  info_printf("\nMOVEMENT %d\n\n", movt);

  for (stave = 0; stave <= m->laststave; stave++)
    {
    stavestr *s = (m->stavetable)[stave];
    if (s == NULL) continue;               /* skips stave 0 if not there */

    info_printf("Stave %2d: ", stave);

    if (m->totalnocount == 0)
      info_printf("%d bar%s", s->lastbar, (s->lastbar == 1)? "":"s");
    else info_printf("%d(+%d) bars",
      s->lastbar - m->totalnocount, m->totalnocount);

    if (stave > laststave)
      {
      laststave = stave;
      toppitch[stave] = -1;
      botpitch[stave] = 9999;
      notecount[stave] = totalpitch[stave] = 0;
      }

    if (s->notecount > 0)
      {
      info_printf(";%s range  %P to %P average %P",
        (s->lastbar == 1)? " ":"",
          s->botpitch, s->toppitch, s->totalpitch/s->notecount);

      if (s->toppitch > toppitch[stave]) toppitch[stave] = s->toppitch;
      if (s->botpitch < botpitch[stave]) botpitch[stave] = s->botpitch;
      totalpitch[stave] += s->totalpitch;
      notecount[stave] += s->notecount;
      }

    info_printf("\n");
    }
  }


/* If there is more than one movement, display overall information for each
stave. */

if (main_lastmovement > 1)
  {
  int stave;
  info_printf("\nOVERALL\n\n");
  for (stave = 1; stave <= laststave; stave++)
    {
    info_printf("Stave %2d: ", stave);
    if (notecount[stave] > 0)
      info_printf("range  %P to %P average %P",
        botpitch[stave], toppitch[stave], totalpitch[stave]/notecount[stave]);
    info_printf("\n");
    }
  }


/* Now display information about the page layout */

if (p != NULL) info_printf("\nPAGE LAYOUT\n\n");

while (p != NULL)
  {
  int count = 14;
  sysblock *s = p->sysblocks;
  info_printf("Page %d bars: ", p->number);

  while (s != NULL)
    {
    if (s->type == sh_system)
      {
      format_movt = s->movt;
      if (count > 65)
        {
        info_printf("\n ");
        count = 1;
        }
      info_printf("%b-%b%s ", s->barstart, s->barend,
        (s->flags & sysblock_stretch)? "":"*");
      count += 6;
      if (s->overrun < 30)
        {
        info_printf("(%d) ", s->overrun);
        count += 5;
        }
      }
    s = s->next;
    }

  info_printf("\n  Space left on page = %f", p->spaceleft);
  if (p->overrun > 0 && p->overrun < 100000)
    info_printf(" Overrun = %f", p->overrun);
  info_printf("\n");

  p = p->next;
  }
}



/*************************************************
*         Convert string to stave map            *
*************************************************/

/* The turns strings like "1,3,4-6,10" into a bitmap.
For some
reason gcc gives a weird warning if ss is used directly with strtol, even with
the right casts. That's why I use another variable of type char *. 

Arguments:
  ss         the string
  endptr     where to return a pointer to the char after the last used
  map        pointer to bitmap, held as ints
  
Returns:     nothing   
*/

static void 
init_strtomap(uschar *ss, uschar **endptr, int *map)
{
long int i;
char *sss = (char *)ss;
*endptr = ss;
mac_initstave(map, 0);
while (isdigit(*sss))
  {
  long int s = strtol(sss, &sss, 0);
  long int t = s;
  if (*sss == '-')
    {
    sss++;
    t = strtol(sss, &sss, 0);
    }
  if (t < s || t > max_stave) return;   /* Not reached end will give error */
  for (i = s; i <= t; i++) mac_setstave(map, i);
  while (*sss == ',' || *sss == ' ') sss++;
  }
*endptr = US sss;
}



/*************************************************
*             Decode command line                *
*************************************************/

/* -V and -help act immediately; otherwise the values from the command line 
options are place in appropriate global variables.

Arguments:
  argc        the (possibly modified) command line argc
  argv        the (possibly modified) command line argv
  
Returns:      nothing
*/  

static void 
decode_command(int argc, char **argv)
{
arg_result results[80];
int rc = rdargs(argc, argv, arg_pattern, results);

if (rc != 0) 
  error_moan(0, results[0].text, results[1].text);  /* Hard */

/* Deal with -V */

if (results[arg_V].number != 0)
  {
  printf("PMW version %s\n%s\n", version_string, copyright);
  exit(EXIT_SUCCESS);
  }

/* Deal with -help */

if (results[arg_help].number != 0)
  {
  givehelp();
  exit(EXIT_SUCCESS);
  }

/* Deal with verifying and debugging */

if (results[arg_v].number != 0) verify = TRUE;

if (results[arg_debug].number != 0)
  {
  debug_file = stderr;
  debugging = TRUE;
  debug_printf("PMW run started\n");
  }

if (results[arg_dsb].text != NULL)
  {
  if (strspn(results[arg_dsb].text, "0123456789,") !=
      strlen(results[arg_dsb].text)) error_moan(77);   /* Hard */ 
  switch (sscanf(results[arg_dsb].text, "%d,%d,%d", &dsb_movement, 
          &dsb_stave, &dsb_bar))
    {
    case 1:                           /* One value is a bar number */
    dsb_bar = dsb_movement;
    dsb_movement = dsb_stave = 1;
    break;  
    
    case 2:                           /* Two values are stave, bar */
    dsb_bar = dsb_stave;
    dsb_stave = dsb_movement;
    dsb_movement = 1;
    break;
    
    case 3:                           /* Three values are movt, stave, bar */
    break;       
     
    default:
    error_moan(77);   /* Hard */        
    break;
    }  
  debug_file = stderr;
  }
  
if (results[arg_dtp].presence != arg_present_not)
  {
  debug_file = stderr;
  main_tracepos = results[arg_dtp].number; 
  } 

/* Deal with -from and -o */

if (results[arg_aa_input].text != NULL)
  arg_from_name = US results[arg_aa_input].text;

if (results[arg_o].text != NULL)
  arg_to_name = US results[arg_o].text;

/* Deal with overriding music fonts, fontmetrics, and psheader, MIDIperc, and
MIDIvoices files */

if (results[arg_F].text != NULL)
  font_metrics_extra = US results[arg_F].text;

if (results[arg_H].text != NULL)
  ps_header = US results[arg_H].text;
  
if (results[arg_MF].text != NULL)
  ps_fontdir_extra = US results[arg_MF].text; 

if (results[arg_MP].text != NULL)
  midi_perc = US results[arg_MP].text;

if (results[arg_MV].text != NULL)
  midi_voices = US results[arg_MV].text;

/* Deal with MIDI output */

if (results[arg_midi].text != NULL)
  midi_filename = US results[arg_midi].text;

if (results[arg_midibars].text != NULL)
  {
  uschar *endptr;
  play_startbar = Ustrtoul(results[arg_midibars].text, &endptr, 10);
  if (*endptr == 0) play_endbar = play_startbar; else
    {
    if (*endptr++ != '-') error_moan(123);     /* Hard error */
    play_endbar = Ustrtoul(endptr, &endptr, 10);
    if (*endptr != 0) error_moan(123);         /* Hard error */
    }
  }

if (results[arg_midimovement].presence != arg_present_not)
  play_movt_number = results[arg_midimovement].number;

/* Some BOOL options */

if (results[arg_norepeats].number != 0) play_repeats = FALSE;
if (results[arg_nowidechars].number != 0) stave_use_widechars = FALSE;

/* Deal with stave selection */

if (results[arg_s].text != NULL)
  {
  uschar *endptr;
  init_strtomap(US results[arg_s].text, &endptr, main_staves);
  mac_setstave(main_staves, 0);
  if (*endptr != 0) error_moan(74);  /* Hard error */
  }

/* Deal with page selection */

if (results[arg_p].text != NULL)
  {
  uschar *s = US results[arg_p].text;
  stave_list *p;
  stave_list **pp = &output_pagelist;

  while (*s)
    {
    int first, last, n;
    int count = sscanf(CS s, "%d%n-%d%n", &first, &n, &last, &n);
    if (count == 0) error_moan(75); else   /* Hard error */
      {
      p = malloc(sizeof(stave_list));
      p->first = first;
      if (count == 1) p->last = first; else
        {
        if (first > last) error_moan(76);  /* Hard error */
          else p->last = last;
        }
      p->next = NULL;
      *pp = p;
      pp = &(p->next);
      }
    s += n;
    if (*s == ',') s++;
    }
  }

/* Deal with transposition */

if (results[arg_t].presence != arg_present_not)
  main_transpose = results[arg_t].number;

/* Deal with format */

if (results[arg_f].text != NULL)
  {
  int i;
  uschar *f = US results[arg_f].text;
  main_format = malloc(sizeof(f) + 1);
  for (i = 0; i <= Ustrlen(f); i++) main_format[i] = tolower(f[i]);
  }

/* Deal with copies */

if (results[arg_c].presence != arg_present_not)
  output_copies = results[arg_c].number;

/* Deal with a number of printing configuration options */

if (results[arg_reverse].number != 0) print_reverse = TRUE;
if (results[arg_a4sideways].number != 0) print_pagefeed = pc_a4sideways;
if (results[arg_a4ona3].number != 0) print_imposition = pc_a4ona3;
if (results[arg_a5ona4].number != 0) print_imposition = pc_a5ona4;

if (results[arg_includefont].number != 0) output_includefont = TRUE;
if (results[arg_manualfeed].number != 0) output_manualfeed = TRUE;
if (results[arg_duplex].number != 0) output_duplex = TRUE;
if (results[arg_tumble].number != 0) output_tumble = TRUE;

if (results[arg_pamphlet].number != 0) print_pamphlet = TRUE;
if (results[arg_eps].number != 0) print_imposition = pc_EPS;

if (results[arg_printadjustx].text != NULL)
  {
  float d;
  sscanf(results[arg_printadjustx].text, "%g", &d);
  print_image_xadjust = (int)(1000.0 * d);
  }

if (results[arg_printadjusty].text != NULL)
  {
  float d;
  sscanf(results[arg_printadjusty].text, "%g", &d);
  print_image_yadjust = (int)(1000.0 * d);
  }

if (results[arg_printgutter].text != NULL)
  {
  float d;
  sscanf(results[arg_printgutter].text, "%g", &d);
  print_gutter = (int)(1000.0 * d);
  }

if (results[arg_printscale].text != NULL)
  {
  float d;
  sscanf(results[arg_printscale].text, "%g", &d);
  print_magnification = (int)(1000.0 * d);
  if (print_magnification == 0) error_moan(11);  /* Hard */
  }

if (results[arg_printside].presence != arg_present_not)
  {
  int n = results[arg_printside].number;
  if (n == 1) print_side2 = FALSE;
    else if (n == 2) print_side1 = FALSE;
      else error_moan(66);  /* Hard */
  }
}



/*************************************************
*                Entry Point                     *
*************************************************/

int 
main(int argc, char **argv)
{
int newargc;
char **newargv;
uschar to_buffer[256];
verify = FALSE;
ps_file = stdout;

font_metrics_default = US FONTMETRICS;
font_metrics_extra = NULL;
ps_fontdir_default = US FONTDIR;
ps_fontdir_extra = NULL;
ps_header = US PSHEADER;
midi_voices = US MIDIVOICES;
midi_perc = US MIDIPERC;

version_init();

font_List = malloc(max_fonts * sizeof(fontstr));
font_table = malloc(font_tablen * sizeof(int));

beam_stemadjusts = malloc(max_beamsize);
movement = malloc((main_max_movements+1) * sizeof(movtstr *));

/* Decode the (possibly modified) command line. */

newargc = init_command(argc, argv, &newargv, arg_pattern);
decode_command(newargc, newargv);
if (verify)
  {
  fprintf(stderr, "PMW version %s\n", version_string);
  main_shownlogo = TRUE;
  }

/* Set up default fonts */

font_init();

/* Initialize MIDI data */

read_midi_translation(&midi_voicenames, midi_voices);
read_midi_translation(&midi_percnames, midi_perc);

/* Initialization done */

main_initialized = TRUE;

/* Sort out the input file, and possibly the name of the output file. If no
input file is supplied, read stdin. Output is to stdout (default) unless
overridden by -o (arg_to_name will be set). */

if (arg_from_name == NULL)
  {
  input_file = stdin;
  }
else
  {
  main_filename = arg_from_name;
  input_file = Ufopen(main_filename, "r");
  if (input_file == NULL)
    {
    error_moan(4, main_filename, strerror(errno));
    return rc_disaster;
    }
  if (arg_to_name == NULL)
    {
    uschar *p;
    arg_to_name = to_buffer;
    Ustrcpy(arg_to_name, arg_from_name);
    p = arg_to_name + Ustrlen(arg_to_name);
    while (p > arg_to_name && p[-1] != '.' && p[-1] != '/') p--;
    if (p > arg_to_name && p[-1] == '.') p[-1] = 0;
    Ustrcat(arg_to_name, ".ps");
    }
  }

/* Read the file */

if (verify) fprintf(stderr, "Reading ...\n");
read_start();
read_go();
if (main_rc > rc_warning) return main_rc;

/* Output debugging if requested */

if (dsb_movement > 0) debug_showbar(dsb_movement, dsb_stave, dsb_bar);

/* Do the typesetting */

if (verify) fprintf(stderr, "Paginating ...\n");
paginate_go();
if (main_rc > rc_warning) return main_rc;

/* Show pagination information if verifying */

if (verify) display_info();

/* If a file name other than "-" is set for the output, open it. Otherwise
we'll be writing to stdout. */

if (arg_to_name != NULL && Ustrcmp(arg_to_name, "-") != 0)
  {
  if (verify)
    fprintf(stderr, "\nWriting PostScript file \"%s\" ...\n", arg_to_name);
  ps_file = Ufopen(arg_to_name, "w");
  if (ps_file == NULL)
    {
    error_moan(4, arg_to_name, strerror(errno));
    return main_rc;
    }
  }
else if (verify) fprintf(stderr, "\nWriting Postscript to stdout ...\n");

/* Set up for printing, and go for it */

print_lastpage = main_lastpage;
if (print_pamphlet) print_lastpage = (print_lastpage + 3) & (-4);

/* This diagram shows the computed values and the positions where the origin
can go in each case. In practice we take the upper value where there are two
possibilities.

 ------------ Sideways -------------   |   ------------ Upright -----------
 ----- 1-up -----   ----- 2-up -----   |   ---- 1-up ----    ---- 2-up ----
  Port     Land      Port     Land     |    Port     Land     Port     Land
 x------  -------   -------  ---x---   |   -----    x----    x----    -----
 |  0  |  |  4  |   |  2  |  |  6  |   |   |   |    |   |    |   |    |   |
 ------x  x------   x------  ---x---   |   | 1 |    | 5 |    | 3 |    x 7 |
                                       |   |   |    |   |    |   |    |   |
                                       |   x----    -----    ----x    -----
*/

print_pageorigin =
  ((print_pagefeed == pc_a4sideways)? 0 : 1) +
  ((print_imposition == -1)? 0 : 2) +
  (opt_landscape? 4 : 0);

ps_go();
if (ps_file != stdout) fclose(ps_file);

/* Output warning if coupled staves were not spaced by a multiple of 4 */

if (error_111) error_moan(111);

/* Write MIDI output if required */

if (midi_filename != NULL) 
  {
  if (verify) fprintf(stderr, "Writing MIDI file \"%s\" ...\n", midi_filename);
  midi_write();
  } 

if (verify) fprintf(stderr, "PMW done\n");
return main_rc;
}

/* End of main.c */
