import { readTestProgram, executeProgram } from "../../test-utils";
import { Bfg93Direction } from "../constants";
import Engine from "../runtime";

/**
 * All test programs are picked up from https://esolangs.org/wiki/Befunge,
 * except the modifications mentioned alongside each test.
 */

/** Relative path to directory of sample programs */
const DIRNAME = __dirname + "/samples";

describe("Test programs", () => {
  // Standard hello-world program
  test("hello world", async () => {
    const code = readTestProgram(DIRNAME, "helloworld");
    const result = await executeProgram(new Engine(), code);
    expect(result.output.charCodeAt(result.output.length - 1)).toBe(0);
    expect(result.output.slice(0, -1)).toBe("Hello, World!");
    expect(result.rendererState.direction).toBe(Bfg93Direction.DOWN);
    expect(result.rendererState.stack.length).toBe(0);
  });

  // cat program
  test("cat program", async () => {
    const input = "abcd efgh\nijkl mnop\n";
    const code = readTestProgram(DIRNAME, "cat");
    const result = await executeProgram(new Engine(), code, input);
    expect(result.output).toBe(input);
    expect(result.rendererState.direction).toBe(Bfg93Direction.LEFT);
    expect(result.rendererState.stack).toEqual([-1]);
  });

  // Random DNA printer
  test("random DNA", async () => {
    const code = readTestProgram(DIRNAME, "dna");
    const result = await executeProgram(new Engine(), code);
    // program prints "\r\n" at the end of output
    expect(result.output.length).toBe(56 + 2);
    expect(result.output.trim().search(/[^ATGC]/)).toBe(-1);
    expect(result.rendererState.direction).toBe(Bfg93Direction.RIGHT);
    expect(result.rendererState.stack).toEqual([0]);
  });

  // Factorial program
  test("factorial", async () => {
    const code = readTestProgram(DIRNAME, "factorial");
    const result = await executeProgram(new Engine(), code, "5");
    expect(result.output).toBe("120 ");
    expect(result.rendererState.direction).toBe(Bfg93Direction.RIGHT);
    expect(result.rendererState.stack.length).toBe(0);
  });

  // Sieve of Eratosthenes - prints prime nums upto 36
  // (original prints up to 80, shortened here for testing purposes)
  test("sieve of eratosthenes", async () => {
    const code = readTestProgram(DIRNAME, "prime-sieve");
    const result = await executeProgram(new Engine(), code);
    const outputNums = result.output
      .trim()
      .split(" ")
      .map((a) => parseInt(a, 10));
    const primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
    expect(outputNums).toEqual(primes);
    expect(result.rendererState.direction).toBe(Bfg93Direction.DOWN);
    expect(result.rendererState.stack).toEqual([37]);
  });

  // Quine 1 - simple single-line quine
  test("simple singleline quine", async () => {
    const code = readTestProgram(DIRNAME, "quine1");
    const result = await executeProgram(new Engine(), code);
    expect(result.output).toBe(code);
    expect(result.rendererState.direction).toBe(Bfg93Direction.RIGHT);
    expect(result.rendererState.stack).toEqual([44]);
  });

  // Quine 2 - multiline quine
  test("multiline quine", async () => {
    const code = readTestProgram(DIRNAME, "quine2");
    const result = await executeProgram(new Engine(), code);
    // Output has an extra space at the end - verified on tio.run
    expect(result.output).toBe(code + " ");
    expect(result.rendererState.direction).toBe(Bfg93Direction.LEFT);
    expect(result.rendererState.stack).toEqual([0]);
  });

  // Quine 3 - quine without using "g"
  test("quine without using 'g'", async () => {
    const code = readTestProgram(DIRNAME, "quine3");
    const result = await executeProgram(new Engine(), code);
    expect(result.output).toBe(code);
    expect(result.rendererState.direction).toBe(Bfg93Direction.LEFT);
    expect(result.rendererState.stack).toEqual([0]);
  });
});