/*
   File: rtsio.c
   Contains the runtime system I/O routines.

   Copyright 2005 Radboud University of Nijmegen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Library General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   CVS ID: "$Id: rtsio.c,v 1.51 2006/09/27 16:01:15 marcs Exp $"
*/

/* standard includes */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

#ifndef WIN32
#include <termios.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif /* !WIN32 */

/* Check where to read the readline.h */
#ifdef HAVE_LIBREADLINE
#ifdef READLINE_LIBRARY
#  include "readline.h"
#  include "history.h"
#else
#  include <readline/readline.h>
#  include <readline/history.h>
#endif /* READLINE_LIBRARY */
#endif /* HAVE_LIBREADLINE */

/* libabase includes */
#include <abase_error.h>
#include <abase_memalloc.h>
#include <abase_dstring.h>

/* local includes */
#include "rtsio.h"
#include "rtsopt.h"
#include "rtscode.h"

/*
   Private stuff
*/
static FILE*	input_file = NULL;
static FILE*	profile_file = NULL;
static FILE*	output_file = NULL;
static char*	input_text = NULL;
static int	interactive = 0;
static int	input_begin_linenumber, input_linenumber;
static int	input_begin_position, input_position;
static int	seen_document_separator;

#ifdef PROFILING
static FILE*	profFile;		/* profile counters */
#endif

#ifdef DEBUG_RTS
#define DB_RTS(x) x
#else
#define DB_RTS(x)
#endif

/*------------------------------------------------------------------------------
// Priority list routines:
// Store a list of pass 2 results in increasing order of penalty (and keep only 
// the ones belonging to the longest parsed input)
//----------------------------------------------------------------------------*/
typedef struct _PrioList
{   Penalty penalty;
    int length;
    StateIndicator state_node;
    dstring data;
    struct _PrioList* next;
} PrioList;

static int priolist_nr_parses;
static Penalty priolist_max_penalty;
static PrioList* ParseResults;
static PrioList* CurrentParse;

/*
   Memory management routines
*/
static PrioList *priolist_new ()
{ PrioList* pl = abs_malloc (sizeof(PrioList), "priolist_new");
  pl -> next = NULL;
  pl -> data = abs_init_dstring (1024);
  return (pl);
}

static void priolist_destroy (PrioList *l)
{ if (l != NULL)
    { priolist_destroy (l -> next);
      abs_free_dstring (l -> data);
      abs_free (l, "priolist_destroy");
    };
}

/*------------------------------------------------------------------
// Check if it makes sense to start a second pass with this penalty
// and length. A longer length is always acceptable; a lower length
// never. A higher penalty only if the number of parses is not
// yet max_parses. For the -B option a parse is only acceptable if
// it has lower or equal penalty as the foremost entry on the list
//----------------------------------------------------------------*/
int current_parse_is_acceptable (Penalty penalty, int length)
{ if (ParseResults == NULL) return (1);
  if (length < ParseResults -> length) return (0);
  if (length > ParseResults -> length) return (1);

  /* Check for best parsings */
  if (best_parsings)
     { if (penalty < ParseResults -> penalty) return (1);
       if (penalty > ParseResults -> penalty) return (0);
       return (priolist_nr_parses < max_parses);
     };

  /*
     We have a parse of equal length. We will add it if the
     number of parses found is less than max_parses or if
     its penalty is less than the maximum penalty on the
     list.
  */
  if (priolist_nr_parses < max_parses) return (1);
  return (penalty < priolist_max_penalty);
}

/*------------------------------------------------------------------
// Add the parse just initiated to the Parse Results priority list
// When we enter this function we are sure that we can insert the
// parse.
//----------------------------------------------------------------*/
static void priolist_add (PrioList *el)
{ PrioList *curr, *prev;
  if ((ParseResults == NULL) || (ParseResults -> length < el -> length))
    { /*
	 If the CurrentParse covers more input than the those on the
	 ParseResults list, replace the entire ParseResults list
      */
      priolist_destroy (ParseResults);
      priolist_max_penalty = el -> penalty;
      priolist_nr_parses = 1;
      ParseResults = el;
      return;
    };

  /* The new parse must have a length equal to the already found ones */
  assert (ParseResults -> length == el -> length);

  /*
     Check for best parsing
     Delete all parses with a higher penalty
  */
  if (best_parsings)
    { if (el -> penalty < ParseResults -> penalty)
         { priolist_destroy (ParseResults);
	   priolist_max_penalty = el -> penalty;
	   priolist_nr_parses = 1;
	   ParseResults = el;
	   return;
	 };
    };

  /*
     We have to insert the new parse
  */
  curr = ParseResults;
  prev = NULL;
  while ((curr != NULL) && (curr -> penalty <= el -> penalty))
     { prev = curr;
       curr = curr -> next;
     };

  /* We have to insert anyway */
  el -> next = curr;
  if (prev == NULL) ParseResults = el;
  else prev -> next = el;
  prev = el;
  priolist_nr_parses++;
  if (priolist_nr_parses <= max_parses) return;

  /* But we are sure it can only be one larger than max_parses */
  assert (priolist_nr_parses == max_parses + 1);

  /* Destroy the last and update the maximum */
  while (curr != NULL)
     { prev = curr;
       curr = curr -> next;
     };

  prev -> next = NULL;
  priolist_max_penalty = prev -> penalty;
  priolist_destroy (curr);
  priolist_nr_parses--;
}


/* Note that pos/negmemo parsing no longer call parse_results_start_new */
void parse_results_start_new ()
{ CurrentParse = priolist_new ();
  DB_RTS (abs_message ("New parse started...\n");)
}

/*----------------------------------------------------------------------------
// current_parse_set_prio_and_add is called whenever a second pass (re)starts
//--------------------------------------------------------------------------*/
void current_parse_set_prio_and_add (Penalty penalty, StateIndicator pos, int length)
{ CurrentParse = priolist_new ();
  CurrentParse -> penalty = penalty;
  CurrentParse -> length = length;
  CurrentParse -> state_node = pos;
  priolist_add (CurrentParse);

  DB_RTS (abs_printf ("Parse with penalty %d and length %d added,", penalty, length));
  DB_RTS (abs_message ("lowest penalty = %d", ParseResults -> penalty));
  DB_RTS (abs_message ("priolist_max_penalty = %ld", priolist_max_penalty));
}

void parse_results_init ()
{ priolist_max_penalty = MAX_PENALTY;
  priolist_nr_parses = 0;
  ParseResults = NULL;
  CurrentParse = NULL;
}

unsigned parse_results_get_nr_parses ()
{ return (priolist_nr_parses);
}

void parse_results_destroy ()
{
  /* XXX memory leak danger
   * Check if CurrentParse points into ParseResults, and if not,
   * destroy it separately...
   * (After some other leak improvements, it seems now that it is always
   * the case that CurrentParse does not point in ParseResults. Maybe one
   * day when that is guaranteed we can simplify this.)
   */
  if (CurrentParse) {
    PrioList *curr;
    for (curr = ParseResults; curr; curr = curr->next) {
      if (curr == CurrentParse) {
	CurrentParse = NULL;
	DB_RTS(printf("CurrentParse points in ParseResults\n");)
	break;
      }
    }
    if (CurrentParse) {
      DB_RTS(printf("CurrentParse does not point in ParseResults\n");)
      priolist_destroy(CurrentParse);
      CurrentParse = NULL;
    }
  }
  priolist_destroy (ParseResults);
  ParseResults = NULL;
}

void parse_results_dump ()
{ PrioList *l = ParseResults;
  int nr = 0;

  DB_RTS(printf("Begin dump parse results:\n");)
  while ((l != NULL) && (nr < max_parses))
    { /* printf("%s", l->data->str); */
      printf ("%s", l -> data -> s);
      l = l->next;
      nr++;
    }

  DB_RTS (printf ("End dump parse results:\n"));
  fflush (stdout);
}

StateIndicator get_statenode_from_first_parse()
{ return ((ParseResults)? ParseResults -> state_node : NULL);
}

unsigned get_length_from_first_parse()
{ return ((ParseResults) ? ParseResults -> length : 0);
}

void current_parse_add_int (int i)
{ if (no_output) return;
  abs_sprintfa_dstring (CurrentParse -> data, "%d", i);
}

void current_parse_add_char (char c)
{ if (no_output) return;
  abs_append_dstring_c (CurrentParse -> data, c);
}

void current_parse_add_string (char* s)
{ if (no_output) return;
  abs_append_dstring (CurrentParse -> data, s);
}

void current_parse_add_space (int nr)
{ int ix;
  if (no_output) return;
  for (ix = 0; ix < nr; ix++)
    abs_append_dstring_c (CurrentParse -> data, ' ');
}

void current_parse_printf (char* format, ...)
{ char buf[MAX_FMT_LEN];
  va_list arg_ptr;

  if (no_output) return;
  va_start (arg_ptr, format);
  vsprintf (buf, format, arg_ptr);
  va_end (arg_ptr);
  abs_append_dstring (CurrentParse -> data, buf);
}

/*------------------------------------------
// File IO
//----------------------------------------*/
static void open_input_file()
{ char* fname = get_input_file_name();
  if (fname == NULL) input_file = stdin;
  else
    { input_file = fopen (fname, "r");
      if (input_file == NULL)
        abs_fatal ("cannot open input file '%s'", fname);
    };
}

/* MS: geeft get_parser_name genoeg ruimte terug voor de strcat */
static void open_profile_file ()
{  char* fname = (char *) get_parser_name ();
   if (profile_option) {
     strcat(fname, ".prd");
     profile_file = fopen (fname, "w");
     if (profile_file == NULL)
        abs_fatal ("cannot open file '%s' to store profile results", fname);
   }
}

/* MS: To be changed with a vsnprintf */
void profile_printf (char* message, ...)
{ va_list argp;
  va_start(argp, message);

  /* Hope this is still compatible with PA-RISC (PARIX define) */
  if (profile_option)
    { vfprintf(profile_file, message, argp);
      /* fflush(stderr); */
    }
  va_end(argp);
}

static void close_profile_file()
{ if (profile_option)
    fclose (profile_file);
}

static void close_input_file ()
{ fclose (input_file);
}

static void open_output_file ()
{ char* fname = get_output_file_name ();
  if (fname == NULL)
    output_file = stdout;
  else
    { output_file = fopen(fname, "w");
      if (output_file == NULL)
        abs_fatal ("cannot open output file '%s'\n", fname);
    };
}

static void close_output_file()
{ fclose (output_file);
}

/*-------------------------------------------------
// Prelude and Postlude
//-----------------------------------------------*/
void init_io ()
{ open_input_file ();
  open_profile_file ();
  open_output_file ();
#if HAVE_LIBREADLINE
  interactive = isatty (fileno (input_file)) && isatty (fileno (output_file)); 
  input_text = NULL;
  rl_instream = input_file;
#else
  interactive = isatty (fileno (input_file)) && isatty (fileno (stderr)); 
  input_text = (char*) abs_malloc (MAXINPUT, "InitIO");
  input_text[0] = '\0';
#endif
}

void end_io ()
{ /*
   * In principle this should
   * - use free() when HAVE_LIBREADLINE and read_input_text() has been used
   * - use abs_free() when read_input_block() has been used.
   */
  abs_free (input_text, "rtsio");
  input_text = NULL;
  close_input_file ();
  close_profile_file ();
  close_output_file ();
}

/*-------------------------------------------------------
// Input
//-----------------------------------------------------*/
char* get_input_text()
{ return (input_text);
}

int get_input_linenumber()
{ return (input_begin_linenumber);
}

int get_input_position()
{ return (input_begin_position);
}

int read_input_line()
{ static int eof = 0;
  unsigned input_len;

  if (eof) return (0);

  input_begin_linenumber = input_linenumber;
  input_begin_position = input_position;

  /* Input from terminal? Prompt user */
  /* Get input line */
#ifdef HAVE_LIBREADLINE
  if (input_text)
    free(input_text);	/* for text allocated with readline() */
  if(interactive) {
    input_text = readline (">> ");
  }
  else {
    input_text = readline ("");
  }
  if (!input_text) 		/* test for EOF */
#else
  if (interactive)
     { fprintf (stderr, ">> ");
       fflush (stderr);
     };
 
  if (fgets (input_text, MAXINPUT, input_file) == NULL)
#endif
    { eof = 1;
      return (0);
    };

  /* increment line number if we read more than nothing */
  input_linenumber++;
  input_position = 0;
 
#ifdef HAVE_LIBREADLINE
  /* If there is anything on the line, remember it */
  if (*input_text) {
    add_history (input_text);
  }
#endif

  /* Chop newline */
  input_len = strlen(input_text);
  if (input_len > 0 && input_text[input_len - 1] == '\n')
    input_text[--input_len] = '\0';

  /* Interactive users exit on pressing just enter */
  if (interactive && (input_len == 0)) return 0;
  return (1);
}

int read_input_block ()
{   dstring tmp = abs_init_dstring (256);
    unsigned int input_len;
    int inchar;
    int seen_newline = 0;
    int seen_end_paragraph = 0;

    /* Remember having seen the end of file */
    static int eof = 0;

    /* On end of file detected, we return */
    if (eof) return (0);

    input_begin_linenumber = input_linenumber;
    input_begin_position = input_position;

    /* Input from terminal? Prompt user */
    if (interactive)
       { fprintf (stderr, ">> ");
         fflush (stderr);
       };

    /* Get input block */
    while (!seen_end_paragraph && !feof (input_file))
      { inchar = fgetc (input_file);
        if (inchar == EOF) break;
	input_position++;

        if ((char) inchar == '\n')
          { /* check for end of line */
	    input_linenumber++;
            input_position = 0;
            if (seen_newline) seen_end_paragraph = 1;
            else
	      { seen_newline = 1;
	        abs_append_dstring_c (tmp, '\n');
              }
          }
        else if (io_sync_option && ((char) inchar == '\001'))
	  { /* check of end of document from lcs */
            seen_end_paragraph = 1;
	    seen_document_separator = 1;
          }
        else if (strchr (invisible_chars, inchar))
          /*
	     White space at the beginning of a new line may still end the
	     paragraph if trailed by a \n
	  */
	  abs_append_dstring_c (tmp, (char) inchar);
        else
	  { seen_newline = 0;
	    abs_append_dstring_c (tmp, (char) inchar);
          };
      };

    /* Convert the dynamic string into a usual C string */
    if (input_text) abs_free (input_text, "read_input_block");
    input_text = abs_finish_dstring (tmp);
    DB_RTS(abs_message("input_text = \"%s\"", input_text);)

    /* Mark if we reached the end of file */
    if (feof (input_file))
       eof = 1;

    /* Chop newline at end */
    input_len = strlen (input_text);
    if (input_len > 0 && input_text[input_len - 1] == '\n')
      input_text[--input_len] = '\0';

    /* Done */
    return (1);
}

/*
   Optionally output an LCS end of document marker and flush the output
*/
void maybe_output_sync(int force)
{ if (seen_document_separator || (force && io_sync_option))
    { fprintf (stdout, "\001\n");
      fflush (stdout);
      seen_document_separator = 0;
    };
}

#ifdef GEN_RTS
typedef struct _GenOutputEl {
    char* token;
    struct _GenOutputEl* next;
} GenOutputEl;

static GenOutputEl *gen_output_push (GenOutputEl *l, char* t)
{   GenOutputEl* n = abs_malloc (sizeof(GenOutputEl), "gen_output_push");
    n -> next = l;
    n -> token = t;
    return (n);
}

static GenOutputEl *gen_output_pop (GenOutputEl *l)
{   GenOutputEl* res;
    assert(l);
    res = l -> next;
    abs_free (l, "gen_output_pop");
    return (res);
}

static void gen_output_printf (GenOutputEl* l)
{   if (!l) return;
    gen_output_printf (l -> next);
    printf (" %s", l -> token);
}

static GenOutputEl* generated_output;
static int gen_line_nr;

void gen_output_init (unsigned line_nr)
{   generated_output = NULL;
    gen_line_nr = line_nr;
}

void gen_output_add_token (char* txt)
{
    switch (*txt) {
        case '\1':
        case '\2':
        case '\3':
        case '\4':
            txt++;
            break;
        default:
    }

    generated_output = gen_output_push (generated_output, txt);
}

void gen_output_remove_token()
{
    generated_output = gen_output_pop (generated_output);
}

void gen_output_show ()
{
    printf ("%3d:", gen_line_nr);
    gen_output_printf (generated_output);
    printf ("\n");
}

void gen_output_free ()
{   while (generated_output)
       generated_output = gen_output_pop (generated_output);
}

#endif /* GEN_RTS */
