"use strict";

const gService = Cc["@mozilla.org/http-sfv-service;1"].getService(
  Ci.nsISFVService
);

add_task(async function test_sfv_bare_item() {
  // tests bare item
  let item_int = gService.newInteger(19);
  Assert.equal(item_int.value, 19, "check bare item value");

  let item_bool = gService.newBool(true);
  Assert.equal(item_bool.value, true, "check bare item value");
  item_bool.value = false;
  Assert.equal(item_bool.value, false, "check bare item value");

  let item_float = gService.newDecimal(145.45);
  Assert.equal(item_float.value, 145.45);

  let item_str = gService.newString("some_string");
  Assert.equal(item_str.value, "some_string", "check bare item value");

  let item_byte_seq = gService.newByteSequence("aGVsbG8=");
  Assert.equal(item_byte_seq.value, "aGVsbG8=", "check bare item value");

  let item_token = gService.newToken("*a");
  Assert.equal(item_token.value, "*a", "check bare item value");
});

add_task(async function test_sfv_params() {
  // test params
  let params = gService.newParameters();
  let bool_param = gService.newBool(false);
  let int_param = gService.newInteger(15);
  let decimal_param = gService.newDecimal(15.45);

  params.set("bool_param", bool_param);
  params.set("int_param", int_param);
  params.set("decimal_param", decimal_param);

  Assert.throws(
    () => {
      params.get("some_param");
    },
    /NS_ERROR_UNEXPECTED/,
    "must throw exception as key does not exist in parameters"
  );
  Assert.equal(
    params.get("bool_param").QueryInterface(Ci.nsISFVBool).value,
    false,
    "get parameter by key and check its value"
  );
  Assert.equal(
    params.get("int_param").QueryInterface(Ci.nsISFVInteger).value,
    15,
    "get parameter by key and check its value"
  );
  Assert.equal(
    params.get("decimal_param").QueryInterface(Ci.nsISFVDecimal).value,
    15.45,
    "get parameter by key and check its value"
  );
  Assert.deepEqual(
    params.keys(),
    ["bool_param", "int_param", "decimal_param"],
    "check that parameters contain all the expected keys"
  );

  params.delete("int_param");
  Assert.deepEqual(
    params.keys(),
    ["bool_param", "decimal_param"],
    "check that parameter has been deleted"
  );

  Assert.throws(
    () => {
      params.delete("some_param");
    },
    /NS_ERROR_UNEXPECTED/,
    "must throw exception upon attempt to delete by non-existing key"
  );
});

add_task(async function test_sfv_inner_list() {
  // create primitives for inner list
  let item1_params = gService.newParameters();
  item1_params.set("param_1", gService.newToken("*smth"));
  let item1 = gService.newItem(gService.newDecimal(172.145865), item1_params);

  let item2_params = gService.newParameters();
  item2_params.set("param_1", gService.newBool(true));
  item2_params.set("param_2", gService.newInteger(145454));
  let item2 = gService.newItem(
    gService.newByteSequence("weather"),
    item2_params
  );

  // create inner list
  let inner_list_params = gService.newParameters();
  inner_list_params.set("inner_param", gService.newByteSequence("tests"));
  let inner_list = gService.newInnerList([item1, item2], inner_list_params);

  // check inner list members & params
  let inner_list_members = inner_list.QueryInterface(Ci.nsISFVInnerList).items;
  let inner_list_parameters = inner_list
    .QueryInterface(Ci.nsISFVInnerList)
    .params.QueryInterface(Ci.nsISFVParams);
  Assert.equal(inner_list_members.length, 2, "check inner list length");

  let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
  Assert.equal(
    inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
    172.145865,
    "check inner list member value"
  );

  let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
  Assert.equal(
    inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
    "weather",
    "check inner list member value"
  );

  Assert.equal(
    inner_list_parameters.get("inner_param").QueryInterface(Ci.nsISFVByteSeq)
      .value,
    "tests",
    "get inner list parameter by key and check its value"
  );
});

add_task(async function test_sfv_item() {
  // create parameters for item
  let params = gService.newParameters();
  let param1 = gService.newBool(false);
  let param2 = gService.newString("str_value");
  let param3 = gService.newBool(true);
  params.set("param_1", param1);
  params.set("param_2", param2);
  params.set("param_3", param3);

  // create item field
  let item = gService.newItem(gService.newToken("*abc"), params);

  Assert.equal(
    item.value.QueryInterface(Ci.nsISFVToken).value,
    "*abc",
    "check items's value"
  );
  Assert.equal(
    item.params.get("param_1").QueryInterface(Ci.nsISFVBool).value,
    false,
    "get item parameter by key and check its value"
  );
  Assert.equal(
    item.params.get("param_2").QueryInterface(Ci.nsISFVString).value,
    "str_value",
    "get item parameter by key and check its value"
  );
  Assert.equal(
    item.params.get("param_3").QueryInterface(Ci.nsISFVBool).value,
    true,
    "get item parameter by key and check its value"
  );

  // check item field serialization
  let serialized = item.serialize();
  Assert.equal(
    serialized,
    `*abc;param_1=?0;param_2="str_value";param_3`,
    "serialized output must match expected one"
  );
});

add_task(async function test_sfv_list() {
  // create primitives for List
  let item1_params = gService.newParameters();
  item1_params.set("param_1", gService.newToken("*smth"));
  let item1 = gService.newItem(gService.newDecimal(145454.14568), item1_params);

  let item2_params = gService.newParameters();
  item2_params.set("param_1", gService.newBool(true));
  let item2 = gService.newItem(
    gService.newByteSequence("weather"),
    item2_params
  );

  let inner_list = gService.newInnerList(
    [item1, item2],
    gService.newParameters()
  );

  // create list field
  let list = gService.newList([item1, inner_list]);

  // check list's members
  let list_members = list.members;
  Assert.equal(list_members.length, 2, "check list length");

  // check list's member of item type
  let member1 = list_members[0].QueryInterface(Ci.nsISFVItem);
  Assert.equal(
    member1.value.QueryInterface(Ci.nsISFVDecimal).value,
    145454.14568,
    "check list member's value"
  );
  let member1_parameters = member1.params;
  Assert.equal(
    member1_parameters.get("param_1").QueryInterface(Ci.nsISFVToken).value,
    "*smth",
    "get list member's parameter by key and check its value"
  );

  // check list's member of inner list type
  let inner_list_members = list_members[1].QueryInterface(
    Ci.nsISFVInnerList
  ).items;
  Assert.equal(inner_list_members.length, 2, "check inner list length");

  let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem);
  Assert.equal(
    inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value,
    145454.14568,
    "check inner list member's value"
  );

  let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem);
  Assert.equal(
    inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value,
    "weather",
    "check inner list member's value"
  );

  // check inner list member's params
  list_members[1]
    .QueryInterface(Ci.nsISFVInnerList)
    .params.QueryInterface(Ci.nsISFVParams);

  // check serialization of list field
  let expected_serialized =
    "145454.146;param_1=*smth, (145454.146;param_1=*smth :d2VhdGhlcg==:;param_1)";
  let actual_serialized = list.serialize();
  Assert.equal(
    actual_serialized,
    expected_serialized,
    "serialized output must match expected one"
  );
});

add_task(async function test_sfv_dictionary() {
  // create primitives for dictionary field

  // dict member1
  let params1 = gService.newParameters();
  params1.set("mp_1", gService.newBool(true));
  params1.set("mp_2", gService.newDecimal(68.758602));
  let member1 = gService.newItem(gService.newString("member_1"), params1);

  // dict member2
  let params2 = gService.newParameters();
  let inner_item1 = gService.newItem(
    gService.newString("inner_item_1"),
    gService.newParameters()
  );
  let inner_item2 = gService.newItem(
    gService.newToken("tok"),
    gService.newParameters()
  );
  let member2 = gService.newInnerList([inner_item1, inner_item2], params2);

  // dict member3
  let params_3 = gService.newParameters();
  params_3.set("mp_1", gService.newInteger(6586));
  let member3 = gService.newItem(gService.newString("member_3"), params_3);

  // create dictionary field
  let dict = gService.newDictionary();
  dict.set("key_1", member1);
  dict.set("key_2", member2);
  dict.set("key_3", member3);

  // check dictionary keys
  let expected = ["key_1", "key_2", "key_3"];
  Assert.deepEqual(
    expected,
    dict.keys(),
    "check dictionary contains all the expected keys"
  );

  // check dictionary members
  Assert.throws(
    () => {
      dict.get("key_4");
    },
    /NS_ERROR_UNEXPECTED/,
    "must throw exception as key does not exist in dictionary"
  );

  // let dict_member1 = dict.get("key_1").QueryInterface(Ci.nsISFVItem);
  let dict_member2 = dict.get("key_2").QueryInterface(Ci.nsISFVInnerList);
  let dict_member3 = dict.get("key_3").QueryInterface(Ci.nsISFVItem);

  // Assert.equal(
  //   dict_member1.value.QueryInterface(Ci.nsISFVString).value,
  //   "member_1",
  //   "check dictionary member's value"
  // );
  // Assert.equal(
  //   dict_member1.params.get("mp_1").QueryInterface(Ci.nsISFVBool).value,
  //   true,
  //   "get dictionary member's parameter by key and check its value"
  // );
  // Assert.equal(
  //   dict_member1.params.get("mp_2").QueryInterface(Ci.nsISFVDecimal).value,
  //   "68.758602",
  //   "get dictionary member's parameter by key and check its value"
  // );

  let dict_member2_items = dict_member2.QueryInterface(
    Ci.nsISFVInnerList
  ).items;
  let dict_member2_params = dict_member2
    .QueryInterface(Ci.nsISFVInnerList)
    .params.QueryInterface(Ci.nsISFVParams);
  Assert.equal(
    dict_member2_items[0]
      .QueryInterface(Ci.nsISFVItem)
      .value.QueryInterface(Ci.nsISFVString).value,
    "inner_item_1",
    "get dictionary member of inner list type, and check inner list member's value"
  );
  Assert.equal(
    dict_member2_items[1]
      .QueryInterface(Ci.nsISFVItem)
      .value.QueryInterface(Ci.nsISFVToken).value,
    "tok",
    "get dictionary member of inner list type, and check inner list member's value"
  );
  Assert.throws(
    () => {
      dict_member2_params.get("some_param");
    },
    /NS_ERROR_UNEXPECTED/,
    "must throw exception as dict member's parameters are empty"
  );

  Assert.equal(
    dict_member3.value.QueryInterface(Ci.nsISFVString).value,
    "member_3",
    "check dictionary member's value"
  );
  Assert.equal(
    dict_member3.params.get("mp_1").QueryInterface(Ci.nsISFVInteger).value,
    6586,
    "get dictionary member's parameter by key and check its value"
  );

  // check serialization of Dictionary field
  let expected_serialized = `key_1="member_1";mp_1;mp_2=68.759, key_2=("inner_item_1" tok), key_3="member_3";mp_1=6586`;
  let actual_serialized = dict.serialize();
  Assert.equal(
    actual_serialized,
    expected_serialized,
    "serialized output must match expected one"
  );

  // check deleting dict member
  dict.delete("key_2");
  Assert.deepEqual(
    dict.keys(),
    ["key_1", "key_3"],
    "check that dictionary member has been deleted"
  );

  Assert.throws(
    () => {
      dict.delete("some_key");
    },
    /NS_ERROR_UNEXPECTED/,
    "must throw exception upon attempt to delete by non-existing key"
  );
});

add_task(async function test_sfv_item_parsing() {
  Assert.ok(gService.parseItem(`"str"`), "input must be parsed into Item");
  Assert.ok(gService.parseItem("12.35;a "), "input must be parsed into Item");
  Assert.ok(gService.parseItem("12.35;  a "), "input must be parsed into Item");
  Assert.ok(gService.parseItem("12.35    "), "input must be parsed into Item");

  Assert.throws(
    () => {
      gService.parseItem("12.35;\ta ");
    },
    /NS_ERROR_FAILURE/,
    "item parsing must fail: invalid parameters delimiter"
  );

  Assert.throws(
    () => {
      gService.parseItem("125666.3565648855455");
    },
    /NS_ERROR_FAILURE/,
    "item parsing must fail: decimal too long"
  );
});

add_task(async function test_sfv_list_parsing() {
  Assert.ok(
    gService.parseList(
      "(?1;param_1=*smth   :d2VhdGhlcg==:;param_1;param_2=145454);inner_param=:d2VpcmR0ZXN0cw==:"
    ),
    "input must be parsed into List"
  );
  Assert.ok("a, (b c)", "input must be parsed into List");

  Assert.throws(() => {
    gService.parseList("?tok", "list parsing must fail");
  }, /NS_ERROR_FAILURE/);

  Assert.throws(() => {
    gService.parseList(
      "a, (b, c)",
      "list parsing must fail: invalid delimiter within inner list"
    );
  }, /NS_ERROR_FAILURE/);

  Assert.throws(
    () => {
      gService.parseList("a, b c");
    },
    /NS_ERROR_FAILURE/,
    "list parsing must fail: invalid delimiter"
  );
});

add_task(async function test_sfv_dict_parsing() {
  Assert.ok(
    gService.parseDictionary(`abc=123;a=1;b=2, def=456, ghi=789;q=9;r="+w"`),
    "input must be parsed into Dictionary"
  );
  Assert.ok(
    gService.parseDictionary("a=1\t,\t\t\t   c=*"),
    "input must be parsed into Dictionary"
  );
  Assert.ok(
    gService.parseDictionary("a=1\t,\tc=*    \t\t"),
    "input must be parsed into Dictionary"
  );

  Assert.throws(
    () => {
      gService.parseDictionary("a=1\t,\tc=*,");
    },
    /NS_ERROR_FAILURE/,
    "dictionary parsing must fail: trailing comma"
  );

  Assert.throws(
    () => {
      gService.parseDictionary("a=1 c=*");
    },
    /NS_ERROR_FAILURE/,
    "dictionary parsing must fail: invalid delimiter"
  );

  Assert.throws(
    () => {
      gService.parseDictionary("INVALID_key=1, c=*");
    },
    /NS_ERROR_FAILURE/,
    "dictionary parsing must fail: invalid key format, can't be in uppercase"
  );
});

add_task(async function test_sfv_list_parse_serialize() {
  let list_field = gService.parseList("1  ,  42, (42 43)");
  Assert.equal(
    list_field.serialize(),
    "1, 42, (42 43)",
    "serialized output must match expected one"
  );

  // create new inner list with parameters
  function params() {
    let inner_list_params = gService.newParameters();
    inner_list_params.set("key1", gService.newString("value1"));
    inner_list_params.set("key2", gService.newBool(true));
    inner_list_params.set("key3", gService.newBool(false));
    return inner_list_params;
  }

  function changeMembers() {
    // set one of list members to inner list and check it's serialized as expected
    let members = list_field.members;
    members[1] = gService.newInnerList(
      [
        gService.newItem(
          gService.newDecimal(-1865.75653),
          gService.newParameters()
        ),
        gService.newItem(gService.newToken("token"), gService.newParameters()),
        gService.newItem(
          gService.newString(`no"yes`),
          gService.newParameters()
        ),
      ],
      params()
    );
    return members;
  }

  list_field.members = changeMembers();

  Assert.equal(
    list_field.serialize(),
    `1, (-1865.757 token "no\\"yes");key1="value1";key2;key3=?0, (42 43)`,
    "update list member and check list is serialized as expected"
  );
});

add_task(async function test_sfv_dict_parse_serialize() {
  let dict_field = gService.parseDictionary(
    "a=1,     b; foo=*, \tc=3, \t     \tabc=123;a=1;b=2\t"
  );
  Assert.equal(
    dict_field.serialize(),
    "a=1, b;foo=*, c=3, abc=123;a=1;b=2",
    "serialized output must match expected one"
  );

  // set new value for existing dict's key
  dict_field.set(
    "a",
    gService.newItem(gService.newInteger(165), gService.newParameters())
  );

  // add new member to dict
  dict_field.set(
    "key",
    gService.newItem(gService.newDecimal(45.0), gService.newParameters())
  );

  // check dict is serialized properly after the above changes
  Assert.equal(
    dict_field.serialize(),
    "a=165, b;foo=*, c=3, abc=123;a=1;b=2, key=45.0",
    "update dictionary members and dictionary list is serialized as expected"
  );
});

add_task(async function test_sfv_list_parse_more() {
  // check parsing of multiline header of List type
  let list_field = gService.parseList("(12 abc), 12.456\t\t   ");
  list_field.parseMore("11, 15, tok");
  Assert.equal(
    list_field.serialize(),
    "(12 abc), 12.456, 11, 15, tok",
    "multi-line field value parsed and serialized successfully"
  );

  // should fail parsing one more line
  Assert.throws(
    () => {
      list_field.parseMore("(tk\t1)");
    },
    /NS_ERROR_FAILURE/,
    "line parsing must fail: invalid delimiter in inner list"
  );
  Assert.equal(
    list_field.serialize(),
    "(12 abc), 12.456, 11, 15, tok",
    "parsed value must not change if parsing one more line of header fails"
  );
});

add_task(async function test_sfv_dict_parse_more() {
  // check parsing of multiline header of Dictionary type
  let dict_field = gService.parseDictionary("");
  dict_field.parseMore("key2=?0, key3=?1, key4=itm");
  dict_field.parseMore("key1,    key5=11, key4=45");

  Assert.equal(
    dict_field.serialize(),
    "key2=?0, key3, key4=45, key1, key5=11",
    "multi-line field value parsed and serialized successfully"
  );

  // should fail parsing one more line
  Assert.throws(
    () => {
      dict_field.parseMore("c=12, _k=13");
    },
    /NS_ERROR_FAILURE/,
    "line parsing must fail: invalid key format"
  );
});