/*
 * fadot.c: example usage of finite automata library
 *
 * Copyright (C) 2009, Francis Giraldeau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
 */

/*
 * The purpose of this example is to show the usage of libfa
 */

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

#include <fa.h>

#define UCHAR_NUM (UCHAR_MAX+1)

const char *progname;

static const char *const escape_chars    = "\"\a\b\t\n\v\f\r\\";
static const char *const escape_names = "\"abtnvfr\\";

__attribute__((noreturn))
static void usage(void) {
  fprintf(stderr, "\nUsage: %s [OPTIONS] REGEXP\n", progname);
  fprintf(stderr, "\nCompile REGEXP and apply operation to them. By default, just print\n");
  fprintf(stderr, "the minimized regexp.\n");
  fprintf(stderr, "\nOptions:\n\n");
  fprintf(stderr, "  -o OPERATION       one of : show concat union intersect json\n");
  fprintf(stderr, "                              complement minus example print\n");
  fprintf(stderr, "  -f DOT_FILE        Path of output .dot file\n");
  fprintf(stderr, "  -n                 do not minimize resulting finite automaton\n");

  exit(EXIT_FAILURE);
}

int main (int argc, char **argv) {

  opterr = 0;

  int reduce = 1;
  char *file_output = NULL;
  const char *operation = NULL;
  FILE *fd;
  int c;
  int nb_regexp = 0;

  progname = argv[0];

  while ((c = getopt (argc, argv, "nhf:o:")) != -1)
    switch (c)
      {
      case 'n':
        reduce = 0;
        break;
      case 'f':
        file_output = optarg;
        break;
      case 'h':
        usage();
        break;
      case 'o':
        operation = optarg;
        break;
      case '?':
        if (optopt == 'o' || optopt == 'f')
          fprintf (stderr, "Option -%c requires an argument.\n", optopt);
        else if (isprint (optopt))
          fprintf (stderr, "Unknown option `-%c'.\n", optopt);
        else
          fprintf (stderr,
                   "Unknown option character `\\x%x'.\n",
                   optopt);
        usage();
        break;
      default:
        usage();
        break;
      }

  //printf ("reduce = %d, file_output = %s, operation = %s\n",
  //        reduce, file_output, operation);

  if (operation == NULL)
    operation = "show";

  for (int i = optind; i < argc; i++) {
    nb_regexp++;
  }

  if (nb_regexp == 0) {
    printf("Please specify regexp to process.\n");
    usage();
  }

  struct fa* fa_result = NULL;

  if (!strcmp(operation,"show")) {
    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);
  } else if (!strcmp(operation,"concat")) {

    if (nb_regexp < 2) {
      fprintf(stderr,"Please specify 2 or more regexp to concat");
      return 1;
    }

    fa_result = fa_make_basic(FA_EPSILON);
    struct fa* fa_tmp;
    for (int i = optind; i < argc; i++) {
      fa_compile(argv[i], strlen(argv[i]), &fa_tmp);
      fa_result = fa_concat(fa_result, fa_tmp);
    }

  } else if (!strcmp(operation, "union")) {

    if (nb_regexp < 2) {
      fprintf(stderr,"Please specify 2 or more regexp to union");
      return 1;
    }

    fa_result = fa_make_basic(FA_EMPTY);

    struct fa* fa_tmp;
    for (int i = optind; i < argc; i++) {
      fa_compile(argv[i], strlen(argv[i]), &fa_tmp);
      fa_result = fa_union(fa_result, fa_tmp);
    }

  } else if (!strcmp(operation, "intersect")) {

    if (nb_regexp < 2) {
      fprintf(stderr,"Please specify 2 or more regexp to intersect");
      return 1;
    }

    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);
    struct fa* fa_tmp;

    for (int i = optind+1; i < argc; i++) {
      fa_compile(argv[i], strlen(argv[i]), &fa_tmp);
      fa_result = fa_intersect(fa_result, fa_tmp);
    }

  } else if (!strcmp(operation, "complement")) {

    if (nb_regexp >= 2) {
      fprintf(stderr,"Please specify one regexp to complement");
      return 1;
    }

    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);
    fa_result = fa_complement(fa_result);

  } else if (!strcmp(operation, "minus")) {

    if (nb_regexp != 2) {
      fprintf(stderr,"Please specify 2 regexp for operation minus");
      return 1;
    }

    struct fa* fa_tmp1;
    struct fa* fa_tmp2;
    fa_compile(argv[optind], strlen(argv[optind]), &fa_tmp1);
    fa_compile(argv[optind+1], strlen(argv[optind+1]), &fa_tmp2);
    fa_result = fa_minus(fa_tmp1, fa_tmp2);

  } else if (!strcmp(operation, "example")) {

    if (nb_regexp != 1) {
      fprintf(stderr,"Please specify one regexp for operation example");
      return 1;
    }

    char* word = NULL;
    size_t word_len = 0;
    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);
    fa_example(fa_result, &word, &word_len);
    printf("Example word = %s\n", word);

  } else if (!strcmp(operation, "json")) {
    if (nb_regexp != 1) {
      fprintf(stderr,"Please specify one regexp for operation example");
      return 1;
    }

    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);

    if (reduce) {
      fa_minimize(fa_result);
    }

    if (file_output != NULL) {
      if ((fd = fopen(file_output, "w")) == NULL) {
        fprintf(stderr, "Error while opening file %s \n", file_output);
        return 1;
      }

      fa_json(fd, fa_result);
      fclose(fd);
    } else {
      fa_json(stdout, fa_result);
    }

    return 0;

  } else if (!strcmp(operation, "print")) {
    if (nb_regexp != 1) {
      fprintf(stderr,"Please specify one regexp for operation example");
      return 1;
    }

    fa_compile(argv[optind], strlen(argv[optind]), &fa_result);

    if (reduce) {
      fa_minimize(fa_result);
    }

    struct state *st, *st2;
    uint32_t num_trans, i;
    unsigned char begin, end;

    st = fa_state_initial(fa_result);

    printf("%s. Initial state: %p", fa_is_deterministic(fa_result) ? "DFA" : "NFA", fa_result);

    while (st != NULL) {
      num_trans = fa_state_num_trans(st);
      printf("\nFrom state %p (final = %s):\n", st, fa_state_is_accepting(st) == 1 ? "true" : "false");
      for (i = 0; i < num_trans; i++) {
        if (fa_state_trans(st, i, &st2, &begin, &end) < 0) {
          printf("Some error occur. \n");
        }
        if (begin == end)
          printf("     to: %p, label: %d\n", st2, begin);
        else
          printf("     to: %p, label: %d-%d\n", st2, begin, end);
      }
      st = fa_state_next(st);
    }

    return 0;

  }

  if (reduce) {
    fa_minimize(fa_result);
  }

  if (file_output != NULL) {
    if ((fd = fopen(file_output, "w")) == NULL) {
      fprintf(stderr, "Error while opening file %s \n", file_output);
      return 1;
    }

    fa_dot(fd, fa_result);
    fclose(fd);
  } else {
    int r;
    char *rx;
    size_t rx_len;

    r = fa_as_regexp(fa_result, &rx, &rx_len);
    if (r < 0) {
      fprintf(stderr, "Converting FA to regexp failed\n");
      return 1;
    }

    for (size_t i=0; i < rx_len; i++) {
      char *p;
      if (rx[i] && ((p = strchr(escape_chars, rx[i])) != NULL)) {
        printf("\\%c", escape_names[p - escape_chars]);
      } else if (! isprint(rx[i])) {
        printf("\\%030o", (unsigned char) rx[i]);
      } else {
        putchar(rx[i]);
      }
    }
    putchar('\n');
    free(rx);
  }

  return 0;
}
