#include <catch.hpp>
#include <internal/config_document_parser.hpp>
#include <hocon/parser/config_document_factory.hpp>

#include "test_utils.hpp"

using namespace std;
using namespace hocon;
using namespace hocon::test_utils;

shared_ptr<config_node_root> conf_parse(string original_text) {
    return config_document_parser::parse(
            token_iterator(fake_origin(),
                           unique_ptr<istringstream>(new istringstream(original_text)),
                           true),
            fake_origin(),
            config_parse_options());
}

shared_ptr<config_node_root> json_parse(string original_text) {
    return config_document_parser::parse(
            token_iterator(fake_origin(),
                           unique_ptr<istringstream>(new istringstream(original_text)),
                           false),
            fake_origin(),
            config_parse_options().set_syntax(config_syntax::JSON));
}

shared_node_value parse_value_conf(string original_text) {
    return config_document_parser::parse_value(
            token_iterator(fake_origin(),
                           unique_ptr<istringstream>(new istringstream(original_text)),
                           true),
            fake_origin(),
            config_parse_options());
}

shared_node_value parse_value_json(string original_text) {
    return config_document_parser::parse_value(
            token_iterator(fake_origin(),
                           unique_ptr<istringstream>(new istringstream(original_text)),
                           false),
            fake_origin(),
            config_parse_options().set_syntax(config_syntax::JSON));
}

void parse_values_test(string original_text, bool simple = true, string final_text = "") {
    string expected = final_text.empty() ? original_text : final_text;
    auto node = parse_value_conf(original_text);
    REQUIRE(expected == node->render());
    if (simple) {
        REQUIRE(dynamic_pointer_cast<const config_node_simple_value>(node));
    } else {
        REQUIRE(dynamic_pointer_cast<const config_node_complex_value>(node));
    }

    auto json_node = parse_value_json(original_text);
    REQUIRE(expected == json_node->render());
    if (simple) {
        REQUIRE(dynamic_pointer_cast<const config_node_simple_value>(node));
    } else {
        REQUIRE(dynamic_pointer_cast<const config_node_complex_value>(node));
    }
}

TEST_CASE("parse values", "[doc-parser]") {
    SECTION("parse simple values") {
        parse_values_test("123");
        parse_values_test("123.456");
        parse_values_test("\"a string\"");
        parse_values_test("true");
        parse_values_test("false");
        parse_values_test("null");
    }

    SECTION("parse complex values") {
        parse_values_test("{\"a\": \"b\"}", false);
        parse_values_test("[\"a\", \"b\", \"c\"]", false);
    }

    SECTION("parse concatenations in CONF mode") {
        string original_text = "123 456 \"abc\"";
        auto node = parse_value_conf(original_text);
        REQUIRE(original_text == node->render());
    }

    SECTION("parse keys with no separators and object values in CONF mode") {
        string original_text = "{\"foo\" { \"bar\" : 12 } }";
        auto node = parse_value_conf(original_text);
        REQUIRE(original_text == node->render());
    }
}

void invalid_json_test(string original_text, string message) {
    try {
        json_parse(original_text);
    } catch (parse_exception& ex) {
        REQUIRE(string(ex.what()).find(message) != string::npos);
    }
}

TEST_CASE("parse single value errors", "[doc-parser]") {
    SECTION("illegal leading and trailing whitespace and comments") {
        REQUIRE_THROWS(parse_value_conf("   123"));
        REQUIRE_THROWS(parse_value_conf("123   "));
        REQUIRE_THROWS(parse_value_conf(" 123 "));
        REQUIRE_THROWS(parse_value_conf("\n123"));
        REQUIRE_THROWS(parse_value_conf("123\n"));
        REQUIRE_THROWS(parse_value_conf("\n123\n"));
        REQUIRE_THROWS(parse_value_conf("#this is a comment\n123#comment"));
    }

    SECTION("illegal whitespace after concatenation") {
        REQUIRE_THROWS(parse_value_conf("123 456 789   "));
    }
}

TEST_CASE("invalid JSON parse errors", "[doc-parser]") {
    invalid_json_test("unquotedtext", "Token not allowed in valid JSON");

    SECTION("no substitutions in JSON") {
        invalid_json_test("${a.b}", "Substitutions (${} syntax) not allowed in JSON");
    }

    SECTION("no concatenations in JSON") {
        invalid_json_test("{ \"foo\": 123 456 789 } ", "Expecting close brace '}' or a comma");
    }

    SECTION("value separators required in JSON") {
        invalid_json_test("{\"foo\" { \"bar\" : 12 } }", "may not be followed by token: '{'");
    }

    SECTION("require root element in JSON") {
        invalid_json_test("\"a\": 123, \"b\": 456", "Document must have an object or array at root");
    }
}

TEST_CASE("parse empty document", "[doc-parser]") {
    auto node = conf_parse("");
    REQUIRE(dynamic_pointer_cast<const config_node_object>(node->value()));
    REQUIRE(node->value()->children().empty());

    auto node2 = conf_parse("#comment\n#comment\n\n");
    REQUIRE(dynamic_pointer_cast<const config_node_object>(node2->value()));
}

void parse_test_string(string original_text) {
    auto node = conf_parse(original_text);
    REQUIRE(original_text == node->render());
}

TEST_CASE("comprehensive parse test") {
    SECTION("without curly braces") {
        parse_test_string("foo:bar");
        parse_test_string(" foo : bar ");
        parse_test_string("include \"foo.conf\" ");
        parse_test_string("   \nfoo:bar\n   ");
        parse_test_string("aUnquoted: bar\naString = \"qux\"\naNumb:123\naDouble=123.456\naTrue=true\naFalse=false\n"
                          "aNull=null\naSub =  ${a.b}\ninclude \"foo.conf\"");
    }

    SECTION("with curly braces") {
        parse_test_string("{}");
        parse_test_string("{foo:bar}");
        parse_test_string("{  foo  :  bar  }");
        parse_test_string("{foo:bar}    ");
        parse_test_string("{include \"foo.conf\"}");
        parse_test_string("  \n{foo:bar}\n  ");
        parse_test_string("{\naUnquoted: bar\naString = \"qux\"\naNumb:123\naDouble=123.456\naTrue=true\naFalse=false\n"
                          "aNull=null\naSub =  ${a.b}\ninclude \"foo.conf\"\n}");
    }

    SECTION("nested maps") {
        parse_test_string("\nfoo.bar.baz : {\n\tqux : \"abcdefg\"\n\t\"abc\".def.\"ghi\" : 123\n\tabc = "
                          "{ food:bar }\n}\nqux = 123.456\n");
    }

    SECTION("comments in maps") {
        parse_test_string("{\nfoo: bar\n// this is a comment\nbaz:qux // this is another comment\n}");
    }

    SECTION("arrays") {
        parse_test_string("[]");
        parse_test_string("[foo]");
        parse_test_string("[foo,]");
        parse_test_string("[foo,]   ");
        parse_test_string("   \n[]\n   ");
        parse_test_string("[foo, bar,\"qux\", 123,123.456, true,false, null, ${a.b}]");
        parse_test_string("[foo,   bar,\"qux\"   , 123  123.456, true,false, null,   ${a.b}   ]");
    }

    SECTION("basic concatenation") {
        parse_test_string("[foo bar baz qux]");
        parse_test_string("{foo: foo bar baz qux}");
        parse_test_string("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]");
    }

    SECTION("all together now") {
        parse_test_string("{\nfoo: bar baz   qux    ernie\n// The above was a concatenation\n\nbaz  =  [ abc 123, {a:12\n"
        "\t\t\t\tb: {\n\t\t\t\t\tc: 13\n\t\t\t\t\td: {\n\t\t\t\t\t\ta: 22\n\t\t\t\t\t\tb: \"abcdefg\" # this is a "
        "comment\n\t\t\t\t\t\tc: [1, 2, 3]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t}, # this was an object in an array\n"
        "\t\t\t\t//The above value is a map containing a map containing a map, all in an array\n\t\t\t\t22,\n"
        "\t\t\t\t// The below is an array contained in another array\n\t\t\t\t[1,2,3]]\n//This is a map with some "
        "nested maps and array within it, as well as as some concatenations\nqux {\n\tbaz: abc 123\n\tbar: {\n\t\t"
        "baz: abcdefg\n\t\tbar: {\n\t\t\ta: null\n\t\t\tb: true\n\t\t\tc: [true false 123, null, [1, 2, 3]]\n\t\t}"
        "\n\t}\n}\n// Did I cover everything?\n}");
    }
}