//! @file hfst-lexc-compiler.cc
//!
//! @brief Lexc compilation command line tool
//!
//! @author HFST Team

//  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, version 3 of the License.
//
//  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/>.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WINDOWS
#include <io.h>
#endif

#include <fstream>
#include <iostream>
#include <memory>

#include <cstdio>
#include <cstdlib>
#include <cstring>

#ifdef _MSC_VER
#include "hfst-getopt.h"
#else
#include <getopt.h>
#endif

#include "HfstOutputStream.h"
#include "HfstTransducer.h"
#include "parsers/LexcCompiler.h"

using hfst::HfstInputStream;
using hfst::HfstOutputStream;
using hfst::HfstTransducer;
using hfst::ImplementationType;
using hfst::lexc::LexcCompiler;

#include "hfst-commandline.h"
#include "hfst-program-options.h"
#include "hfst-tool-metadata.h"

#include "inc/globals-common.h"
static char **lexcfilenames = 0;
static FILE **lexcfiles = 0;
static unsigned int lexccount = 0;
static bool is_input_stdin = true;
static ImplementationType format = hfst::UNSPECIFIED_TYPE;
static bool align_strings = false;
static bool with_flags = false;
static bool minimize_flags = false;
static bool rename_flags = false;
static bool treat_warnings_as_errors = false;
static bool warn_everything = false;
static bool warn_missing_lexicons = false;
static bool warn_unused_lexicons = false;
static bool warn_repeated_lexicons = false;
static bool warn_one_sided_flags = false;
static bool warn_missing_alphabets = false;
static bool warn_unnecessary_escapes = false;
static bool xerox_composition
    = true; // Compatibility with Xerox tools is the default
static bool encode_weights = false;
static bool enc = false;
static bool split_characters = false;

void
print_usage()
{
    // c.f. http://www.gnu.org/prep/standards/standards.html#g_t_002d_002dhelp
    fprintf(message_out,
            "Usage: %s [OPTIONS...] [INFILE1...]]\n"
            "Compile lexc files into transducer\n"
            "\n",
            program_name);
    print_common_program_options(message_out);
    fprintf(message_out,
            "Input/Output options:\n"
            "  -f, --format=FORMAT     compile into FORMAT transducer\n"
            "  -o, --output=OUTFILE    write result into OUTFILE\n");
    fprintf(
        message_out,
        "Lexc options:\n"
        "  -A, --alignStrings      align characters in input and output "
        "strings\n"
        "  -E, --encode-weights    encode weights when minimizing "
        "(default is false)\n"
        "  -F, --withFlags         use flags to hyperminimize result\n"
        "  -M, --minimizeFlags     if --withFlags is used, minimize the "
        "number of flags\n"
        "  -R, --renameFlags       if --withFlags and --minimizeFlags are "
        "used, rename\n"
        "                          flags (for testing)\n"
        "  -x,\n"
        "  --xerox-composition=BOOL   Whether flag diacritics are "
        "treated as ordinary\n"
        "                             symbols in composition (default "
        "is true).\n"
        "  -X, --xfst=VARIABLE     toggle xfst compatibility option "
        "VARIABLE.\n"
        "   --split-characters     disable unicode character parsing for "
        "multichars\n"
        "   -Wall                  enable all warnings:\n"
        "   -Wone-sided-flags      warn about one sided flag diacritics\n"
        "   -Wrepeated-lexicons    warn about repeat lexicon names\n"
        "   -Wmissing-lexicons     warn about lexicons used but missing\n"
        "   -Wunused-lexicons      warn about lexicons defined but unused\n"
        "   -Wmissing-alphabets    warn about implicit alphabets\n"
        "   -Wunnecessary-escapes  warn about unneeded %%-escapes\n"
        "   -Werror                treat warnings as errors\n");
    fprintf(message_out, "\n");
    fprintf(message_out,
            "If INFILE or OUTFILE are omitted or -, standard streams will "
            "be used\n"
            "The possible values for FORMAT are { sfst, openfst-tropical, "
            "openfst-log,\n"
            "foma, optimized-lookup-unweighted, optimized-lookup-weighted }.\n"
            "BOOL is one of {true,ON,yes} or {false,OFF,no}.\n"
            "Xfst variables are {flag-is-epsilon (default OFF)}.\n");
    fprintf(message_out,
            "\n"
            "Examples:\n"
            "  %s -o cat.hfst cat.lexc               Compile single-file "
            "lexicon\n"
            "  %s -o L.hfst Root.lexc 2.lexc 3.lexc  Compile multi-file "
            "lexicon\n"
            "\n"
            "Using weights:\n"
            "  LEXICON Root\n"
            "  cat # \"weight: 2\" ;    Define weight for a word\n"
            "  <[dog::1]+> # ;        Use weights in regular expressions\n"
            "\n"
            "Using weights has an effect only if FORMAT is weighted, i.e.\n"
            "{ openfst-tropical, openfst-log, optimized-lookup-weighted }.\n"
            "\n",
            program_name, program_name);
    print_report_bugs();
    fprintf(message_out, "\n");
    print_more_info();
}

int
parse_options(int argc, char **argv)
{
    extend_options_getenv(&argc, &argv);
    // use of this function requires options are settable on global scope
    while (true)
    {
        static const struct option long_options[]
            = { HFST_GETOPT_COMMON_LONG,
                { "encode-weights", no_argument, 0, 'E' },
                { "format", required_argument, 0, 'f' },
                { "output", required_argument, 0, 'o' },
                { "alignStrings", no_argument, 0, 'A' },
                { "withFlags", no_argument, 0, 'F' },
                { "minimizeFlags", no_argument, 0, 'M' },
                { "renameFlags", no_argument, 0, 'R' },
                { "xerox-composition", required_argument, 0, 'x' },
                { "xfst", required_argument, 0, 'X' },
                { "Werror", no_argument, 0, 'Q' },
                { "Wstuff", required_argument, 0, 'W' },
                { "split-characters", no_argument, 0, '9' },
                { 0, 0, 0, 0 } };
        int option_index = 0;
        int c = getopt_long(argc, argv,
                            HFST_GETOPT_COMMON_SHORT "Ef:o:AFMRx:X:QW:9",
                            long_options, &option_index);
        if (-1 == c)
        {
            break;
        }
        switch (c)
        {
#include "inc/getopt-cases-common.h"
        case 'A':
            align_strings = true;
            break;
        case 'E':
            encode_weights = true;
            break;
        case 'f':
            format = hfst_parse_format_name(optarg);
            break;
        case 'F':
            with_flags = true;
            break;
        case 'M':
            minimize_flags = true;
            break;
        case 'R':
            rename_flags = true;
            break;
        case 'x':
        {
            const char *argument = hfst_strdup(optarg);
            if (strcmp(argument, "yes") == 0 || strcmp(argument, "true") == 0
                || strcmp(argument, "ON") == 0)
            {
                xerox_composition = true;
            }
            else if (strcmp(argument, "no") == 0
                     || strcmp(argument, "false") == 0
                     || strcmp(argument, "OFF") == 0)
            {
                xerox_composition = false;
            }
            else
            {
                fprintf(stderr,
                        "Error: unknown option to --xerox-composition: '%s'\n",
                        optarg);
                return EXIT_FAILURE;
            }
        }
        break;
        case 'X':
        {
            const char *argument = hfst_strdup(optarg);
            if (strcmp(argument, "flag-is-epsilon") == 0)
            {
                hfst::set_flag_is_epsilon_in_composition(true);
            }
            else
            {
                fprintf(stderr, "Error: unknown option to --xfst: '%s'\n",
                        optarg);
                return EXIT_FAILURE;
            }
        }
        break;
        case 'Q':
            treat_warnings_as_errors = true;
            warn_one_sided_flags = true;
            warn_missing_lexicons = true;
            warn_unused_lexicons = true;
            warn_repeated_lexicons = true;
            // compatibility?? might change later:
            warn_unnecessary_escapes = false;
            warn_missing_alphabets = false;
            fprintf(stderr, "Warning: --Werror is deprecated, "
                            "use -Werror -Wall instead\n");
            break;
        case 'W':
            if (strcmp(optarg, "error") == 0)
            {
                treat_warnings_as_errors = true;
            }
            else if (strcmp(optarg, "all") == 0)
            {
                warn_one_sided_flags = true;
                warn_everything = true;
                warn_missing_lexicons = true;
                warn_unused_lexicons = true;
                warn_repeated_lexicons = true;
                warn_missing_alphabets = true;
                warn_unnecessary_escapes = true;
                warn_missing_alphabets = true;
            }
            else if (strcmp(optarg, "one-sided-flags") == 0)
            {
                warn_one_sided_flags = true;
            }
            else if (strcmp(optarg, "no-one-sided-flags") == 0)
            {
                warn_one_sided_flags = false;
            }
            else if (strcmp(optarg, "unused-lexicons") == 0)
            {
                warn_unused_lexicons = true;
            }
            else if (strcmp(optarg, "no-unused-lexicons") == 0)
            {
                warn_unused_lexicons = false;
            }
            else if (strcmp(optarg, "repeated-lexicons") == 0)
            {
                warn_repeated_lexicons = true;
            }
            else if (strcmp(optarg, "no-repeated-lexicons") == 0)
            {
                warn_repeated_lexicons = false;
            }
            else if (strcmp(optarg, "missing-lexicons") == 0)
            {
                warn_missing_lexicons = true;
            }
            else if (strcmp(optarg, "no-missing-lexicons") == 0)
            {
                warn_missing_lexicons = false;
            }
            else if (strcmp(optarg, "missing-alphabets") == 0)
            {
                warn_missing_alphabets = true;
            }
            else if (strcmp(optarg, "no-missing-alphabets") == 0)
            {
                warn_missing_alphabets = false;
            }
            else if (strcmp(optarg, "unnecessary-escapes") == 0)
            {
                warn_unnecessary_escapes = true;
            }
            else if (strcmp(optarg, "no-unnecessary-escapes") == 0)
            {
                warn_unnecessary_escapes = false;
            }
            else
            {
                fprintf(stderr, "Unknown warning option %s\n", optarg);
                return EXIT_FAILURE;
            }
            break;
        case '9':
            split_characters = true;
            break;
#include "inc/getopt-cases-error.h"
        }
    }

#include "inc/check-params-common.h"
    if (format == hfst::UNSPECIFIED_TYPE)
    {
        if (!silent)
        {
            hfst_warning(0, 0, "Defaulting to OpenFst tropical type");
        }
        format = hfst::TROPICAL_OPENFST_TYPE;
    }

    if (argc - optind > 0)
    {
        lexcfilenames
            = static_cast<char **>(malloc(sizeof(char *) * (argc - optind)));
        lexcfiles
            = static_cast<FILE **>(malloc(sizeof(char *) * (argc - optind)));
        while (optind < argc)
        {
            lexcfilenames[lexccount] = hfst_strdup(argv[optind]);
            lexcfiles[lexccount] = hfst_fopen(argv[optind], "r");
            lexccount++;
            optind++;
        }
        is_input_stdin = false;
    }
    else
    {
        lexcfilenames = static_cast<char **>(malloc(sizeof(char *)));
        lexcfiles = static_cast<FILE **>(malloc(sizeof(FILE *)));
        lexcfilenames[0] = hfst_strdup("<stdin>");
        lexcfiles[0] = stdin;
        is_input_stdin = true;
        lexccount++;
    }
    return EXIT_CONTINUE;
}

int
lexc_streams(LexcCompiler &lexc, HfstOutputStream &outstream)
{
    for (unsigned int i = 0; i < lexccount; i++)
    {
        verbose_printf("Parsing lexc file %s\n", lexcfilenames[i]);
        if (lexcfiles[i] == stdin)
        {
#ifdef _MSC_VER
            if (!silent)
            {
                hfst_warning(
                    0, 0,
                    "Reading from standard input. UTF-8 characters "
                    "outside\n"
                    "ascii range are supported only if input comes from a "
                    "file.");
            }
#endif
            lexc.parse(stdin);
        }
        else
        {
            lexc.parse(lexcfilenames[i]);
        }
    }
    verbose_printf("Compiling... ");
    HfstTransducer *res = lexc.compileLexical();

    if (0 == res)
    {
        if (lexccount == 1)
        {
            hfst_error(
                EXIT_FAILURE, 0,
                "The file %s did not compile cleanly.\n"
                "(if there are no error messages above, try -v or -d to "
                "get more info)",
                lexcfilenames[0]);
        }
        else
        {
            hfst_error(
                EXIT_FAILURE, 0,
                "The files %s... did not compile cleanly.\n"
                "(if there are no error messages above, try -v or -d to "
                "get more info)",
                lexcfilenames[0]);
        }
        return EXIT_FAILURE;
    }
    hfst_set_name(*res, lexcfilenames[0], "lexc");
    hfst_set_formula(*res, lexcfilenames[0], "L");
    verbose_printf("\nWriting... ");
    outstream << *res;
    verbose_printf("done\n");
    delete res;
    outstream.close();

    if (encode_weights)
    {
        hfst::set_encode_weights(enc);
    }

    return EXIT_SUCCESS;
}

int
main(int argc, char **argv)
{
#ifdef WINDOWS
    _setmode(1, _O_BINARY);
#endif

    hfst_set_program_name(argv[0], "0.1", "HfstLexc");

    int retval = parse_options(argc, argv);
    if (retval != EXIT_CONTINUE)
    {
        return retval;
    }
    // close buffers, we use streams
    for (unsigned int i = 0; i < lexccount; i++)
    {
        if (lexcfiles[i] != stdin)
        {
            fclose(lexcfiles[i]);
        }
    }
    if (outfile != stdout)
    {
        fclose(outfile);
    }

    enc = hfst::get_encode_weights();
    if (encode_weights)
    {
        hfst::set_encode_weights(true);
    }

    verbose_printf("Reading from ");
    for (unsigned int i = 0; i < lexccount; i++)
    {
        verbose_printf("%s, ", lexcfilenames[i]);
    }
    verbose_printf("writing to %s\n", outfilename);
    // here starts the buffer handling part
    auto outstream
        = (outfile != stdout)
              ? std::make_unique<HfstOutputStream>(outfilename, format)
              : std::make_unique<HfstOutputStream>(format);
    hfst::set_xerox_composition(xerox_composition);
    LexcCompiler lexc(format, with_flags, align_strings);
    lexc.setMinimizeFlags(minimize_flags);
    lexc.setRenameFlags(rename_flags);
    // lexc.with_flags_ = with_flags;
    if (silent)
    {
        lexc.setVerbosity(0);
    }
    else
    {
        lexc.setVerbosity(verbose ? 2 : 1);
    }
    if (treat_warnings_as_errors)
    {
        lexc.setTreatWarningsAsErrors(true);
    }
    lexc.setWarning("-Wone-sided-flags", warn_one_sided_flags);
    lexc.setWarning("-Wunused-lexicons", warn_unused_lexicons);
    lexc.setWarning("-Wrepeated-lexicons", warn_repeated_lexicons);
    lexc.setWarning("-Wmissing-lexicons", warn_missing_lexicons);
    lexc.setWarning("-Wmissing-alphabets", warn_missing_alphabets);
    lexc.setWarning("-Wunnecessary-escapes", warn_unnecessary_escapes);
    if (!silent && verbose)
    {
        printf("Warning settings: ");
        if (treat_warnings_as_errors)
        {
            printf(" -Werror (fail on all warnings)");
        }
        if (warn_one_sided_flags)
        {
            printf(" -Wone-sided-flags");
        }
        if (warn_unused_lexicons)
        {
            printf(" -Wunused-lexicons");
        }
        if (warn_repeated_lexicons)
        {
            printf(" -Wrepeated-lexicons");
        }
        if (warn_missing_lexicons)
        {
            printf(" -Wmissing-lexicons");
        }
        if (warn_missing_alphabets)
        {
            printf(" -Wmissing-alphabets");
        }
        if (warn_unnecessary_escapes)
        {
            printf(" -Wunnecessary-escapes");
        }
        printf("\n");
    }
    if (split_characters)
    {
        fprintf(stderr,
                "Warningn: Disabling unicode character tokenisation\n");
        lexc.setSplitCharacters(true);
    }
    retval = lexc_streams(lexc, *outstream);
    for (unsigned int i = 0; i < lexccount; i++)
    {
        free(lexcfilenames[i]);
    }
    free(lexcfilenames);
    free(outfilename);
    return retval;
}
