var toml = require('../');
var fs = require('fs');

var assert = require("nodeunit").assert;

assert.parsesToml = function(tomlStr, expected) {
  try {
    var actual = toml.parse(tomlStr);
  } catch (e) {
    var errInfo = "line: " + e.line + ", column: " + e.column;
    return assert.fail("TOML parse error: " + e.message, errInfo, null, "at", assert.parsesToml);
  }
  return assert.deepEqual(actual, expected);
};

var exampleExpected = {
  title: "TOML Example",
  owner: {
    name: "Tom Preston-Werner",
    organization: "GitHub",
    bio: "GitHub Cofounder & CEO\n\tLikes \"tater tots\" and beer and backslashes: \\",
    dob: new Date("1979-05-27T07:32:00Z")
  },
  database: {
    server: "192.168.1.1",
    ports: [8001, 8001, 8003],
    connection_max: 5000,
    connection_min: -2,
    max_temp: 87.1,
    min_temp: -17.76,
    enabled: true
  },
  servers: {
    alpha: {
      ip: "10.0.0.1",
      dc: "eqdc10"
    },
    beta: {
      ip: "10.0.0.2",
      dc: "eqdc10"
    }
  },
  clients: {
    data: [ ["gamma", "delta"], [1, 2] ]
  }
};

var hardExampleExpected = {
  the: {
    hard: {
      another_test_string: ' Same thing, but with a string #',
      'bit#': {
        multi_line_array: [']'],
        'what?': "You don't think some user won't do that?"
      },
      harder_test_string: " And when \"'s are in the string, along with # \"",
      test_array: ['] ', ' # '],
      test_array2: ['Test #11 ]proved that', 'Experiment #9 was a success']
    },
    test_string: "You'll hate me after this - #"
  }
};

var easyTableArrayExpected = {
  "products": [
    { "name": "Hammer", "sku": 738594937 },
    { },
    { "name": "Nail", "sku": 284758393, "color": "gray" }
  ]
};

var hardTableArrayExpected = {
  "fruit": [
    {
      "name": "durian",
      "variety": []
    },
    {
      "name": "apple",
      "physical": {
        "color": "red",
        "shape": "round"
      },
      "variety": [
        { "name": "red delicious" },
        { "name": "granny smith" }
      ]
    },
    {},
    {
      "name": "banana",
      "variety": [
        { "name": "plantain" }
      ]
    },
    {
      "name": "orange",
      "physical": {
        "color": "orange",
        "shape": "round"
      }
    }
  ]
}

var badInputs = [
  '[error]   if you didn\'t catch this, your parser is broken',
  'string = "Anything other than tabs, spaces and newline after a table or key value pair has ended should produce an error unless it is a comment"   like this',
  'array = [\n           \"This might most likely happen in multiline arrays\",\n           Like here,\n           \"or here,\n           and here\"\n           ]     End of array comment, forgot the #',
  'number = 3.14  pi <--again forgot the #'
];

exports.testParsesExample = function(test) {
  var str = fs.readFileSync(__dirname + "/example.toml", 'utf-8')
  test.parsesToml(str, exampleExpected);
  test.done();
};

exports.testParsesHardExample = function(test) {
  var str = fs.readFileSync(__dirname + "/hard_example.toml", 'utf-8')
  test.parsesToml(str, hardExampleExpected);
  test.done();
};

exports.testEasyTableArrays = function(test) {
  var str = fs.readFileSync(__dirname + "/table_arrays_easy.toml", 'utf8')
  test.parsesToml(str, easyTableArrayExpected);
  test.done();
};

exports.testHarderTableArrays = function(test) {
  var str = fs.readFileSync(__dirname + "/table_arrays_hard.toml", 'utf8')
  test.parsesToml(str, hardTableArrayExpected);
  test.done();
};

exports.testSupportsTrailingCommasInArrays = function(test) {
  var str = 'arr = [1, 2, 3,]';
  var expected = { arr: [1, 2, 3] };
  test.parsesToml(str, expected);
  test.done();
};

exports.testSingleElementArrayWithNoTrailingComma = function(test) {
  var str = "a = [1]";
  test.parsesToml(str, {
    a: [1]
  });
  test.done();
};

exports.testEmptyArray = function(test) {
  var str = "a = []";
  test.parsesToml(str, {
    a: []
  });
  test.done();
};

exports.testArrayWithWhitespace = function(test) {
  var str = "[versions]\nfiles = [\n 3, \n    5 \n\n ]";
  test.parsesToml(str, {
    versions: {
      files: [3, 5]
    }
  });
  test.done();
};

exports.testEmptyArrayWithWhitespace = function(test) {
  var str = "[versions]\nfiles = [\n  \n  ]";
  test.parsesToml(str, {
    versions: {
      files: []
    }
  });
  test.done();
};

exports.testDefineOnSuperkey = function(test) {
  var str = "[a.b]\nc = 1\n\n[a]\nd = 2";
  var expected = {
    a: {
      b: {
        c: 1
      },
      d: 2
    }
  };
  test.parsesToml(str, expected);
  test.done();
};

exports.testWhitespace = function(test) {
  var str = "a = 1\n  \n  b = 2  ";
  test.parsesToml(str, {
    a: 1, b: 2
  });
  test.done();
};

exports.testUnicode = function(test) {
  var str = "str = \"My name is Jos\\u00E9\"";
  test.parsesToml(str, {
    str: "My name is Jos\u00E9"
  });

  var str = "str = \"My name is Jos\\U000000E9\"";
  test.parsesToml(str, {
    str: "My name is Jos\u00E9"
  });
  test.done();
};

exports.testMultilineStrings = function(test) {
  var str = fs.readFileSync(__dirname + "/multiline_strings.toml", 'utf8');
  test.parsesToml(str, {
    key1: "One\nTwo",
    key2: "One\nTwo",
    key3: "One\nTwo"
  });
  test.done();
};

exports.testMultilineEatWhitespace = function(test) {
  var str = fs.readFileSync(__dirname + "/multiline_eat_whitespace.toml", 'utf8');
  test.parsesToml(str, {
    key1: "The quick brown fox jumps over the lazy dog.",
    key2: "The quick brown fox jumps over the lazy dog.",
    key3: "The quick brown fox jumps over the lazy dog."
  });
  test.done();
};

exports.testLiteralStrings = function(test) {
  var str = fs.readFileSync(__dirname + "/literal_strings.toml", 'utf8');
  test.parsesToml(str, {
    winpath: "C:\\Users\\nodejs\\templates",
    winpath2: "\\\\ServerX\\admin$\\system32\\",
    quoted: "Tom \"Dubs\" Preston-Werner",
    regex: "<\\i\\c*\\s*>"
  });
  test.done();
};

exports.testMultilineLiteralStrings = function(test) {
  var str = fs.readFileSync(__dirname + "/multiline_literal_strings.toml", 'utf8');
  test.parsesToml(str, {
    regex2: "I [dw]on't need \\d{2} apples",
    lines: "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"
  });
  test.done();
};

exports.testIntegerFormats = function(test) {
  var str = "a = +99\nb = 42\nc = 0\nd = -17\ne = 1_000_001\nf = 1_2_3_4_5   # why u do dis";
  test.parsesToml(str, {
    a: 99,
    b: 42,
    c: 0,
    d: -17,
    e: 1000001,
    f: 12345
  });
  test.done();
};

exports.testFloatFormats = function(test) {
  var str = "a = +1.0\nb = 3.1415\nc = -0.01\n" +
            "d = 5e+22\ne = 1e6\nf = -2E-2\n" +
            "g = 6.626e-34\n" +
            "h = 9_224_617.445_991_228_313\n" +
            "i = 1e1_000";
  test.parsesToml(str, {
    a: 1.0,
    b: 3.1415,
    c: -0.01,
    d: 5e22,
    e: 1e6,
    f: -2e-2,
    g: 6.626e-34,
    h: 9224617.445991228313,
    i: 1e1000
  });
  test.done();
};

exports.testDate = function(test) {
  var date = new Date("1979-05-27T07:32:00Z");
  test.parsesToml("a = 1979-05-27T07:32:00Z", {
    a: date
  });
  test.done();
};

exports.testDateWithOffset = function(test) {
  var date1 = new Date("1979-05-27T07:32:00-07:00"),
      date2 = new Date("1979-05-27T07:32:00+02:00");
  test.parsesToml("a = 1979-05-27T07:32:00-07:00\nb = 1979-05-27T07:32:00+02:00", {
    a: date1,
    b: date2
  });
  test.done();
};

exports.testDateWithSecondFraction = function(test) {
  var date = new Date("1979-05-27T00:32:00.999999-07:00");
  test.parsesToml("a = 1979-05-27T00:32:00.999999-07:00", {
    a: date
  });
  test.done();
};

exports.testDateFromIsoString = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/20
  var date = new Date(),
      dateStr = date.toISOString(),
      tomlStr = "a = " + dateStr;

  test.parsesToml(tomlStr, {
    a: date
  });
  test.done();
};

exports.testLeadingNewlines = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/22
  var str = "\ntest = \"ing\"";
  test.parsesToml(str, {
    test: "ing"
  });
  test.done();
};

exports.testInlineTables = function(test) {
  var str = fs.readFileSync(__dirname + "/inline_tables.toml", 'utf8');
  test.parsesToml(str, {
    name: {
      first: "Tom",
      last: "Preston-Werner"
    },
    point: {
      x: 1,
      y: 2
    },
    nested: {
      x: {
        a: {
          b: 3
        }
      }
    },
    points: [
      { x: 1, y: 2, z: 3 },
      { x: 7, y: 8, z: 9 },
      { x: 2, y: 4, z: 8 }
    ],
    arrays: [
      { x: [1, 2, 3], y: [4, 5, 6] },
      { x: [7, 8, 9], y: [0, 1, 2] }
    ]
  });
  test.done();
};

exports.testEmptyInlineTables = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/24
  var str = "a = { }";
  test.parsesToml(str, {
    a: {}
  });
  test.done();
};

exports.testKeyNamesWithWhitespaceAroundStartAndFinish = function(test) {
  var str = "[ a ]\nb = 1";
  test.parsesToml(str, {
    a: {
      b: 1
    }
  });
  test.done();
};

exports.testKeyNamesWithWhitespaceAroundDots = function(test) {
  var str = "[ a . b . c]\nd = 1";
  test.parsesToml(str, {
    a: {
      b: {
        c: {
          d: 1
        }
      }
    }
  });
  test.done();
};

exports.testSimpleQuotedKeyNames = function(test) {
  var str = "[\"ʞ\"]\na = 1";
  test.parsesToml(str, {
    "ʞ": {
      a: 1
    }
  });
  test.done();
};

exports.testComplexQuotedKeyNames = function(test) {
  var str = "[ a . \"ʞ\" . c ]\nd = 1";
  test.parsesToml(str, {
    a: {
      "ʞ": {
        c: {
          d: 1
        }
      }
    }
  });
  test.done();
};

exports.testEscapedQuotesInQuotedKeyNames = function(test) {
  test.parsesToml("[\"the \\\"thing\\\"\"]\na = true", {
    'the "thing"': {
      a: true
    }
  });
  test.done();
};

exports.testMoreComplexQuotedKeyNames = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/21
  test.parsesToml('["the\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
    "the\\ key": {
      one: "one",
      two: 2,
      three: false
    }
  });
  test.parsesToml('[a."the\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
    a: {
      "the\\ key": {
        one: "one",
        two: 2,
        three: false
      }
    }
  });
  test.parsesToml('[a."the-key"]\n\none = "one"\ntwo = 2\nthree = false', {
    a: {
      "the-key": {
        one: "one",
        two: 2,
        three: false
      }
    }
  });
  test.parsesToml('[a."the.key"]\n\none = "one"\ntwo = 2\nthree = false', {
    a: {
      "the.key": {
        one: "one",
        two: 2,
        three: false
      }
    }
  });
  // https://github.com/BinaryMuse/toml-node/issues/34
  test.parsesToml('[table]\n\'a "quoted value"\' = "value"', {
    table: {
      'a "quoted value"': "value"
    }
  });
  // https://github.com/BinaryMuse/toml-node/issues/33
  test.parsesToml('[module]\n"foo=bar" = "zzz"', {
    module: {
      "foo=bar": "zzz"
    }
  });

  test.done();
};

exports.testErrorOnBadUnicode = function(test) {
  var str = "str = \"My name is Jos\\uD800\"";
  test.throws(function() {
    toml.parse(str);
  });
  test.done();
};

exports.testErrorOnDotAtStartOfKey = function(test) {
  test.throws(function() {
    var str = "[.a]\nb = 1";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnDotAtEndOfKey = function(test) {
  test.throws(function() {
    var str = "[.a]\nb = 1";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnTableOverride = function(test) {
  test.throws(function() {
    var str = "[a]\nb = 1\n\n[a]\nc = 2";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnKeyOverride = function(test) {
  test.throws(function() {
    var str = "[a]\nb = 1\n[a.b]\nc = 2";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnKeyOverrideWithNested = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/23
  test.throws(function() {
    var str = "[a]\nb = \"a\"\n[a.b.c]";
    toml.parse(str);
  }, "existing key 'a.b'");
  test.done();
};

exports.testErrorOnKeyOverrideWithArrayTable = function(test) {
  test.throws(function() {
    var str = "[a]\nb = 1\n[[a]]\nc = 2";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnKeyReplace = function(test) {
  test.throws(function() {
    var str = "[a]\nb = 1\nb = 2";
    toml.parse(str);
  });
  test.done()
};

exports.testErrorOnInlineTableReplace = function(test) {
  // https://github.com/BinaryMuse/toml-node/issues/25
  test.throws(function() {
    var str = "a = { b = 1 }\n[a]\nc = 2";
    toml.parse(str);
  }, "existing key 'a'");
  test.done();
};

exports.testErrorOnArrayMismatch = function(test) {
  test.throws(function() {
    var str = 'data = [1, 2, "test"]'
    toml.parse(str);
  });
  test.done();
};

exports.testErrorOnBadInputs = function(test) {
  var count = 0;
  for (i in badInputs) {
    (function(num) {
      test.throws(function() {
        toml.parse(badInputs[num]);
      });
    })(i);
  }
  test.done();
};

exports.testErrorsHaveCorrectLineAndColumn = function(test) {
  var str = "[a]\nb = 1\n [a.b]\nc = 2";
  try { toml.parse(str); }
  catch (e) {
    test.equal(e.line, 3);
    test.equal(e.column, 2);
    test.done();
  }
};
