/* The GNU Aris program.

   Copyright (C) 2012 Ian Dunn.

   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 3 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

#include "process.h"
#include "vec.h"
#include "list.h"
#include "var.h"
#include "sen-data.h"
#include "proof.h"
#include "aio.h"
#include "rules.h"
#include "config.h"
#include "interop-isar.h"

#ifdef ARIS_GUI
#include <gtk/gtk.h>
#include "app.h"
#include "aris-proof.h"
#include "rules-table.h"
#endif

// The options array for getopt_long.

static struct option const long_opts[] =
  {
    {"evaluate", no_argument, NULL, 'e'},
    {"premise", required_argument, NULL, 'p'},
    {"conclusion", required_argument, NULL, 'c'},
    {"rule", required_argument, NULL, 'r'},
    {"variable", required_argument, NULL, 'a'},
    {"file", required_argument, NULL, 'f'},
    {"grade", required_argument, NULL, 'g'},
    {"isar", required_argument, NULL, 'i'},
    {"sexpr", required_argument, NULL, 's'},
    {"boolean", no_argument, NULL, 'b'},
    {"list", no_argument, NULL, 'l'},
    {"verbose", no_argument, NULL, 'v'},
    {"version", no_argument, NULL, 0},
    {"help", no_argument, NULL, 'h'},
    {NULL, 0, NULL, 0}
  };

// The structure for holding the argument flags.

struct arg_flags {
  int verbose : 1;
  int evaluate : 1;
  int boolean : 1;
};

/* Lists the rules.
 *  input:
 *    none.
 *  output:
 *    none.
 */
void
list_rules ()
{
  printf ("Inference rules:\n");
  printf ("  mp - Modus Ponens\n");
  printf ("  ad - Addition\n");
  printf ("  sm - Simplification\n");
  printf ("  cn - Conjunction\n");
  printf ("  hs - Hypothetical Syllogism\n");
  printf ("  ds - Disjunctive Syllogism\n");
  printf ("  ex - Excluded Middle\n");
  printf ("  cd - Constructive Dilemma\n");
  printf ("\n");
  printf ("Equivalence Rules\n");
  printf ("  im - Implication\n");
  printf ("  dm - DeMorgan*\n");
  printf ("  as - Association*\n");
  printf ("  co - Commutativity*\n");
  printf ("  id - Idempotence*\n");
  printf ("  dt - Distribution*\n");
  printf ("  eq - Equivalence\n");
  printf ("  dn - Double Negation*\n");
  printf ("  ep - Exportation\n");
  printf ("  sb - Subsumption*\n");
  printf ("\n");
  printf ("Predicate Rules\n");
  printf ("  ug - Universal Generalization\n");
  printf ("  ui - Universal Instantiation\n");
  printf ("  eg - Existential Generalization\n");
  printf ("  ei - Existential Instantiation\n");
  printf ("  bv - Bound Variable\n");
  printf ("  nq - Null Quantifier\n");
  printf ("  pr - Prenex\n");
  printf ("  ii - Identity");
  printf ("  fv - Free Variable\n");
  printf ("\n");
  printf ("Miscellaneous Rules\n");
  printf ("  lm - Lemma\n");
  printf ("  sp - Subproof\n");
  printf ("  sq - Sequence Instantiation\n");
  printf ("  in - Induction\n");
  printf ("\n");
  printf ("Boolean Rules\n");
  printf ("  bi - Boolean Identity*\n");
  printf ("  bn - Boolean Negation*\n");
  printf ("  bd - Boolean Dominance*\n");
  printf ("  sn - Symbol Negation*\n");
  printf ("\n");
  printf ("* = This rule is available in boolean mode.\n");

  exit (EXIT_SUCCESS);
}

/* Prints the version information and exits.
 *  input:
 *    none.
 *  output:
 *    none.
 */
void
version ()
{
  printf ("%s - %s\n", PACKAGE_NAME, VERSION);
  printf ("Copyright (C) 2012 Ian Dunn.\n");
  printf ("License GPLv3: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n");
  printf ("This is free software: you are free to change and redistribute it.\n");
  printf ("There is NO WARRANTY, to the extent premitted by the law.\n");
  printf ("\n");
  printf ("Written by Ian Dunn.\n");
  exit (EXIT_SUCCESS);
}

/* Prints usage and exits.
 *  input:
 *    status - status to exit with.
 *  output:
 *    none.
 */
void
usage (int status)
{
  printf ("Usage: aris [OPTIONS]... [-f FILE] [-g FILE]\n");
  printf ("   or: aris [OPTIONS]... [-p PREMISE]... -r RULE -c CONCLUSION\n");
  printf ("\n");
  printf ("Options:\n");
  printf ("  -a, --variable=VARIABLE        Use VARIABLE as a variable.\n");
  printf ("                                  Place an '*' next to the variable to designate it as arbitrary.\n");
  printf ("  -b, --boolean                  Run Aris in boolean mode.\n");
  printf ("  -c, --conclusion=CONCLUSION    Set CONCLUSION as the conclusion.\n");
  printf ("  -e, --evaluate                 Run Aris in evaluation mode.\n");
  printf ("  -f, --file=FILE                Evaluate FILE.\n");
  printf ("  -g, --grade=FILE               Grade file flag FILE against grade flag FILE.\n");
  printf ("  -l, --list                     List the available rules.\n");
  printf ("  -p, --premise PREMISE          Use PREMISE as a premise.\n");
  printf ("  -r, --rule RULE                Set RULE as the rule.\n");
  printf ("  -v, --verbose                  Print status and error messages.\n");
  printf ("  -h, --help                     Print this help and exit.\n");
  printf ("      --version                  Print the version and exit.\n");
  printf ("\n");
  printf ("Report %s bugs to %s\n", PACKAGE_NAME, PACKAGE_BUGREPORT);
  printf ("%s home page: <http://www.gnu.org/software/%s/>\n",
	  PACKAGE_NAME, PACKAGE);
  printf ("General help using GNU software: <http://www.gnu.org/gethelp/>\n");

  exit (status);
}

/* Main function.  There REALLY shouldn't be any explaination needed. */
int
main (int argc, char *argv[])
{
  int c;

  vec_t * prems;
  unsigned char * conc = NULL;
  char rule[3];
  vec_t * vars;
  char * file_name[256], * grade_name[256];
  proof_t ** proof, ** grade;
  int cur_file, cur_grade;
  char * rule_file = NULL;
  int opt_len, verbose, boolean, evaluate_mode;
  int c_ret;

  cur_file = cur_grade = 0;
  verbose = boolean = evaluate_mode = 0;
  for (c = 0; c < 256; c++)
    file_name[c] = NULL;

  prems = init_vec (sizeof (char*));
  vars = init_vec (sizeof (variable));
  memset ((char *) rule, 0, sizeof (char) * 3);

  //Only one conclusion and one rule can exist.
  //int got_conc = false, got_rule = false;
  int got_conc = 0, got_rule = 0;

  main_conns = cli_conns;

  while (1)
    {
      int opt_idx = 0;

      c = getopt_long (argc, argv, "ep:c:r:a:f:g:i:s:lbvh", long_opts, &opt_idx);

      if (c == -1)
	break;

      switch (c)
	{
	case 'e':
	  evaluate_mode = 1;
	  break;
	case 'p':
	  if (optarg)
	    {
	      unsigned char * sexpr_prem;
	      unsigned char * tmp_str;

	      c_ret = check_text (optarg);
	      if (c_ret == -1)
		exit (EXIT_FAILURE);

	      switch (c_ret)
		{
		case 0:
		  tmp_str = die_spaces_die (optarg);
		  if (!tmp_str)
		    exit (EXIT_FAILURE);
		  sexpr_prem = convert_sexpr (tmp_str);
		  if (!sexpr_prem)
		    exit (EXIT_FAILURE);
		  free (tmp_str);
		  c_ret = vec_add_obj (prems, &sexpr_prem);
		  if (c_ret < 0)
		    exit (EXIT_FAILURE);
		  break;
		case -2:
		  fprintf (stderr, "Text Error - there are mismatched parentheses in premise '%s' - ignoring premise.\n", optarg);
		  break;
		case -3:
		  fprintf (stderr, "Text Error - there are invalid connectives in premise '%s' - ignoring premise.\n", optarg);
		  break;
		case -4:
		  fprintf (stderr, "Text Error - there are invalid quantifiers in premise '%s' - ignoring premise.\n", optarg);
		  break;
		}
	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - premise flag requires an argument, ignoring flag.\n");
	      break;
	    }
	  break;

	case 'c':

	  if (optarg)
	    {
	      if (got_conc)
		{
		  fprintf (stderr, "Argument Error - only one (1) conclusion must be specified, ignoring conclusion \"%s\".\n", optarg);
		  break;
		}

	      c_ret = check_text (optarg);
	      if (c_ret == -1)
		exit (EXIT_FAILURE);

	      switch (c_ret)
		{
		case 0:
		  break;
		case -2:
		  fprintf (stderr, "Text Error - there are mismatched parentheses in your conclusion - '%s'\n", conc);
		  exit (EXIT_FAILURE);
		case -3:
		  fprintf (stderr, "Text Error - there are invalid connectives in your conclusion - '%s'\n", conc);
		  exit (EXIT_FAILURE);
		case -4:
		  fprintf (stderr, "Text Error - there are invalid quantifiers in your conclusion - '%s'\n", conc);
		  exit (EXIT_FAILURE);
		}

	      unsigned char * tmp_str;
	      tmp_str = die_spaces_die (optarg);

	      conc = convert_sexpr (tmp_str);
	      free (tmp_str);

	      got_conc = 1;
	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - conclusion flag requires an argument, ignoring flag.\n");
	      break;
	    }
	  break;

	case 'r':

	  if (optarg)
	    {
	      if (got_rule)
		{
		  fprintf (stderr, "Argument Warning - only one (1) rule must be specified, ignoring rule \"%s\".\n", optarg);
		  break;
		}

	      opt_len = strlen (optarg);

	      if (opt_len > 2 && strncmp (optarg, "lm:", 3))
		{
		  fprintf (stderr, "Argument Warning - a rule must be two (2) characters long, ignoring rule \"%s\".\n", optarg);
		  break;
		}

	      strncpy (rule, optarg, 2);
	      rule[2] = '\0';

	      if (strlen (optarg) > 3)
		{
		  rule_file = (char *) calloc (opt_len - 2, sizeof (char));

		  strncpy (rule_file, optarg + 3, opt_len - 3);
		  rule_file[opt_len - 3] = '\0';
		}
	      got_rule = 1;
	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - rule flag requires an argument, ignoring flag.\n");
	      break;
	    }
	  break;

	case 'a':
	  if (optarg)
	    {
	      if (!islower (optarg[0]))
		{
		  fprintf (stderr, "Argument Warning - the first character of a variable must be lowercase, ignoring variable \"%s\".\n", optarg);
		  break;
		}

	      variable v;
	      opt_len = strlen (optarg);

	      v.text = (unsigned char *) calloc (opt_len, sizeof (char));
	      strcpy (v.text, optarg);

	      if (v.text[opt_len - 1] == '*')
		{
		  v.arbitrary = 1;
		  v.text[opt_len - 1] = '\0';
		}
	      else
		{
		  v.arbitrary = 0;
		}

	      vec_add_obj (vars, &v);
	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - variable flag requires an argument, ignoring flag.\n");
	      break;
	    }
	  break;

	case 'f':

	  if (optarg)
	    {
	      if (file_name[255])
		{
		  fprintf (stderr, "Argument Warning - a maximum of 256 filenames can be specified, ignoring file \"%s\".\n", optarg);
		  break;
		}

	      int arg_len = strlen (optarg);

	      file_name[cur_file] = (char *) calloc (arg_len + 1,
						     sizeof (char));
	      if (!file_name[cur_file])
		{
		  perror (NULL);
		  exit (EXIT_FAILURE);
		}

	      strcpy (file_name[cur_file], optarg);
	      cur_file++;
	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - file flag requires a filename, ignoring flag.\n");
	      break;
	    }
	  break;

	case 'g':
	  if (optarg)
	    {
	      if (grade_name[255])
		{
		  fprintf (stderr, "Argument Warning - a maximum of 256 filenames can be specified, ignoring file \"%s\".\n", optarg);
		  break;
		}

	      int arg_len = strlen (optarg);

	      grade_name[cur_grade] = (char *) calloc (arg_len + 1,
						     sizeof (char));
	      if (!grade_name[cur_grade])
		{
		  perror (NULL);
		  exit (EXIT_FAILURE);
		}

	      strcpy (grade_name[cur_grade], optarg);
	      cur_grade++;

	    }
	  else
	    {
	      fprintf (stderr, "Argument Warning - grade flag requires a filename, ignoring flag.\n");
	    }
	  break;

	case 'i':
	  if (optarg)
	    {
	      proof_t * proof;

	      proof = proof_init ();
	      if (!proof)
		exit (EXIT_FAILURE);

	      main_conns = cli_conns;
	      parse_thy (optarg, proof);
	      return 0;
	    }

	case 's':
	  if (optarg)
	    {
	      main_conns = cli_conns;

	      unsigned char * no_spaces, * sexpr_str;
	      no_spaces = die_spaces_die (optarg);
	      sexpr_str = convert_sexpr (no_spaces);
	      printf ("%s\n", sexpr_str);
	      return 0;
	    }

	case 'b':
	  boolean = 1;
	  break;

	case 'l':
	  list_rules ();
	  break;

	case 'v':
	  verbose = 1;
	  break;

	case 0:
	  if (opt_idx == 12)
	    version ();
	  break;

	case 'h':
	  usage (EXIT_SUCCESS);
	  break;

	default:
	  fprintf (stderr, "Argument Error - ignoring unrecognized option: \"%c\" .\n",c);
	  break;
	}
    }

  if (conc == NULL && evaluate_mode && !file_name[0])
    {
      fprintf (stderr, "Argument Error - a conclusion must be specified in evaluation mode.\n");
      exit (EXIT_FAILURE);
    }

  if (cur_file > 0)
    {
      proof = (proof_t **) calloc (cur_file, sizeof (proof_t *));
      if (!proof)
	{
	  perror (NULL);
	  exit (EXIT_FAILURE);
	}

      for (c = 0; c < cur_file; c++)
	{
	  proof[c] = aio_open (file_name[c]);
	  if (!proof[c])
	    exit (EXIT_FAILURE);
	}
    }

  if (cur_grade > 0)
    {
      grade = (proof_t **) calloc (cur_grade, sizeof (proof_t *));
      if (!grade)
	{
	  perror (NULL);
	  exit (EXIT_FAILURE);
	}

      for (c = 0; c < cur_grade; c++)
	{
	  grade[c] = aio_open (grade_name[c]);
	  if (!grade[c])
	    exit (EXIT_FAILURE);
	}
    }

  if (evaluate_mode)
    {

      main_conns = cli_conns;

      if (cur_file > 0)
	{
	  main_conns = gui_conns;

	  for (c = 0; c < cur_file; c++)
	    {
	      int ret_chk;
	      ret_chk = proof_eval (proof[c], NULL, 1);
	      if (ret_chk == -1)
		exit (EXIT_FAILURE);
	    }

	  /*TODO: Figure out how we are going to handle the grade flag. */
	}
      else
	{
	  char * p_ret;

	  proof_t * proof = NULL;

	  if (rule_file)
	    {
	      int f_len;
	      f_len = strlen (rule_file);
	      if (!strcmp (rule_file + f_len - 4, ".thy"))
		{
		  int ret_chk;
		  proof = proof_init ();
		  if (!proof)
		    exit (EXIT_FAILURE);

		  ret_chk = parse_thy (rule_file, proof);
		  if (ret_chk == -1)
		    exit (EXIT_FAILURE);
		}
	      else
		{
		  main_conns = gui_conns;
		  proof = aio_open (rule_file);
		  if (!proof)
		    exit (EXIT_FAILURE);
		  main_conns = cli_conns;
		}
	    }

	  p_ret = process (conc, prems, rule, vars, proof);
	  if (!p_ret)
	    exit (EXIT_FAILURE);

	  printf ("%s\n", p_ret);
	}

      return 0;
    }
  else
    {
#ifndef ARIS_GUI
      fprintf (stderr, "Fatal Error - evaluate flag not specified in non-gui mode.\n");
      exit (EXIT_FAILURE);
#else

      main_conns = gui_conns;

      gtk_init (&argc, &argv);

      the_app = init_app (boolean, verbose);

      // Get the current working directory from arg0, then determine the help file.
      GFile * arg0, * parent;

      arg0 = g_file_new_for_commandline_arg (argv[0]);
      parent = g_file_get_parent (arg0);
      the_app->working_dir = g_file_get_path (parent);

      if (the_app->working_dir)
	{
	  parent = g_file_get_parent (parent);
	  sprintf (the_app->help_file, "file://%s/doc/aris/index.html", g_file_get_path (parent));
	}

      int ret;
      if (cur_file > 0)
	{

	  for (c = 0; c < cur_file; c++)
	    {
	      aris_proof * new_gui = aris_proof_init_from_proof (proof[c]);
	      if (!new_gui)
		return -1;

	      aris_proof_set_filename (new_gui, file_name[c]);
	      new_gui->edited = 0;

	      ret = the_app_add_gui (new_gui);
	      if (ret < 0)
		return -1;
	    }

	  gtk_widget_show_all (the_app->rt->window);
	  rules_table_align (the_app->rt, the_app->focused);
	  rules_table_set_font (the_app->rt, FONT_TYPE_SMALL);
	}
      else
	{
	  aris_proof * main_gui;
	  main_gui = aris_proof_init (NULL);
	  if (!main_gui)
	    return -1;

	  ret = the_app_add_gui (main_gui);
	  if (ret < 0)
	    return -1;

	  gtk_widget_show_all (the_app->rt->window);
	  rules_table_align (the_app->rt, main_gui);
	  rules_table_set_font (the_app->rt, FONT_TYPE_SMALL);
	}
      gtk_main ();
#endif
    }

  return 0;
}
