/*
   File: rts.c
   Agfl runtime system main 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: rts.c,v 1.59 2006/09/27 16:01:14 marcs Exp $"
*/

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

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>

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

/* liblexicon includes */
#include <lexicon.h>

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

/*
** Private proto's
*/

#ifdef PROFILING
static void ShowProfile();
static void UpdateProfile();
#endif

/*---------------------------------------------------
// install_signal_handlers():
// Give bug message in the event of crashing system
//-------------------------------------------------*/
void CatchSignal (int sig)
{ char *fName = "CatchSignal";

  switch(sig)
    { case SIGSEGV:	abs_bug (fName, "Segmentation Fault");
#ifndef WIN32
      case SIGBUS:	abs_bug (fName, "Bus Error");
#endif /* WIN32 */
      default: 	abs_bug (fName, "Killed by signal %d", sig);
    };
}

static void install_signal_handlers (void)
{
#ifdef DIST
    char *fName = "install_signal_handlers";

    if (signal (SIGSEGV, CatchSignal) == SIG_ERR)
      abs_abort (fName, "signal failed");

#ifndef WIN32
    if (signal (SIGBUS, CatchSignal) == SIG_ERR)
      abs_abort (fName, "signal failed");
#endif /* WIN32 */
#endif /* DIST */
}

static FILE *try_open_lexicon_file (void)
{ char lex_fname[MAXPATHLEN + 1];
  FILE *lex_file;

  sprintf (lex_fname, "%s.blf", get_parser_name ());
  lex_file = fopen (lex_fname, "rb");			/* Note WIN32 */
  if (lex_file == NULL)
    abs_fatal ("cannot open binary lexicon file '%s'", lex_fname);

  return (lex_file);
}

static void parse_alphabet_file (char *fname, char **trans_src, char **trans_dst)
{ char src_chars[256];
  char dst_chars[256];
  char buffer[256];
  int lineno = 0;
  int index = 0;
  int ix;

  /* Open the alphabet file */
  FILE *f = fopen (fname, "r");
  if (f == NULL)
     abs_fatal ("cannot open alphabet file '%s'", fname);

  /* Parse the file line for line */
  while (fgets (buffer, 255, f) != NULL)
     { lineno++;
       if (buffer[0] != '#')
	  { char *bptr = buffer;
	    char *eptr;
	    int val;

	    /* Skip white space and convert the first value on the line */    
	    while (isspace ((int) (*bptr))) bptr++;
	    val = strtol (bptr, &eptr, 0);
	    if (bptr == eptr)
	       abs_fatal ("illegal first value on line %d in alphabet file", lineno);
	    if ((val < 0) || (val > 255))
	       abs_fatal ("first value out of range on line %d in alphabet file", lineno);
	    src_chars[index] = (char) val;

	    /* Skip white space and convert the second value on the line */    
	    bptr = eptr;
	    while (isspace ((int) (*bptr))) bptr++;	/* Skip white space */
	    val = strtol (bptr, &eptr, 0);
	    if (bptr == eptr)
	       abs_fatal ("illegal second value on line %d in alphabet file", lineno);
	    if ((val < 0) || (val > 255))
	       abs_fatal ("second value out of range on line %d in alphabet file", lineno);
	    dst_chars[index] = (char) val;
	    index++;
          };
     };
  fclose (f);

  /* complete the translation buffers */
  src_chars[index] = '\0';
  dst_chars[index] = '\0';

  /* Check if the user specified a mapping */
  for (ix = 0; src_chars[ix] != 0; ix++)
     { char v2 = src_chars[ix];
       int iy;
       for (iy = ix + 1; src_chars[iy] != 0; iy++)
	  if (src_chars[iy] == v2)
	     abs_fatal ("Multiple mapping in alphabet file for value %d", v2);
     };

  /* Copy the strings and be done */
  *trans_src = abs_new_string (src_chars, "parse_alphabet_file");
  *trans_dst = abs_new_string (dst_chars, "parse_alphabet_file");
}

static void log_unknown (unsigned pos, char* word_form, unsigned len)
{ if (lexer_stats_option)
    { char buf[MAX_FMT_LEN];
      strncpy(buf, word_form, len);
      buf[len] = '\0';
      abs_warning ("skipped unknown token \"%s\" at pos %d", buf, pos);
    }
}

static void init_lex_info (LexInfo* info, Trie trie, Lexicon lexicon)
{   char *tsrc, *tdst,*fname;
    info -> trie = trie;
    info -> lexicon = lexicon;
    info -> terminals = term_names;
    info -> matches = match_regexp_names;
    info -> skips = skip_regexp_names;
    info -> nr_terminals = nr_terminals;
    info -> nr_skips = nr_skip_regexps;
    info -> nr_matches = nr_match_regexps;
    info -> nr_neg_memos = nr_memos;
    info -> nr_syntax_nonterminals = nr_syntax_nonterminals;
    info -> eos_terminal = eos_terminal;

    info -> blanks = " \t\n\r";				/* this is constant */

    info -> terminators = word_terminator_chars;	/* ",.\"';:!? \t\n" */
    info -> invisibles = invisible_chars;		/* " \t\n\"'" */

    /* Check for translations */
    fname = (char *) get_alphabet_file_name ();
    if (fname != NULL)					/* -A alphabet_file */
       { parse_alphabet_file (fname, &tsrc, &tdst);
         info -> translate_src = tsrc;
	 info -> translate_dst = tdst;
       }
    else if (alphabet_file == NULL)			/* |translate off| */
       { info -> translate_src = "";
         info -> translate_dst = "";
       }
    else if (*alphabet_file == '\0')			/* |translate default| */
       { info -> translate_src = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
         info -> translate_dst = "abcdefghijklmnopqrstuvwxyz";
       }
    else						/* |translate "alphabet_file"| */
       { parse_alphabet_file (alphabet_file, &tsrc, &tdst);
         info -> translate_src = tsrc;
	 info -> translate_dst = tdst;
       };

    /* log_unknown gekopieerd naar uit testlex.c */
    info -> log_unknown = log_unknown;
}

static void prelude (LexInfo *lex_info)
{ Trie lex_trie = NULL;
  Lexicon the_lexicon = NULL;

  /* Make sure we abort with some error message */
  abs_mm_init ();
  install_signal_handlers ();

  if (use_lexicon)
    { the_lexicon = lexicon_new (try_open_lexicon_file ());
      lex_trie = lxcn_get_lexicon_trie (the_lexicon);
    };

  init_io ();
  init_lex_info (lex_info, lex_trie, the_lexicon);
  init_lexer (lex_info);
  init_parser ();
}

static void postlude (int nop, Lexicon the_lexicon)
{
#ifdef PROFILING
    if ((prof_count > 0) && !nop)
      { if (verbose && !reset_profile)
          ShowProfile ("corpus");

        if (update_profile)
          UpdateProfile ();
      };
#endif

    end_parser ();
    end_lexer ();
    end_io ();

    if (use_lexicon)
      lexicon_free (the_lexicon);
}

/*-----------------------------------------
// Main
//---------------------------------------*/
int main (int argc, char* argv[])
{   LexInfo lex_info; /* should exist all through the process */
    Lexicon the_lexicon;

    int nop = 1;
    int read_by_paragraph = segment_mode;
    StateIndicator node = NULL;

    /* Handle the options of the command_line */
    process_options (argc, argv);
    if (show_options_option) show_options ();

    prelude (&lex_info);
    the_lexicon = lex_info.lexicon;

    /* While we have input, lexicalize and parse it. */
    while (read_by_paragraph ? read_input_block (): read_input_line ())
      { Trellis* trellis;
        char* input_text = get_input_text ();
	int input_linenr = get_input_linenumber ();
	int input_colnr = get_input_position ();

        /*------------------
        // Lexicalize input
        //----------------*/
        reset_timers();

	/* skip empty lines silently */
	if (!*input_text || !strcmp (input_text, "\n")) continue;

        start_scan_time();
        trellis = make_trellis_by_word_lexing (input_text, input_linenr, input_colnr, the_lexicon);
        stop_scan_time();

        /* If we have non-empty input, parse it */
        if (!is_empty_trellis (trellis)) {
#ifndef STANDALONE_LEXER
            if (graph_option)
              print_trellis (trellis);
#ifdef COUNTERS
            show_neg_memo_blocks (trellis);
#endif
            if (max_parses > 0) {
	       int first_parse = 1;
               nop = 0;
               do {
#ifdef PMRTS
		    if (!first_parse) reset_trellis_pos_memos (trellis);
#endif
                    node = parse_trellis (trellis, node);
#ifdef COUNTERS
                    show_neg_memo_blocks (trellis);
#endif
		    first_parse = 0;
               } while (node);
            }
#else /* STANDALONE_LEXER */
            print_trellis (trellis);
#endif /* STANDALONE_LEXER */
        }

        /* Clean-up trellis */
        delete_trellis (trellis);
        /* and synchronise with other programs */
        maybe_output_sync (0);
	abs_mm_free ("main");
      };

    /* finish up and leave */
    maybe_output_sync (1);
    postlude (nop, the_lexicon);
    return (0);
}

/*------------------------------------------------------------------------------
// Profiling
//
// Profiling code currently not generated. See version 1.7 for profiling
//----------------------------------------------------------------------------*/
#ifdef PROFILING
static void ShowProfile (char *msg)
{ long i;
  char buf[256];
  int len;

  int **prof_exec_cnt = get_profile_exec ();
  int **prof_succ_cnt = get_profile_succ ();
  int **prof_parse_cnt = get_profile_parse ();

  sprintf(buf, "Profile (%s)                              ", msg);
  sprintf(buf + 40 ,"|      Exec |      Succ |     Parse\n"
          "----------------------------------------"
                    "+-----------+-----------+-----------");
  abs_message (buf);

  for (i = 0; i < prof_count; i++)
    { sprintf (buf, "%s", prof_table[i].id);
      len = strlen (buf);
      if (len > 29) len = 29;
      sprintf (buf + len, "/%-2d", prof_table[i].arity);
      abs_message ("%-32s alt %2d | %9d | %9d | %9d", buf, prof_table[i].altno,
                   *(prof_exec_cnt[i]), *(prof_succ_cnt[i]), *(prof_parse_cnt[i]));
    };
}

#define MAXBUF 512
static char buf[MAXBUF];
static long line = 0;
static char *maxbuf = buf + MAXBUF;

static int ReadLine (char *buf, FILE *file, char *fname)
{ if (fgets (buf, MAXBUF, file) == NULL)
    { if (feof(file)) return (0);
      abs_error ("error reading from file '%s'\n", fname);
      abs_abort ("ReadLine", "update aborted");
    };
  line++;
  return (1);
}

static void WriteFile (char *buf, FILE *file, char *fname)
{ if (fputs (buf, file) == EOF)
    { fclose (file);
      remove (fname);
      abs_error ("Error writing to file '%s'\n", fname);
      abs_abort ("WriteFile", "update aborted\n");
    };
}

static void WriteProfile(FILE *dst, char *dst_name)
{ int **prof_exec_cnt = get_profile_exec ();
  int **prof_succ_cnt = get_profile_succ ();
  int **prof_parse_cnt = get_profile_parse ();
  int i;

  sprintf (buf, "#%d %s %s\n", prof_count, fingerprint, TimeToText (gra_version));
  WriteFile (buf, dst, dst_name);

  for (i = 0; i < prof_count; i++)
    { sprintf (buf, "%s/%d/%d %d %d %d\n",
	       prof_table[i].id, prof_table[i].arity, prof_table[i].altno,
               *(prof_exec_cnt[i]), *(prof_succ_cnt[i]), *(prof_parse_cnt[i]));
      WriteFile (buf, dst, dst_name);
    };
}

static void UpdateCounters(FILE *src, FILE *dst, char *src_name, char *dst_name)
{ int **prof_exec_cnt = get_profile_exec ();
  int **prof_succ_cnt = get_profile_succ ();
  int **prof_parse_cnt = get_profile_parse ();
  int i;

  for (i = 0; i < prof_count; i++)
    { char *p, *q, *r, *s;
      int exec, succ, parse;

      if (!ReadLine (buf, src, src_name))
	{ fclose (dst);
	  remove (dst_name);
	  abs_error ("Line %d: missing line\n", line + 1);
	  abs_abort ("UpdateCounters", "update aborted");
	};

      p = buf;
      while ((*p != '/') && (p < maxbuf)) p++;
      while ((*p != ' ') && (p < maxbuf)) p++;
      exec = (int) strtol (p, &q, 10);
      succ = (int) strtol (q, &r, 10);
      parse = (int) strtol (r, &s, 10);

      if ((p == q) || (q == r) || (r == s))
	{ fclose (dst);
	  remove (dst_name);
	  abs_error ("Line %d: invalid or missing counter\n", line);
	  abs_abort ("UpdateCounters", "update aborted");
	};

      if (!new_profile)
	{ *(prof_exec_cnt[i]) += exec;
	  *(prof_succ_cnt[i]) += succ;
	  *(prof_parse_cnt[i]) += parse;
	};
    };
}

static void CopyProfile(FILE *src, FILE *dst, char *src_name, char *dst_name)
{ int idlen = strlen(fingerprint);

  while (ReadLine(buf, src, src_name))
    { if (buf[0] == '#')
	{ char *id;

	  int cnt = (int) strtol(buf + 1, &id, 10);
	  if (id == buf + 1)
	    { fclose (dst);
	      remove (dst_name);
	      abs_error ("Line %ld: number expected\n", line);
	      abs_abort ("CopyProfile", "update aborted");
	    };

	  while (*id == ' ') id++;
	  if (strncmp(id, fingerprint, idlen) == 0)
	    { if (cnt != prof_count)
		{ fclose (dst);
		  remove (dst_name);
		  abs_error ("Line %ld: invalid id or number\n", line);
		  abs_abort ("CopyProfile", "update aborted");
		}
	      else UpdateCounters (src, dst, src_name, dst_name);
      	    }
	  else WriteFile (buf, dst, dst_name);
	}
      else WriteFile (buf, dst, dst_name);
    };
}

static int FileExists(char *fname)
{ struct stat sbuf;
  return (stat(fname, &sbuf) >= 0);
}

static char * TempFName(char *dir, char *prefix)
{ char *fname = tempnam (dir, prefix);
  if (fname == NULL)
    abs_abort ("TempFName", "cannot get name for temp file");
  return (fname);
}

static FILE *OpenFile (char *fname, char *access)
{ FILE *file = fopen (fname, access);
  if (file == NULL)
    { abs_error ("Cannot open file '%s'\n", fname);
      abs_abort ("OpenFile", "update aborted");
    };
  return (file);
}

static void CloseFile (FILE *file, char *fname)
{ if (fclose(file) != 0)
    { abs_error ("Cannot close file '%s'\n", fname);
      abs_abort ("CloseFile", "update aborted");
    };
}

#ifdef WIN32
#  define MV "REN"
#else
#  define MV "mv"
#endif

static void MoveFile(char *src, char *dst)
{ char cmd[512];

  sprintf (cmd, "%s %s %s", MV, src, dst);
  if (system (cmd) == -1)
    { abs_error ("Cannot move file '%s' to '%s'\n", src, dst);
      abs_abort ("MoveFile", "update aborted");
    };
}

static void UpdateProfile(void)
{ char profname[256];
  sprintf (profname, "%s.prof", get_parser_name ());

  abs_message ("Updating profile '%s'...", profname);
  if (FileExists(profname))
    { char *new_name = TempFName("./", "prof");
      FILE *old = OpenFile (profname, "r");
      FILE *new = OpenFile (new_name, "w");

      CopyProfile (old, new, profname, new_name);
      WriteProfile (new, new_name);
      CloseFile (new, new_name);
      fclose (old);
      MoveFile (new_name, profname);
      free (new_name);
    }
  else
    { FILE *profile = OpenFile (profname, "w");

      WriteProfile (profile, profname);
      CloseFile (profile, profname);
    };

  abs_message ("Done");
}

#endif /* PROFILING */
