// Copyright (c) 2016 University of Helsinki
//
// 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 3 of the License, or (at your option) any later version.
// See the file COPYING included with this distribution for more
// information.

//! @file lexc-utils.cc
//!
//! @brief Implementation of some string handling in HFST lexc.

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

#include <string.h>
#include <string>
#include <vector>

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

// for internal epsilon
#include "HfstSymbolDefs.h"

#include "LexcCompiler.h"
#include "lexc-utils.h"
#include "string-utils.h"

#ifdef YACC_USE_PARSER_H_EXTENSION
#include "lexc-parser.h"
#else
#include "lexc-parser.hh"
#endif

using std::string;

// flex stuffa
extern int hlexclineno;
static char *hlexcfilename = 0;
extern char *hlexctext;
extern YYLTYPE hlexclloc;

namespace hfst
{
namespace lexc
{

extern hfst::lexc::LexcCompiler *lexc_;

string &
stripPercents(string &s)
{
    string &stripped = s;
    stripped = replace_all(stripped, "%%", "@PERCENT@");
    stripped = replace_all(stripped, "%", "");
    stripped = replace_all(stripped, "@PERCENT@", "%");
    return stripped;
}

string &
addPercents(string &s)
{
    string &added = s;
    added = replace_all(added, "%", "%%");
    added = replace_all(added, "<", "%<");
    added = replace_all(added, ">", "%>");
    return added;
}

string &
flagJoinerEncode(string &s, bool left)
{
    if (left)
    {
        string &lxs = s;
        lxs = lxs.insert(0, LEXC_FLAG_LEFT_START);
        lxs = lxs.append(LEXC_FLAG_END);
        return lxs;
    }
    else
    {
        string &lxs = s;
        lxs = lxs.insert(0, LEXC_FLAG_RIGHT_START);
        lxs = lxs.append(LEXC_FLAG_END);
        return lxs;
    }
}

string &
joinerEncode(string &s)
{
    string &lxs = s;
    lxs = lxs.insert(0, LEXC_JOINER_START);
    lxs = lxs.append(LEXC_JOINER_END);
    return lxs;
}
string &
joinerDecode(string &s)
{
    assert(s.length() >= 4);
    string &decoded = s;
    size_t jStart = strlen(LEXC_JOINER_START);
    size_t jEnd = strlen(LEXC_JOINER_END);
    decoded = decoded.substr(jStart, (s.length() - (jStart + jEnd)));
    return decoded;
}

string &
regExpresionEncode(string &s)
{
    string &lxs = s;
    lxs = lxs.insert(0, REG_EX_START);
    lxs = lxs.append(LEXC_JOINER_END);
    return lxs;
}

string &
regExpresionDecode(string &s)
{
    assert(s.length() >= 4);
    string &decoded = s;
    size_t jStart = strlen(REG_EX_START);
    size_t jEnd = strlen(LEXC_JOINER_END);
    decoded = decoded.substr(jStart, (s.length() - (jStart + jEnd)));
    return decoded;
}

string &
xreDefinitionEncode(string &s)
{
    string &lxs = s;
    lxs = lxs.insert(0, LEXC_DFN_START);
    lxs = lxs.append(LEXC_DFN_END);
    return lxs;
}

// replaces @ZERO@ with "0" in a string
string
replace_zero(const string s)
{
    string str = s;
    string zero = "@ZERO@";
    size_t start_pos = str.find(zero);
    if (start_pos != std::string::npos)
    {
        str.replace(start_pos, zero.length(), "0");
    }
    return str;
}

// FLEX stuff

static size_t
count_newlines(const char *linestring)
{
    size_t linecount = 0;
    char *linep = (char *)linestring;
    while (*linep != '\0')
    {
        if (*linep == '\n')
        {
            linecount++;
        }
        linep++;
    }
    return linecount;
}

void
set_infile_name(const char *s)
{
    free(hlexcfilename);
    hlexcfilename = strdup(s);
}

void
token_reset_positions()
{
    hlexclloc.first_line = hlexclloc.last_line = 1;
    hlexclloc.first_column = hlexclloc.last_column = 1;
    hlexclineno = 1;
    if (hlexcfilename != 0)
    {
        free(hlexcfilename);
    }
    hlexcfilename = 0;
}

void
token_update_positions(const char *token)
{
    size_t token_length = strlen(token);
    int newlines = hfst::size_t_to_int(count_newlines(token));
    hlexclloc.first_line = hlexclloc.last_line;
    hlexclloc.last_line = (hlexclloc.first_line + newlines);
    // FIXME: columns equal bytes not characters
    hlexclloc.first_column = hlexclloc.last_column + 1;
    if (0 == newlines)
    {
        hlexclloc.last_column
            = (hlexclloc.first_column + hfst::size_t_to_int(token_length));
    }
    else
    {
        char *token_last_line_start = strrchr((char *)token, '\n');
        char *token_end = strrchr((char *)token, '\0');
        hlexclloc.last_column = (token_end - token_last_line_start) - 1;
    }
}

char *
strdup_token_positions()
{
    // N.B. reason for this error format is automagic support by
    // vim/emacs/jedit must be â€œfilename:lineno:colno-lineno:colno: stuffâ€�
    // c.f. http://www.gnu.org/prep/standards/standards.html#Errors
    char *filenames_lines_cols
        = (char *)malloc(sizeof(char) * (strlen(hlexcfilename) + 100));
    // linenumbers and columns
    if ((hlexclloc.first_line == hlexclloc.last_line)
        && (hlexclloc.first_column == (hlexclloc.last_column - 1)))
    {
        // TRANSLATORS: filename, line and column
        (void)sprintf(filenames_lines_cols, "%s:%d.%d", hlexcfilename,
                      hlexclloc.first_line, hlexclloc.first_column);
    }
    else if (hlexclloc.first_line == hlexclloc.last_line)
    {
        // TRANSLATORS: filename, line, column to column
        (void)sprintf(filenames_lines_cols, "%s:%d.%d-%d", hlexcfilename,
                      hlexclloc.first_line, hlexclloc.first_column,
                      hlexclloc.last_column);
    }
    else
    {
        // TRANSLATORS: filename, line-column to line-column
        (void)sprintf(filenames_lines_cols, "%s:%d.%d-%d.%d", hlexcfilename,
                      hlexclloc.first_line, hlexclloc.first_column,
                      hlexclloc.last_line, hlexclloc.last_column);
    }
    return filenames_lines_cols;
}

char *
strdup_token_part()
{
    char *error_token = (char *)malloc(sizeof(char) * strlen(hlexctext) + 100);
    char *maybelbr = strchr(hlexctext, '\n');
    if (maybelbr != NULL)
    {
        char *beforelbr = (char *)malloc(sizeof(char) * strlen(hlexctext) + 1);
        (void)memcpy(beforelbr, hlexctext, maybelbr - hlexctext);
        beforelbr[maybelbr - hlexctext] = '\0';
        (void)sprintf(error_token, "[near: `%s\\n']", beforelbr);
        free(beforelbr);
    }
    else if (strlen(hlexctext) < 80)
    {
        (void)sprintf(error_token, "[near: `%s']", hlexctext);
    }
    else
    {
        (void)sprintf(error_token, "[near: `%30s...' (truncated)]", hlexctext);
    }
    return error_token;
}

char *
strip_percents(const char *s, bool do_zeros)
{
    std::ostream *err = hfst::lexc::lexc_->get_stream(
        (hfst::lexc::lexc_->get_error_stream()));
    char *rv = 0;
    if (do_zeros)
    {
        // worst case scenario is string of 0's...
        rv = (char *)malloc(sizeof(char) * strlen(s) * strlen("@0@") + 1);
    }
    else
    {
        // worst case scenario is string of %0's...
        rv = (char *)malloc(
            sizeof(char) * ((strlen(s) / 2) + 1) * strlen("@ZERO@") + 1);
    }
    char *p = rv;
    const char *c = s;
    bool escaping = false;
    bool in_at = false;
    while (*c != '\0')
    {
        if (in_at)
        {
            if (*c == '@')
            {
                in_at = false;
            }
            *p = *c;
            p++;
            c++;
        }
        else if (escaping)
        {
            if (*c != '0')
            {
                if ((*c != ':') && (*c != '<') && (*c != ' ') && (*c != ';')
                    && (*c != '%') && (*c != '"') && (*c != '@') && (*c != '!')
                    && (*c != '>') && (*c != '#'))
                {
                    char *errmsg
                        = (char *)malloc(sizeof(char) * strlen(c)
                                         + strlen("Unnecessary escape %% "
                                                  "[-Wunnecessary-escapes]")
                                         + 1);
                    if (*c > 0)
                    {
                        sprintf(errmsg,
                                "Unnecessary escape %%%c "
                                "[-Wunnecessary-escapes]",
                                *c);
                    }
                    else
                    {
                        sprintf(errmsg,
                                "Unnecessary escape %%%s "
                                "[-Wunnecessary-escapes]",
                                c);
                    }
                    if (lexc_->isWarning("-Wunnecessary-escapes")
                        && lexc_->areWarningsTreatedAsErrors())
                    {
                        error_at_current_token(0, 0, errmsg);
                    }
                    if (lexc_->isWarning("-Wunnecessary-escapes"))
                    {
                        warning_at_current_token(0, 0, errmsg);
                    }
                }
                *p = *c;
                p++;
            }
            else
            {
                const char *escaped = "@ZERO@";
                while (*escaped != '\0')
                {
                    *p = *escaped;
                    p++;
                    escaped++;
                }
            }
            escaping = false;
            ++c;
        }
        else if (*c == '%')
        {
            escaping = true;
            ++c;
        }
        else if (*c == '@')
        {
            in_at = true;
            *p = *c;
            p++;
            c++;
        }
        else if (do_zeros && (*c == '0'))
        {
            const char *escaped = "@0@";
            while (*escaped != '\0')
            {
                *p = *escaped;
                p++;
                escaped++;
            }
            c++;
        }
        else
        {
            *p = *c;
            ++p;
            ++c;
        }
    }
    *p = '\0';
    if (escaping)
    {
        // fprintf(stderr, "Stray escape char %% in %s\n", s);
        warning_at_current_token(0, 0, "Stray escape char %%\n");
        return NULL;
    }
    return rv;
}

char *
strstrip(const char *s)
{
    char *rv = (char *)malloc(sizeof(char) * strlen(s) + 1);

    if (*s == '\0') // empty string is a special case
    {
        *rv = '\0';
        return rv;
    }

    char *p = rv;
    while (isspace(*s))
    {
        ++s;
    }
    while (*s != '\0')
    {
        *p = *s;
        ++p;
        ++s;
    }
    *p = '\0';
    --p;
    while (isspace(*p))
    {
        *p = '\0';
        --p;
    }
    return rv;
}

char *
strdup_nonconst_part(const char *token, const char *prefix, const char *suffix,
                     bool strip)
{
    size_t prefix_len = 0;
    size_t suffix_len = 0;
    size_t varpart_len = 0;
    size_t token_len = strlen(token);
    char *token_part = (char *)malloc(sizeof(char) * token_len + 1);
    if (prefix)
    {
        prefix_len = strlen(prefix);
    }
    if (suffix)
    {
        suffix_len = strlen(suffix);
    }
    varpart_len = strlen(token) - prefix_len - suffix_len;
    assert(varpart_len <= token_len);

    if (prefix == NULL)
    {
        assert(strncmp(token, "", prefix_len) == 0);
    }
    else
    {
        assert(strncmp(token, prefix, prefix_len) == 0);
    }

    if (suffix == NULL)
    {
        assert(strncmp(token + prefix_len + varpart_len, "", suffix_len) == 0);
    }
    else
    {
        assert(strncmp(token + prefix_len + varpart_len, suffix, suffix_len)
               == 0);
    }

    (void)memcpy(token_part, token + prefix_len, varpart_len);
    token_part[varpart_len] = '\0';
    if (strip)
    {
        char *tmp = strstrip(token_part);
        free(token_part);
        token_part = tmp;
    }
    return token_part;
}

#define COLOUR_BOLD "\033[01m"
#define COLOUR_RED "\033[31m"
#define COLOUR_GREEN "\033[32m"
#define COLOUR_YELLOW "\033[33m"
#define COLOUR_BLUE "\033[34m"
#define COLOUR_MAGENTA "\033[35m"
#define COLOUR_CYAN "\033[36m"
#define COLOUR_RESET "\033[0m"

static bool
should_colourise()
{
    if (isatty(1))
    {
        return true;
    }
    else
    {
        return false;
    }
    return false;
}

void
error_at_current_token(int, int, const char *format)
{
    char *leader = strdup_token_positions();
    char *token = strdup_token_part();
    // fprintf(stderr, "%s: %s %s\n", leader, format, token);
    std::ostream *err = hfst::lexc::lexc_->get_stream(
        (hfst::lexc::lexc_->get_error_stream()));
    if (should_colourise())
    {
        *err << COLOUR_BOLD;
    }
    *err << leader << ": ";
    if (should_colourise())
    {
        *err << COLOUR_RED;
    }
    *err << format << ": ";
    if (should_colourise())
    {
        *err << COLOUR_RESET;
    }
    *err << token << std::endl;
    hfst::lexc::lexc_->flush(err);
    free(leader);
}

void
warning_at_current_token(int, int, const char *format)
{
    char *leader = strdup_token_positions();
    char *token = strdup_token_part();
    // fprintf(stderr, "%s: %s %s\n", leader, format, token);
    std::ostream *err = hfst::lexc::lexc_->get_stream(
        (hfst::lexc::lexc_->get_error_stream()));
    if (should_colourise())
    {
        *err << COLOUR_BOLD;
    }
    *err << leader << ": ";
    if (should_colourise())
    {
        *err << COLOUR_YELLOW;
    }
    *err << format << ": ";
    if (should_colourise())
    {
        *err << COLOUR_RESET;
    }
    *err << token << std::endl;
    hfst::lexc::lexc_->flush(err);
    free(leader);
}
pair<vector<string>, vector<string> >
find_med_alingment(const vector<string> &s1, const vector<string> &s2)
{
    const size_t substitution = 100;
    const size_t deletion = 1;
    const size_t insertion = 1;

    const size_t len1 = s1.size(), len2 = s2.size();
    vector<vector<unsigned int> > d(len1 + 1, vector<unsigned int>(len2 + 1));
    vector<vector<unsigned int> > dir(len1 + 1,
                                      vector<unsigned int>(len2 + 1));
    d[0][0] = 0;
    dir[0][0] = 0;
    for (unsigned int i = 1; i <= len1; ++i)
    {
        d[i][0] = deletion * i;
        dir[i][0] = DELETE;
    }
    for (unsigned int i = 1; i <= len2; ++i)
    {
        d[0][i] = insertion * i;
        dir[0][i] = INSERT;
    }

    for (unsigned int i = 1; i <= len1; ++i)
    {
        for (unsigned int j = 1; j <= len2; ++j)
        {

            int sub = d[i - 1][j - 1]
                      + (s1[i - 1] == s2[j - 1] ? 0 : substitution);
            int ins = d[i][j - 1] + insertion;
            int del = d[i - 1][j] + deletion;

            if (sub <= ins && sub <= del)
            {
                d[i][j] = sub;
                dir[i][j] = SUBSTITUTE;
            }
            // It's important to prioritize "del" when it has the same value as
            // "ins"" because we want the first string to have zeroes before
            // the second one. For example, if we have two strings: abc and
            // xyz, we would prefer the output 000abc:xyz000 over
            // abc000:000xyz, because we need to invert the transducer to use
            // it for the analysis and in lookup we want zeroes as late as
            // possible on the upper side. Anyway, the order of this two
            // following blocks determines the order of zeroes on upper/lower
            // side
            else if (del <= sub && del <= ins)
            {
                d[i][j] = del;
                dir[i][j] = DELETE;
            }
            else
            {
                d[i][j] = ins;
                dir[i][j] = INSERT;
            }
        }
    }

    vector<string> medcwordin;
    vector<string> medcwordout;

    int x, y, i;
    for (x = hfst::size_t_to_int(s1.size()),
        y = hfst::size_t_to_int(s2.size()), i = 0;
         (x > 0) || (y > 0); i++)
    {
        int dirValue = dir[x][y];

        if (dirValue == SUBSTITUTE)
        {
            medcwordin.push_back(s1[x - 1]);
            medcwordout.push_back(s2[y - 1]);
            x--;
            y--;
        }
        else if (dirValue == INSERT)
        {
            medcwordin.push_back(EPSILON_);
            medcwordout.push_back(s2[y - 1]);
            y--;
        }
        else
        {
            medcwordin.push_back(s1[x - 1]);
            medcwordout.push_back(EPSILON_);
            x--;
        }
    }

    std::reverse(medcwordin.begin(), medcwordin.end());
    std::reverse(medcwordout.begin(), medcwordout.end());

    pair<vector<string>, vector<string> > returnValue(medcwordin, medcwordout);
    return returnValue;
}

}
}
