#include <json.hh>
#include "test.hh"

using namespace std;

void parse(string s, const sw::json::var_type &expect=sw::json::var_type::undef())
{
  sw::json::var_type json;
  try {
    std::string sout;
    {
      std::stringstream ssout;
      ssout << "JSON  : '" << s << "'" << endl << "OUTPUT: ";
      json = sw::json::parse(s);
      json.dump(ssout);
      sout = ssout.str();
      while(sout.length() > 1 && ::isspace(sout[sout.length()-1])) { // std98: no .back()
        sout.resize(sout.length()-1);
      }
    }
    test_comment("Check: " << endl << sout);
    if(expect.is_unset() || expect == "<IGNORE>") {
      test_pass("Ok    : yes (no expectation/ignored)");
    } else if(expect == "<EXCEPTION>") {
      test_fail("Ok    : no (exception was expected)");
    } else if(json == expect) {
      test_pass("Ok    : yes");
    } else {
      test_fail("Ok    : no");
    }
  } catch(const sw::json::exception_type &e) {
    test_comment("Exception: " << e.what());
    if(expect == "<EXCEPTION>") {
      test_pass("Ok    : yes (expected exception)");
    } else if(expect == "<IGNORE>") {
      test_pass("Ok    : yes (ignored)");
    } else {
      test_fail(std::string("Ok    : no, exception: ") + e.what());
    }
  } catch(...) {
    test_fail("Ok    : no (unexpected exception type)");
  }
}

void compose(const string &json_parse, const string &expect="<IGNORE>",
    sw::json::options_type opts=sw::json::o_strict)
{
  try {
    std::stringstream sout;
    sout << "JSON IN : '" << json_parse << "'" << endl
         << "EXPECT  : '" << expect << "'" << endl
         << "JSON OUT: ";
    sw::json::var_type j = sw::json::parse(json_parse);
    string s = sw::json::compose(j, opts);
    sout << "'" << s << "'";
    test_comment("Composed: " << endl << sout.str());
    if(expect == "<IGNORE>") {
      test_pass("Ok      : yes (no expectation/ignored)");
    } else if(expect == s) {
      test_pass("Ok      : yes");
    } else {
      test_fail("Ok      : no");
    }
  } catch(const sw::json::exception_type& e) {
    test_fail(std::string("Ok      : no (exception ") + e.what() + ")");
  } catch(...) {
    test_fail("Ok      : no (unexpected exception)");
  }

}

////////////////////////////////////////////////////////////////////////////////

void test_num()
{
  parse("12345", 12345);
  parse("12.345", 12.345);
  parse("12.34e5", 12.34e5);
  parse("12.3E5", 12.3e5);
  parse("1.2e-5", 1.2e-5);
  parse("-1.2e-5", -1.2e-5);
  parse("+1.2e-5", 1.2e-5);
  parse("1.2e+5", 1.2e5);
  parse(".2e-5", 0.2e-5);
  parse(".1", 0.1);
  parse("  12345  ", 12345);
  parse("12.34.5", "<EXCEPTION>");
  parse("12.34r5", "<EXCEPTION>");
  parse("12.3E4e5", "<EXCEPTION>");

}

void test_literal()
{
  parse("null", sw::var::nul());
  parse("true", true);
  parse("false", false);
  parse(" null", sw::var::nul());
  parse("Nulll", "<EXCEPTION>");
  parse("NULL", sw::var::nul());
}

void test_string()
{
  parse(" \"Hello\" ", "Hello");
  parse(" \"He\\\"llo\" ", "He\"llo");
  parse(" \"He\\\\\\\"llo\" ", "He\\\"llo");
  parse(" \"Hello\\\" ", "<EXCEPTION>");
  parse(" \"Hello\\\" ", "<EXCEPTION>");

  parse(" \"\\n\" ", "\n");
  parse(" \"\\r\" ", "\r");
  parse(" \"\\t\" ", "\t");
  parse(" \"\\f\" ", "\f");
  parse(" \"\\a\" ", "\a");
  parse(" \"\\v\" ", "\v");
  parse(" \"\\b\" ", "\b");
  parse(" \"\\\\\" ", "\\");
}

void test_array()
{
  parse("[]");
  parse("[ 1, 2, 3, 4, 5 ]");
  parse("[ 1, \"A\", null, false, 5.5 ]");
  parse(" [1,\"A\",null,false,5.5] ");
  parse(" [\"A\", [], [1,2,3], [4,5,6] ] ");
  parse("[[[[[[[[1]]]]]]]]");

}

void test_object()
{
  parse("{}");
  parse("{ \"a\" : 10  }");
  parse("{a:10}");
  parse("{  a  :  \"Hello\"  }");
  parse("{a:1,b:2,c:3}");
  parse("{a:{b:1,c:2,d:3}, e:{f:4,g:5.5}, h:{}, i : { } }");
}

void test_combined()
{
  parse(
   "\n{ // This is an object                               \n"
   "a: [ 1, 2.1e5, .3, {a_1:1,a_2:2}], // a: is an array   \n"
   "b: { \"b1\":\"b1 value\" } ,       // b: is an object  \n"
   "c: [false /* means 0 */, true, /*>*/null/*<*/, \"$\"] // c: array as well \n"
   "\n}\n"
  );

}

void test_comments()
{
  parse("// Comment ignored\n\n");
  parse("// REM\n //REM\n 10 //REM");
  parse("[ // REM\n 10, //REM\n 20 //REM \n ]");
  parse("/**/");
  parse("/*REM*/");
  parse("10/*REM*/");
  parse("/*REM*/10");
  parse("/*R/E/M*/10");
  parse("/*R//E//M*/10");
  parse("// /*REM*/\n10");
  parse("[ /*<<*/10/*>>*/, 20 //COMMENT\n, /*//*/30/*Value30*/ ]");
  parse("/**/4/**/0", "<EXCEPTION>");
}

////////////////////////////////////////////////////////////////////////////////

void test_compose_basic()
{
  compose("", "");
  compose("null", "null");
  compose("true", "true");
  compose("false", "false");
  compose("\"\"", "\"\"");
  compose("[]", "[]");
  compose("{}", "{}");
  compose("1234", "1234");
  compose("1.234", "1.234");
  compose("1e3", "1000");
  compose(".1", "0.1");
}

void test_compose_array()
{
  compose("[]", "[]");
  compose("[1]", "[1]");
  compose("[1,2]", "[1,2]");
  compose("[[],[]]", "[[],[]]");
  compose("[[],[[1]]]", "[[],[[1]]]");
}

void test_compose_object()
{
  compose("{}", "{}");
  compose("{a:1}", "{\"a\":1}");
  compose("{a:1}", "{a:1}", sw::json::o_no);
  compose("{1:1}", "{\"1\":1}", sw::json::o_no);
  compose("{_:1}", "{\"_\":1}", sw::json::o_no);
  compose("{_a:1}", "{\"_a\":1}", sw::json::o_no);
  compose("{abcdefghijklmnopqrstuvwxyz01234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaa:1}",
      "{\"abcdefghijklmnopqrstuvwxyz01234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaa\":1}");
  compose("{a_1:1}", "{a_1:1}", sw::json::o_no);
}

void test_compose_string()
{
  compose("\"\"", "\"\"");
  compose("\"H\"", "\"H\"");
  compose("\"Hello\"", "\"Hello\"");
  compose("\" \\\\ \"", "\" \\\\ \"");
  compose("\" \\n \"", "\" \\n \"");

  compose("\" \\u0009 \"", "\" \\t \"");
  compose("\" \\u000a \"", "\" \\n \"");
  compose("\" \\u000d \"", "\" \\r \"");
  compose("\" \\u000b \"", "\" \\v \"");
  compose("\" \\u0007 \"", "\" \\a \"");
  compose("\" \\u000c \"", "\" \\f \"");
}

void test_compose_combined()
{
  compose("[1,{a:1},2.2,.3,\"\"]", "[1,{\"a\":1},2.2,0.3,\"\"]");
  compose("{c:15,b:10,a:5}", "{a:5,b:10,c:15}", sw::json::o_no);
}

////////////////////////////////////////////////////////////////////////////////

void test()
{
  test_num();
  test_literal();
  test_string();
  test_array();
  test_object();
  test_comments();
  test_combined();
  test_compose_basic();
  test_compose_array();
  test_compose_object();
  test_compose_string();
  test_compose_combined();
}
