/* Copyright (C) 2002 Bjoern Beutel. */

/* Description. =============================================================*/

/* This is a GTK program that displays Malaga values. */

/* Includes. ================================================================*/

#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <math.h>
#include <gtk/gtk.h>
#include <glib.h>
#include "basic.h"
#include "input.h"
#include "scanner.h"
#include "files.h"
#include "canvas.h"
#include "allomorphs.h"
#include "expressions.h"
#include "result.h"
#include "tree.h"
#include "variables.h"
#ifdef POSIX
#include <unistd.h>
#endif

/* Variables. ===============================================================*/

#ifdef WIN32
static bool_t input_consumed; /* Set by consumer, reset by pipe thread. */
static GCond *input_consumed_cond; /* Signals that "input_consumed" is set. */
static GMutex *input_consumed_mutex;
#endif

/* Functions. ===============================================================*/

static void 
parse_font_size( string_t input, int_t *font_size )
{
  int_t size;

  size = parse_cardinal( &input );
  parse_end( &input );
  if (size != 8 && size != 10 && size != 12 && size != 14 && size != 18
      && size != 24)
  {
    complain( "Illegal font size, valid sizes are 8, 10, 12, 14, 18, 24." );
  }
  *font_size = size;
}

/*---------------------------------------------------------------------------*/

static int_t 
my_parse_int( string_t *input_p )
/* Parse an integer number in *INPUT_P, adjust *INPUT_P.
 * Parsing stops when the first non-digit is encountered. */
{
  int_t number;

  if (**input_p < '0' || **input_p > '9') 
    complain( "Number expected." );
  number = 0;
  while (**input_p >= '0' && **input_p <= '9') 
    number = 10 * number + (*(*input_p)++ - '0');
  return number;
}

/*---------------------------------------------------------------------------*/

static void 
parse_geometry( string_t input, rectangle_t *geometry )
{ 
  static char_t malformed[] = "Malformed geometry.";
  string_t arg, arg_p;
  
  arg_p = arg = parse_word( &input );
  parse_end( &input );

  if ((*arg_p < '0' || *arg_p > '9') && *arg_p != '+') 
    complain( malformed );
  if (*arg_p >= '0' && *arg_p <= '9')
  { 
    /* Parse size. */
    geometry->width = my_parse_int( &arg_p );
    if (*arg_p != 'x') 
      complain( malformed );
    arg_p++;
    geometry->height = my_parse_int( &arg_p );
  }
  if (*arg_p == '+')
  { 
    /* Parse position. */
    arg_p++;
    geometry->x = my_parse_int( &arg_p );
    if (*arg_p != '+') 
      complain( malformed );
    arg_p++;
    geometry->y = my_parse_int( &arg_p );
  }
  if (*arg_p != EOS) 
    complain( malformed );
  free_mem( &arg );
}

/*---------------------------------------------------------------------------*/

static void 
read_profile( string_t file_name )
{
  FILE *stream;
  string_t line_p, argument, value;
  char_t *line;
  volatile int_t line_count;

  /* Initialise positions to be ignored. */
  allomorphs_geometry.x = allomorphs_geometry.y = -1;
  expressions_geometry.x = expressions_geometry.y = -1;
  path_geometry.x = path_geometry.y = -1;
  result_geometry.x = result_geometry.y = -1;
  tree_geometry.x = tree_geometry.y = -1;
  variables_geometry.x = variables_geometry.y = -1;

  stream = open_stream( file_name, "r" );
  line_count = 0;

  while (TRUE)
  {
    line = read_line( stream );
    if (line == NULL)
      break;
    line_count++;
    cut_comment( line );
    line_p = line;

    if (*line_p != EOS)
    {
      argument = NULL;
      TRY
      {
	argument = parse_word( &line_p );
	if (strcmp_no_case( argument, "allomorphs_geometry:" ) == 0)
	  parse_geometry( line_p, &allomorphs_geometry );
	else if (strcmp_no_case( argument, "expressions_geometry:" ) == 0)
	  parse_geometry( line_p, &expressions_geometry );
	else if (strcmp_no_case( argument, "path_geometry:" ) == 0)
	  parse_geometry( line_p, &path_geometry );
	else if (strcmp_no_case( argument, "result_geometry:" ) == 0)
	  parse_geometry( line_p, &result_geometry );
	else if (strcmp_no_case( argument, "tree_geometry:" ) == 0)
	  parse_geometry( line_p, &tree_geometry );
	else if (strcmp_no_case( argument, "variables_geometry:" ) == 0)
	  parse_geometry( line_p, &variables_geometry );
	else if (strcmp_no_case( argument, "font_size:" ) == 0)
	  parse_font_size( line_p, &font_size );
	else if (strcmp_no_case( argument, "font:" ) == 0)
	{
	  if (font_family != NULL) 
	    complain( "Font family already defined." );
	  font_family = parse_word( &line_p );
	  parse_end( &line_p );
	}
	else if (strcmp_no_case( argument, "show_indexes:" ) == 0)
	{
	  show_state_indexes = parse_yes_no( &line_p );
	  parse_end( &line_p );
	}
	else if (strcmp_no_case( argument, "hanging_style:" ) == 0)
	{
	  hanging_style = parse_yes_no( &line_p );
	  parse_end( &line_p );
	}
	else if (strcmp_no_case( argument, "inline_path:" ) == 0)
	{
	  inline_path = parse_yes_no( &line_p );
	  parse_end( &line_p );
	}
	else if (strcmp_no_case( argument, "show_tree:" ) == 0)
	{
	  value = parse_word( &line_p );
	  if (strcmp_no_case( value, "full" ) == 0)
	    tree_mode = FULL_TREE;
	  else if (strcmp_no_case( value, "no_dead_ends" ) == 0)
	    tree_mode = NO_DEAD_ENDS;
	  else if (strcmp_no_case( value, "result_paths" ) == 0)
	    tree_mode = RESULT_PATHS;
	  else
	    complain( "\"full\", \"no_dead_ends\" or \"result_paths\" "
		      "expected." );
	  free_mem( &value );
	}
      }
      IF_ERROR
      {
        print_text( error_text, " (\"%s\", line %d)\n", 
		    name_in_path( file_name ), line_count );
      }
      FINALLY 
	free_mem( &argument );
      END_TRY;
    }
    free_mem( &line );
  }
  close_stream( &stream, file_name );
}

/*---------------------------------------------------------------------------*/

static void
read_input( string_t input_type )
/* Read and show INPUT_TYPE from STDIN. */
{
  if (strcmp_no_case( input_type, "allomorph" ) == 0) 
    read_allomorphs();
  else if (strcmp_no_case( input_type, "expressions" ) == 0) 
    read_expressions();
  else if (strcmp_no_case( input_type, "result" ) == 0) 
    read_result();
  else if (strcmp_no_case( input_type, "tree" ) == 0) 
    read_tree();
  else if (strcmp_no_case( input_type, "variables" ) == 0) 
    read_variables();
}

/*---------------------------------------------------------------------------*/

#ifdef POSIX
static void
read_on_condition( gpointer data, gint source, GdkInputCondition condition )
/* This is called by GDK whenever there is something to read. 
 * It reads input from malaga and displays the appropriate canvas. */
{
  string_t input_type;
  
  input_type = read_line( stdin );
  if (input_type == NULL) 
    gtk_exit(0);
  read_input( input_type );
  free_mem( &input_type );
}
#endif

/*---------------------------------------------------------------------------*/


#ifdef WIN32
static bool_t
read_input_notify( string_t input_type )
/* Read INPUT_TYPE from stdin, display it, and notify waiting thread. */ 
{
  if (input_type == NULL) 
    gtk_exit(0);
  read_input( input_type );
  g_mutex_lock( input_consumed_mutex );
  input_consumed = TRUE;
  g_cond_signal( input_consumed_cond );
  g_mutex_unlock( input_consumed_mutex );
  return FALSE;
}
#endif

/*---------------------------------------------------------------------------*/

#ifdef WIN32
static void *
input_thread( void *dummy )
/* Read one line from STDIN, inform main thread when new input has arrived. */
{
  string_t input_type;

  while (TRUE)
  {
    input_type = read_line( stdin );
    g_idle_add( (GSourceFunc) read_input_notify, input_type );
    g_mutex_lock( input_consumed_mutex );
    while (! input_consumed)
      g_cond_wait( input_consumed_cond, input_consumed_mutex );
    input_consumed = FALSE;
    g_mutex_unlock( input_consumed_mutex );
    free_mem( &input_type );
  }
  return NULL;
}
#endif

/*---------------------------------------------------------------------------*/

int 
main( int argc, char *argv[] )
{
  string_t malagarc_path, input_type;

  init_basic( "malshow" );
  init_input();
  init_scanner();

  if (argc == 2) 
  { 
    if (strcmp_no_case( argv[1], "--version" ) == 0
	|| strcmp_no_case( argv[1], "-version" ) == 0
	|| strcmp_no_case( argv[1], "-v" ) == 0) 
    { 
      program_message();
      exit(0);
    } 
    else if (strcmp_no_case( argv[1], "--help" ) == 0
	     || strcmp_no_case( argv[1], "-help" ) == 0
	     || strcmp_no_case( argv[1], "-h" ) == 0) 
    { 
      printf( "Display results of a Malaga process in a graphical format.\n\n"
	      "Usage:\n"
	      "malshow            -- Display results from malaga.\n"
	      "malshow -v[ersion] -- Print the version number.\n"
	      "malshow -h[elp]    -- Print this help.\n\n"
	      "This program is usually started by malaga or mallex.\n" );
      exit(0);
    }
  }

  gtk_init( &argc, &argv );

  /* Read Malaga profile. */
  malagarc_path = NULL;
#ifdef POSIX
  TRY 
    malagarc_path = absolute_path( "~/.malagarc", NULL );
  IF_ERROR 
    RESUME;
  END_TRY;
#endif
#ifdef WIN32
  TRY 
    malagarc_path = absolute_path( "~\\malaga.ini", NULL );
  IF_ERROR 
    RESUME;
  END_TRY;
#endif
  if (malagarc_path != NULL && file_exists( malagarc_path ))
    read_profile( malagarc_path );
  free_mem( &malagarc_path );

  /* Determine font name. */
  if (font_family == NULL)
  {
#ifdef POSIX
    font_family = new_string( "Sans", NULL );
#endif
#ifdef WIN32
    font_family = new_string( "Arial", NULL );
#endif
  }
  
  /* Read first input. */
  input_type = read_line( stdin );
  read_input( input_type );
  free_mem( &input_type );

  /* Prepare to read again whenever there is new input. */
#ifdef POSIX
  gdk_input_add( STDIN_FILENO, GDK_INPUT_READ, read_on_condition, NULL );
#endif
#ifdef WIN32
  g_thread_init( NULL );
  input_consumed_cond = g_cond_new();
  input_consumed_mutex = g_mutex_new();
  g_thread_create( input_thread, NULL, FALSE, NULL );
#endif

  gtk_main();

  free_mem( &font_family );
  terminate_scanner();
  terminate_input();
  terminate_basic();
  return 0;
}

/* End of file. =============================================================*/
