#pragma once
#include "maphoon-lexer/includes.h"
#include "parser_tools.h"
#include "flowchart.h"
#include "flowcsh.h"

namespace flow {

/* This class contains the tokenizer grammar.
 * It cannot generate the full symbol, just the token type. */
template <typename TOKEN>
lexing::classifier<char, TOKEN> build_classifier() {
    using namespace lexing;
    classifier< char, TOKEN > cl(TOKEN::TOK_UNRECOGNIZED_CHAR);

    /* Newline characters in all forms accepted */
    const auto newline = just('\n') | just('\r') | just('\r') * just('\n');
    cl.insert(newline, TOKEN::TOK_NEWLINE);
    /* # starts a comment last until end of line */
    auto noCRLF = every<char>().without('\n').without('\r');
    cl.insert(just('#') * noCRLF.star(), TOKEN::TOK_COMMENT);
    /* // starts a comment last until end of line */
    cl.insert(just('/') * just('/') * noCRLF.star(), TOKEN::TOK_COMMENT);
    /* / * .. * / block comments*/
    cl.insert(word("/*") *
              (every<char>().without('*') |
               (just('*').plus() * every<char>().without('/').without('*')).star()
               ).star() * just('*').plus() * just('/'), TOKEN::TOK_COMMENT);
      /* / * ... unclosed block comments */
    cl.insert(word("/*"), TOKEN::TOK_COMMENT);
    /* ignore whitespace */
    auto WS = just(' ') | just('\t');
    cl.insert(WS, TOKEN::TOK_WHITESPACE);

    cl.insert(just('='), TOKEN::TOK_EQUAL);
    cl.insert(just(','), TOKEN::TOK_COMMA);
    cl.insert(just(';'), TOKEN::TOK_SEMICOLON);
    cl.insert(just('['), TOKEN::TOK_OSBRACKET);
    cl.insert(just(']'), TOKEN::TOK_CSBRACKET);
    cl.insert(word("++"), TOKEN::TOK_PLUS_PLUS);
    cl.insert(just('{'), TOKEN::TOK_OCBRACKET);
    cl.insert(just('}'), TOKEN::TOK_CCBRACKET);

    /* This is a colon-quoted string, finished by a quotation mark (UTF-8 allowed)
     * : "<string>"
     * ... or a colon-quoted string, finished by a newline or file-end (UTF-8 allowed)
     * : "<string>$
     * <string> can contain escaped quotation marks, hashmarks, but no line breaks
     */
    cl.insert(just(':') * WS.star() * just('"') *
              (noCRLF.without('"') | (word("\\\""))).star() *
              just('"').optional(), TOKEN::TOK_COLON_QUOTED_STRING);
    /* This is a non quoted colon-string
     * : <string>
     * terminated by any of: [ { or ;
     * Honors escaping of the above via a backslash. (UTF-8 allowed)
     * Can contain quotation marks (escaped or unescaped), but can not start with it
     * If it contains a hashmark, unescaped [ { or ; is allowed till the end of the line
     * (representing a commented section inside a label)
     */
    auto noterm = every<char>().without(";[{#\\");
    cl.insert(just(':') * (WS | newline).star() *
              (
                  just('#') * noCRLF.star() | //starts with a comment
                  just('\\') * noCRLF |       //or an escape sequence
                  noterm.without("\" \t\n\r") //or something else, but not a quotation mark or whitespace
              ) * (
                  just('#') * noCRLF.star() | // continues with a comment
                  just('\\') * noCRLF |       //or an escape sequence
                  noterm                      // or something else
              ).star(),
              TOKEN::TOK_COLON_STRING);
    //degenerate colon string: empty or just a solo escape char
    cl.insert(just(':') * WS.star() * just('\\').optional(), TOKEN::TOK_COLON_STRING);

    /* A simple quoted string, that can have escaped quotation marks inside. (UTF-8 allowed)
     * ...or a quoted string missing the terminating quotation mark (at line-end).*/
    cl.insert(just('"') * (noCRLF.without('"') | word("\\\"")).star() * just('"').optional(),
              TOKEN::TOK_QSTRING);

    /* Numbers */
    auto digit = range('0', '9');
    auto dot = just('.');
    auto sign = oneof("+-");
    auto num = digit.plus() * (dot * digit.star()).optional(); //no sign
    cl.insert(sign.optional() * num, TOKEN::TOK_NUMBER);

    /* Strings (UTF-8 allowed)
     * Starts with letter or underscore, may also include numbers or dots
     * (but not 2 consecutive dots and dots may not be followed by digits).
     * May end in zero, one or two dots.*/
    auto utf8 = range('A', 'Z') | range('a', 'z') | just('_') | range('\x80', '\xff');
    auto str = utf8 * (utf8 | digit | dot * utf8).star() * dot.optional(); //may end in dots
    cl.insert(str * dot.optional(), TOKEN::TOK_STRING);

    /* Color definitions. We allow any non ASCII UTF-8 character in the color name.
     * Also, color name may contain (single) dots and end in a (single) dot. */
    /* string+-number[,number]*/
    auto comma = just(',');
    cl.insert(str * sign * num * (comma * num).optional(), TOKEN::TOK_COLORDEF);
     /* string,number[+-number].*/
    cl.insert(str * comma * num * (sign * num).optional(), TOKEN::TOK_COLORDEF);
     /* number,number,number[,number] */
    cl.insert(num * comma * num * comma * num * (comma * num).optional(), TOKEN::TOK_COLORDEF);

    //Have concrete keywords (also matching strings) late as for
    //equal length matches, the later will be selected.

    /* These shape definition keywords are case sensitive */
    cl.insert(oneof("MLCESTHP"), TOKEN::TOK_SHAPE_COMMAND);
    /* These keywords are case insensitive */
    cl.insert(iword("bye"), TOKEN::TOK_BYE);
    cl.insert(iword("defshape"), TOKEN::TOK_COMMAND_DEFSHAPE);
    cl.insert(iword("defstyle"), TOKEN::TOK_COMMAND_DEFSTYLE);
    cl.insert(iword("defcolor"), TOKEN::TOK_COMMAND_DEFCOLOR);
    cl.insert(iword("defdesign"), TOKEN::TOK_COMMAND_DEFDESIGN);
    cl.insert(iword("if"), TOKEN::TOK_IF);
    cl.insert(iword("then"), TOKEN::TOK_THEN);
    cl.insert(iword("else"), TOKEN::TOK_ELSE);
    cl.insert(iword("step")|iword("box"), TOKEN::TOK_STEP);
    cl.insert(iword("repeat"), TOKEN::TOK_REPEAT);
    cl.insert(iword("branch"), TOKEN::TOK_BRANCH);
    cl.insert(iword("yes"), TOKEN::TOK_YES);
    cl.insert(iword("no"), TOKEN::TOK_NO);
    cl.insert(iword("start"), TOKEN::TOK_START);
    cl.insert(iword("stop"), TOKEN::TOK_STOP);
    cl.insert(iword("goto"), TOKEN::TOK_GOTO);

    cl = make_deterministic(cl);
    cl = minimize(cl);
    return cl;

}

//When we do Color Syntax Highlight parsing the chart object is Csh instead.
template <bool CSH>
using ChartOrCsh = std::conditional<CSH, FlowCsh, FlowChart>::type;

/* This class tokenizes the input and generates a full symbol. */

template <typename TOKEN, bool CSH, typename READER_FN, typename CSH_STYPE, typename CSH_LTYPE> requires std::is_enum_v<TOKEN>
TOKEN get_token(sv_reader<CSH>& inp, ChartOrCsh<CSH>& C, READER_FN readandclassify, CSH_STYPE& val, CSH_LTYPE& loc) {
    while (true) {
        inp.try_pop();
        if (!inp.has(1)) {
            loc = inp.template commit<false, false>(0);
            return TOKEN::TOK_EOF;
        }

        auto [int_token, len] = readandclassify(0, inp);
    re_scanned:
        TOKEN token = TOKEN(int_token);
        if (len == 0) {
            loc = inp.template commit<false, false>(1);
            return TOKEN::TOK_UNRECOGNIZED_CHAR;
        }
        switch (token) {
        case TOKEN::TOK_WHITESPACE:
            inp.template commit<false, false>(len);
            continue;
        case TOKEN::TOK_NEWLINE:
            inp.template commit<true, false>(len);
            continue;
        case TOKEN::TOK_COMMENT: {
            //if equals to "/*" then this is a comment, that is not closed(file contains no "*/")
            const bool open_only = inp.view(len) == "/*";
            auto loc = inp.template commit<true, false>(len);
            if constexpr (CSH)
                C.AddCSH(loc, COLOR_COMMENT);
            if (open_only) {
                if constexpr (CSH)
                    C.AddCSH_Error(loc, "Unpaired beginning of block comment '/" "*'.");
                else
                    C.Error.Error(loc.start(), "Unpaired beginning of block comment '/" "*'.");
            }
            continue;
        }
        case TOKEN::TOK_DASH:
        case TOKEN::TOK_EQUAL:
        case TOKEN::TOK_COMMA:
        case TOKEN::TOK_SEMICOLON:
        case TOKEN::TOK_PLUS_PLUS:
        case TOKEN::TOK_OSBRACKET:
        case TOKEN::TOK_CSBRACKET:
        case TOKEN::TOK_UNRECOGNIZED_CHAR:
        case TOKEN::TOK_EOF:
        case TOKEN::TOK_OCBRACKET:
        case TOKEN::TOK_CCBRACKET:
            loc = inp.template commit<false, false>(len);
            return token;


        case TOKEN::TOK_SHAPE_COMMAND: {
            _ASSERT(len == 1);
            const char c = inp.peek(0);
            loc = inp.template commit<false, false>(len);
            switch (c) {
            case 'M': val.shapecommand = ShapeElement::MOVE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'L': val.shapecommand = ShapeElement::LINE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'C': val.shapecommand = ShapeElement::CURVE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'E': val.shapecommand = ShapeElement::CLOSE_PATH; return TOKEN::TOK_SHAPE_COMMAND;
            case 'S': val.shapecommand = ShapeElement::SECTION_BG; return TOKEN::TOK_SHAPE_COMMAND;
            case 'T': val.shapecommand = ShapeElement::TEXT_AREA; return TOKEN::TOK_SHAPE_COMMAND;
            case 'H': val.shapecommand = ShapeElement::HINT_AREA; return TOKEN::TOK_SHAPE_COMMAND;
            case 'P': val.shapecommand = ShapeElement::PORT; return TOKEN::TOK_SHAPE_COMMAND;
            default: _ASSERT(0); return TOKEN::TOK_UNRECOGNIZED_CHAR;
            }
        }
        //built-in keywords
        case TOKEN::TOK_BYE:
        case TOKEN::TOK_COMMAND_DEFSHAPE:
        case TOKEN::TOK_COMMAND_DEFCOLOR:
        case TOKEN::TOK_COMMAND_DEFSTYLE:
        case TOKEN::TOK_COMMAND_DEFDESIGN:
        case TOKEN::TOK_IF:
        case TOKEN::TOK_THEN:
        case TOKEN::TOK_ELSE:
        case TOKEN::TOK_STEP:
        case TOKEN::TOK_REPEAT:
        case TOKEN::TOK_BRANCH:
        case TOKEN::TOK_YES:
        case TOKEN::TOK_NO:
        case TOKEN::TOK_START:
        case TOKEN::TOK_STOP:
        case TOKEN::TOK_GOTO:

        //special tokens, whose value is their text
        case TOKEN::TOK_COLORDEF:
        case TOKEN::TOK_NUMBER:
            val.str = inp.view(len);
            loc = inp.template commit<false, false>(len);
            return token;

        case TOKEN::TOK_COLON_STRING: {
            const std::string_view text = inp.view(len);
            _ASSERT(text.starts_with(':'));
            loc = inp.template commit<true, false>(len);
            val.multi_str.init();
            if constexpr (CSH) {
                C.AddCSH_ColonString_CheckAndAddEscapeHint(loc, text, true);
                C.AddColonLabel(loc, text);
            } else
                val.multi_str.set_owning(process_colon_string(text, loc.start()));
            return TOKEN::TOK_COLON_STRING;
        }
        case TOKEN::TOK_COLON_QUOTED_STRING: { //This is transmuted to a TOK_COLON_STRING
            //if last char is not '"' the quotation is not closed
            std::string_view text = inp.view(len);
            _ASSERT(text.starts_with(':'));
            loc = inp.template commit<false, false>(len);
            if constexpr (CSH) {
                if (!text.ends_with('"'))
                    C.AddCSH_ErrorAfter(loc, "Missing closing quotation mark.");
                C.AddCSH_ColonString_CheckAndAddEscapeHint(loc, text, false);
                C.AddColonLabel(loc, text);
                val.multi_str.init();
            } else {
                const char* const colon_pos = text.data();
                const bool closed = text.ends_with('"');
                if (closed)
                    text.remove_suffix(1);
                text.remove_prefix(1); //the colon
                remove_head_tail_whitespace(text);
                FileLineCol pos = loc.start();
                pos.AdvanceCol(text.data() - colon_pos);
                if (!closed)
                    C.Error.Error(loc.start(),
                                  "This opening quotation mark misses its closing pair. "
                                  "Assuming string termination at line-end.",
                                  "Quoted strings cannot have line breaks. Use \'\\n\' to insert a line break.");
                text.remove_prefix(1); //leading quotation mark
                pos.AdvanceCol(1);
                val.multi_str.CombineThemToMe(pos.Print(), text);
            }
            return TOKEN::TOK_COLON_STRING;
        }
        case TOKEN::TOK_STRING: {
            std::string_view text = inp.view(len);
            //TOK_STRING: may end in two dots. In that case we should
            //re-scan without the two dots. (E.g., in "xxx..", we want to
            //scan "xxx" as TOK_XXX and not as TOK_STRING
            if (text.ends_with("..")) {
                text.remove_suffix(2);
                sv_reader<CSH> input(text, {});
                std::tie(int_token, len) = readandclassify(0, input);
                goto re_scanned;
            }
            loc = inp.template commit<false, false>(len);
            val.str.set(text);
            return TOKEN::TOK_STRING;
        }
        case TOKEN::TOK_QSTRING: {
            std::string_view text = inp.view(len);
            loc = inp.template commit<false, false>(len);
            text.remove_prefix(1); //opening quotation mark
            //TOK_QSTRING: if last char is not '"' the quotation is not closed
            if (text.ends_with('"'))
                text.remove_suffix(1); //closing quotation mark
            else if constexpr (CSH)
                C.AddCSH_ErrorAfter(loc, "Missing closing quotation mark.");
            else
                C.Error.Error(loc.start(),
                              "This opening quotation mark misses its closing pair. "
                              "Assuming string termination at line-end.",
                              "Quoted strings cannot have line breaks. Use \'\\n\' to insert a line break.");
            val.str.set(text);
            return TOKEN::TOK_QSTRING;
        }
        default:
            _ASSERT(0);
            loc = inp.template commit<false, false>(len);
            return token;
        } //switch(token)
    }
}
}
