import { Cell } from "../cell";
import { ProgramBuilder } from "../program-builder";
import { TestCell } from "./testcell";

describe("program builder", () => {
  function createCell(
    executionEventId: string,
    text: string,
    executionCount?: number
  ): Cell {
    return new TestCell(text, executionCount, executionEventId);
  }

  let programBuilder: ProgramBuilder;
  beforeEach(() => {
    programBuilder = new ProgramBuilder();
  });

  it("appends cell contents in order", () => {
    programBuilder.add(
      createCell("id1", "print(1)"),
      createCell("id2", "print(2)")
    );
    let code = programBuilder.buildTo("id2").text;
    expect(code).toBe(["print(1)", "print(2)", ""].join("\n"));
  });

  it("builds a map from lines to cells", () => {
    let cell1 = createCell("id1", "print(1)");
    let cell2 = createCell("id2", "print(2)");
    programBuilder.add(cell1, cell2);
    let lineToCellMap = programBuilder.buildTo("id2").lineToCellMap;
    expect(lineToCellMap[1]).toEqual(cell1);
    expect(lineToCellMap[2]).toEqual(cell2);
  });

  it("builds a map from cells to lines", () => {
    let cell1 = createCell("id1", "print(1)");
    let cell2 = createCell("id2", "print(2)");
    programBuilder.add(cell1, cell2);
    let cellToLineMap = programBuilder.buildTo("id2").cellToLineMap;
    expect(cellToLineMap["id1"].items).toEqual([1]);
    expect(cellToLineMap["id2"].items).toEqual([2]);
  });

  it("stops after the specified cell", () => {
    programBuilder.add(
      createCell("id1", "print(1)"),
      createCell("id2", "print(2)")
    );
    let code = programBuilder.buildTo("id1").text;
    expect(code).toBe("print(1)\n");
  });

  /* We might want the program builder to include code that was executed before a runtime
   * error, though this will probably require us to rewrite the code. */
  it("skips cells with errors", () => {
    let badCell = createCell("idE", "print(2)");
    badCell.hasError = true;
    programBuilder.add(
      createCell("id1", "print(1)"),
      badCell,
      createCell("id3", "print(3)")
    );
    let code = programBuilder.buildTo("id3").text;
    expect(code).toBe(["print(1)", "print(3)", ""].join("\n"));
  });

  it("includes cells that end with errors", () => {
    let badCell = createCell("idE", "print(bad_name)");
    badCell.hasError = true;
    programBuilder.add(
      createCell("id1", "print(1)"),
      createCell("id2", "print(2)"),
      badCell
    );
    let code = programBuilder.buildTo("idE").text;
    expect(code).toBe(
      ["print(1)", "print(2)", "print(bad_name)", ""].join("\n")
    );
  });

  /* Sometimes, a cell might not throw an error, but our parser might choke. This shouldn't
   * crash the entire program---just skip it if it can't parse. */
  it("skips cells that fail to parse", () => {
    let badCell = createCell("idE", "causes_syntax_error(");

    // Hide console output from parse errors.
    let oldConsoleLog = console.log;
    console.log = () => {};

    programBuilder.add(
      createCell("id1", "print(1)"),
      badCell,
      createCell("id3", "print(3)")
    );

    // Restore console output.
    console.log = oldConsoleLog;

    let code = programBuilder.buildTo("id3").text;
    expect(code).toBe(["print(1)", "print(3)", ""].join("\n"));
  });

  it("doesn't skip cells with array slices", () => {
    programBuilder.add(
      createCell("id1", "array[0:1]"),
      createCell("id2", "print(x)")
    );
    let code = programBuilder.buildTo("id2").text;
    expect(code).toBe("array[0:1]\nprint(x)\n");
  });

  it("skips cells that were executed in prior kernels", () => {
    programBuilder.add(
      createCell("id1", "print(1)", 1),
      createCell("id2", "print(2)", 1),
      createCell("id3", "print(3)", 2),
      createCell("id3", "print(4)", 1)
    );
    let code = programBuilder.buildTo("id3").text;
    expect(code).toBe(["print(4)", ""].join("\n"));
  });

  it("constructs a tree for the program", () => {
    programBuilder.add(
      createCell("id1", "print(1)"),
      createCell("id2", "print(2)")
    );
    let tree = programBuilder.buildTo("id2").tree;
    expect(tree.code).toHaveLength(2);
  });

  it("adjusts the node locations", () => {
    programBuilder.add(
      createCell("id1", "print(1)"),
      createCell("id2", "print(2)")
    );
    let tree = programBuilder.buildTo("id2").tree;
    expect(tree.code[0].location.first_line).toBe(1);
    expect(tree.code[1].location.first_line).toBe(2);
  });

  it("annotates tree nodes with cell ID info", () => {
    programBuilder.add(createCell("id1", "print(1)"));
    let tree = programBuilder.buildTo("id1").tree;
    expect(tree.code[0].location.path).toBe("id1");
  });
});
