import { getUrlFromNode, getAccessionFromNode, getBranchMutations } from "../src/util/treeMiscHelpers";
import { treeJsonToState } from "../src/util/treeJsonProcessing";
import { parseIntervalsOfNsOrGaps } from "../src/components/tree/infoPanels/MutationTable";

/**
 * `dummyTree` is a simple tree with three tips: tipX-Z
 * root to tipX mutations:
 *      single mutation at position 100 in gene "GENE" of A->B
 *      a reversion of C->D->C at position 200
 *      multiple mutations at 300 E->F->G (F300G is a homoplasy)
 * root to tipY mutations:
 *      ["A100B", "C200D", "E300F"]
 * root to tipZ mutations:
 *      the three root mutations + F300G (homoplasy)
 */
const dummyTree = treeJsonToState({
  name: "ROOT",
  children: [
    {
      name: "node1",
      branch_attrs: {mutations: {GENE: ["A100B", "C200D", "E300F"]}},
      children: [
        { // start 1st child of node1
          name: "node1.1",
          branch_attrs: {mutations: {GENE: ["D200C", "F300G"]}},
          children: [
            {
              name: "tipX"
            }
          ]
        },
        { // start 2nd child of node1
          name: "tipY"
        },
        { // 3rd child of node1
          name: "tipZ",
          branch_attrs: {mutations: {GENE: ["F300G", "B100A"]}}
        }
      ]
    }
  ]
});

describe('Parse and summarise mutations', () => {
  test("Collection of all mutations", () => {
    // note that the function we are testing, collectObservedMutations,
    // is part of treeJsonToState so we are testing it indirectly
    expect(dummyTree.observedMutations)
      .toEqual({ // exactly equal all Obj keys and values
        "GENE:A100B": 1,
        "GENE:C200D": 1,
        "GENE:E300F": 1,
        "GENE:D200C": 1,
        "GENE:F300G": 2,
        "GENE:B100A": 1,
      });
  });

  test("Branch mutations are correctly interpreted", () => {
    const node_1 = getBranchMutations(
      getNodeByName(dummyTree.nodes, "node1"),
      dummyTree.observedMutations
    );
    expect(node_1).toEqual({
      GENE: {
        unique: ["A100B", "C200D", "E300F"],
        homoplasies: [],
        gaps: [],
        reversionsToRoot: []
      }
    });
    const node_1_1 = getBranchMutations(
      getNodeByName(dummyTree.nodes, "node1.1"),
      dummyTree.observedMutations
    );
    expect(node_1_1).toEqual({
      GENE: {
        unique: ["D200C"],
        homoplasies: ["F300G"],
        gaps: [],
        reversionsToRoot: ["D200C"]
      }
    });
  });
});


function getNodeByName(tree, name) {
  let namedNode;
  const recurse = (node) => {
    if (node.name === name) namedNode = node;
    else if (node.children) node.children.forEach((n) => recurse(n));
  };
  recurse(tree[0]);
  return namedNode;
}

describe('Extract various values from node_attrs', () => {
  beforeEach(() => {
    jest.spyOn(console, 'warn').mockImplementation(() => {});
  });

  // the following test also covers `validateUrl`
  test("getUrlFromNode correctly handles various URLs", () => {
    expect(getUrlFromNode({}, "trait")).toEqual(undefined); // no node_attrs on node
    expect(getUrlFromNode({node_attrs: {}}, "trait")).toEqual(undefined); // no "trait" defined in node_attrs
    expect(getUrlFromNode({node_attrs: {trait: "str_value"}}, "trait")).toEqual(undefined); // incorrectly formatted "trait"
    expect(getUrlFromNode({node_attrs: {trait: {}}}, "trait")).toEqual(undefined); // correctly formatted "trait", no URL
    expect(getUrlFromNode({node_attrs: {trait: {url: 1234}}}, "trait")).toEqual(undefined); // invalid URL
    expect(getUrlFromNode({node_attrs: {trait: {url: "bad url"}}}, "trait")).toEqual(undefined); // invalid URL
    expect(getUrlFromNode({node_attrs: {trait: {url: "https://nextstrain.org"}}}, "trait")).toEqual("https://nextstrain.org/");
    expect(getUrlFromNode({node_attrs: {trait: {url: "http://nextstrain.org"}}}, "trait")).toEqual("http://nextstrain.org/");
    expect(getUrlFromNode({node_attrs: {trait: {url: "https_//nextstrain.org"}}}, "trait")).toEqual("https://nextstrain.org/"); // see code for details
    expect(getUrlFromNode({node_attrs: {trait: {url: "http_//nextstrain.org"}}}, "trait")).toEqual("http://nextstrain.org/"); // see code for details
  });

  test("extract accession & corresponding URL from a node", () => {
    expect(getAccessionFromNode({}))
      .toStrictEqual({accession: undefined, url: undefined});  // no node_attrs on node
    expect(getAccessionFromNode({node_attrs: {}}))
      .toStrictEqual({accession: undefined, url: undefined}); // no "accession" on node_attrs
    expect(getAccessionFromNode({node_attrs: {accession: "MK049251", url: "https://www.ncbi.nlm.nih.gov/nuccore/MK049251"}}))
      .toStrictEqual({accession: "MK049251", url: "https://www.ncbi.nlm.nih.gov/nuccore/MK049251"});
    expect(getAccessionFromNode({node_attrs: {url: "https://www.ncbi.nlm.nih.gov/nuccore/MK049251"}}))
      .toStrictEqual({accession: undefined, url: "https://www.ncbi.nlm.nih.gov/nuccore/MK049251"}); // url can be defined without accession
    expect(getAccessionFromNode({node_attrs: {accession: "MK049251", url: "nuccore/MK049251"}}))
      .toStrictEqual({accession: "MK049251", url: undefined}); // invalid URL
  });

});

test("Parse intervals of gaps or Ns", () => {
  expect(parseIntervalsOfNsOrGaps(["T200N", "T100N", "C101N", "G102N"]))
    .toStrictEqual(
      [{start: 100, end: 102, count: 3, char: 'N'}, {start: 200, end: 200, count: 1, char: 'N'}]
    );
  expect(parseIntervalsOfNsOrGaps(["T5N", "T3N", "C1N", "G7N"]))
    .toStrictEqual(
      [{start: 1, end: 1, count: 1, char: 'N'}, {start: 3, end: 3, count: 1, char: 'N'}, {start: 5, end: 5, count: 1, char: 'N'}, {start: 7, end: 7, count: 1, char: 'N'}]
    );
  expect(parseIntervalsOfNsOrGaps(["T5-", "T3-", "C4-", "G7-", "G8-"]))
    .toStrictEqual(
      [{start: 3, end: 5, count: 3, char: '-'}, {start: 7, end: 8, count: 2, char: '-'}]
    );
});
