#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define UNITY_INTERNAL 1

#include "unity.h"

int print_lexemes(char* s, UnitySyntax, FILE* out); /* from lex.lex */
void display_syntaxes();
void parse_and_display_string(const char* unit_string, UnitySyntax input_syntax, UnitySyntax output_syntax, int validate);
void set_lexeme_output(FILE*);
void Usage(int);

static char* progname;

extern int yydebug;                    /* declared in bison, defined in unity-lexer.c */

int main(int argc, char** argv)
{
    char* unit_string = NULL;
    int ch;
    int show_lexemes = 0;
    int show_syntaxes = 0;
    int validate = 0;
    UnitySyntax input_syntax = UNITY_SYNTAX_FITS;
    UnitySyntax output_syntax = UNITY_SYNTAX_DEBUG;
    int verbosity = 0;
    /* const char *input_syntax = "fits"; */
    /* const char *output_syntax = "debug"; */

    progname = argv[0];

    while ((ch = getopt(argc, argv, "ghi:lo:SvV")) != -1) {
        switch (ch) {
          case 'g':
            verbosity += 1;
            break;

          case 'l':             /* show lexemes */
            show_lexemes = 1;
            break;

          case 'h':             /* help */
            Usage(0);
            break;
          case 'i':             /* input syntax */
            input_syntax = unity_identify_parser(optarg);
            if (input_syntax == UNITY_SYNTAX_NONE) {
                Usage(1);
            }
            break;
          case 'o':             /* output syntax */
            output_syntax = unity_identify_parser(optarg);
            if (output_syntax == UNITY_SYNTAX_NONE) {
                Usage(1);
            }
            break;
          case 'v':             /* validate parsed expression */
            validate = 1;
            break;
          case 'S':
            show_syntaxes = 1;
            break;
          case 'V':             /* show version information */
            puts(unity_version_string());
            exit(0);
            break;
          default:
            Usage(1);
            break;
        }
    }
    argc -= optind;
    argv += optind;
    unit_string = *argv;

    switch (verbosity) {
      case 0:
        break;
      case 1:
        set_lexeme_output(stderr);
        break;
      default:
        yydebug = 1;
        break;
    }

    if (show_syntaxes) {
        display_syntaxes();
    } else if (unit_string == NULL) {
        Usage(1);               /* ...and EXIT with an error message */
    } else {
        int nunits;
        if (show_lexemes) {
            print_lexemes(unit_string, input_syntax, stdout);
        }
        for (nunits=argc; nunits>0; nunits--, unit_string++) {
            parse_and_display_string(unit_string, input_syntax, output_syntax, validate);
        }
    }

    return 0;
}

void display_syntaxes()
{
    const char** syntaxes = unity_parser_names();
    int i;
    char* spacer = "parsers:    ";
    for (i=0; syntaxes[i] != NULL; i++) {
        printf("%s%s", spacer, syntaxes[i]);
        spacer = ", ";
    }
    syntaxes = unity_formatter_names();
    spacer = "\nformatters: ";
    for (i=0; syntaxes[i] != NULL; i++) {
        printf("%s%s", spacer, syntaxes[i]);
        spacer = ", ";
    }
    putchar('\n');
    return;
}

void parse_and_display_string(const char* unit_string, UnitySyntax input_syntax, UnitySyntax output_syntax, int validate)
{
    const UnitExpression* parsed;

    if ((parsed = unity_parse_string(unit_string, input_syntax)) == NULL) {
        // parse error
        fprintf(stderr, "parse error: %s\n", unity_parse_error());
    } else {
        char buf[1024];
        printf("%s\n", unity_write_formatted(buf, 1024, parsed, output_syntax));
        if (validate) {
            printf("check: all units recognised?           %s\n",
                   unity_check_expression(parsed, input_syntax, UNITY_CHECK_RECOGNISED) ? "yes" : "no");
            printf("check: all units recommended?          %s\n",
                   unity_check_expression(parsed, input_syntax, UNITY_CHECK_RECOMMENDED) ? "yes" : "no");
            printf("check: all units satisfy constraints?  %s\n",
                   unity_check_expression(parsed, input_syntax, UNITY_CHECK_CONSTRAINTS) ? "yes" : "no");
        }
        unity_free_expression(parsed);
    }
    return;
}

void Usage(int status)
{
    fprintf(stderr, "%s\n\nUsage: %s [-lgvV] [-i isyntax] [-o osyntax] string ...\n       %s -S\n\
\n\
Options:\n\
    -i isyntax : set the input syntax -- for syntaxes see -S\n\
    -o osyntax : set the output syntax -- for syntaxes see -S\n\
    -v         : display validation checks\n\
    -l         : show the lexemes in the input (really only for debugging)\n\
    -g         : turn on debugging\n\
    -S         : show the list of parsers and formatters (and exit)\n\
    -V, -h     : show the version number, or show this help\n\
\n\
See https://heptapod.host/nxg/unity/ for source and downloads.\n",
            unity_version_string(), progname, progname);
    exit(status);
}
