import Whitespace from "../lib/whitespace";
import Printer from "../lib/printer";
import generate from "../lib";
import assert from "assert";
import { parse } from "babylon";
import chai from "chai";
import * as t from "babel-types";
import fs from "fs";
import path from "path";
import fixtures from "babel-helper-fixtures";

describe("generation", function () {
  it("completeness", function () {
    Object.keys(t.VISITOR_KEYS).forEach(function (type) {
      assert.ok(!!Printer.prototype[type], type + " should exist");
    });

    Object.keys(Printer.prototype).forEach(function (type) {
      if (!/[A-Z]/.test(type[0])) return;
      assert.ok(t.VISITOR_KEYS[type], type + " should not exist");
    });
  });

  it("multiple sources", function () {
    const sources = {
      "a.js": "function hi (msg) { console.log(msg); }\n",
      "b.js": "hi('hello');\n"
    };
    const parsed = Object.keys(sources).reduce(function (_parsed, filename) {
      _parsed[filename] = parse(sources[filename], { sourceFilename: filename });
      return _parsed;
    }, {});

    const combinedAst = {
      "type": "File",
      "program": {
        "type": "Program",
        "sourceType": "module",
        "body": [].concat(parsed["a.js"].program.body, parsed["b.js"].program.body)
      }
    };

    const generated = generate(combinedAst, { sourceMaps: true }, sources);

    chai.expect(generated.map).to.deep.equal({
      version: 3,
      sources: [ "a.js", "b.js" ],
      mappings: "AAAA,SAASA,EAAT,CAAaC,GAAb,EAAkB;AAAEC,UAAQC,GAAR,CAAYF,GAAZ;AAAmB;;ACAvCD,GAAG,OAAH",
      names: [
        "hi",
        "msg",
        "console",
        "log",
      ],
      sourcesContent: [
        "function hi (msg) { console.log(msg); }\n",
        "hi('hello');\n"
      ]
    }, "sourcemap was incorrectly generated");

    chai.expect(generated.rawMappings).to.deep.equal([
      { name: undefined,
        generated: { line: 1, column: 0 },
        source: "a.js",
        original: { line: 1, column: 0 } },
      { name: "hi",
        generated: { line: 1, column: 9 },
        source: "a.js",
        original: { line: 1, column: 9 } },
      { name: undefined,
        generated: { line: 1, column: 11 },
        source: "a.js",
        original: { line: 1, column: 0 } },
      { name: "msg",
        generated: { line: 1, column: 12 },
        source: "a.js",
        original: { line: 1, column: 13 } },
      { name: undefined,
        generated: { line: 1, column: 15 },
        source: "a.js",
        original: { line: 1, column: 0 } },
      { name: undefined,
        generated: { line: 1, column: 17 },
        source: "a.js",
        original: { line: 1, column: 18 } },
      { name: "console",
        generated: { line: 2, column: 0 },
        source: "a.js",
        original: { line: 1, column: 20 } },
      { name: "log",
        generated: { line: 2, column: 10 },
        source: "a.js",
        original: { line: 1, column: 28 } },
      { name: undefined,
        generated: { line: 2, column: 13 },
        source: "a.js",
        original: { line: 1, column: 20 } },
      { name: "msg",
        generated: { line: 2, column: 14 },
        source: "a.js",
        original: { line: 1, column: 32 } },
      { name: undefined,
        generated: { line: 2, column: 17 },
        source: "a.js",
        original: { line: 1, column: 20 } },
      { name: undefined,
        generated: { line: 3, column: 0 },
        source: "a.js",
        original: { line: 1, column: 39 } },
      { name: "hi",
        generated: { line: 5, column: 0 },
        source: "b.js",
        original: { line: 1, column: 0 } },
      { name: undefined,
        generated: { line: 5, column: 3 },
        source: "b.js",
        original: { line: 1, column: 3 } },
      { name: undefined,
        generated: { line: 5, column: 10 },
        source: "b.js",
        original: { line: 1, column: 0 } },
    ], "raw mappings were incorrectly generated");

    chai.expect(generated.code).to.equal(
      "function hi(msg) {\n  console.log(msg);\n}\n\nhi('hello');",
      "code was incorrectly generated"
    );
  });

  it("identifierName", function () {
    const code = "function foo() { bar; }\n";

    const ast = parse(code, { filename: "inline" }).program;
    const fn = ast.body[0];

    const id = fn.id;
    id.name += "2";
    id.loc.identifierName = "foo";

    const id2 = fn.body.body[0].expression;
    id2.name += "2";
    id2.loc.identiferName = "bar";

    const generated = generate(ast, {
      filename: "inline",
      sourceFileName: "inline",
      sourceMaps: true
    }, code);

    chai.expect(generated.map).to.deep.equal({
      version: 3,
      sources: ["inline"],
      names: ["foo", "bar" ],
      mappings: "AAAA,SAASA,IAAT,GAAe;AAAEC;AAAM",
      sourcesContent: [ "function foo() { bar; }\n" ]
    }, "sourcemap was incorrectly generated");

    chai.expect(generated.rawMappings).to.deep.equal([
      { name: undefined,
        generated: { line: 1, column: 0 },
        source: "inline",
        original: { line: 1, column: 0 } },
      { name: "foo",
        generated: { line: 1, column: 9 },
        source: "inline",
        original: { line: 1, column: 9 } },
      { name: undefined,
        generated: { line: 1, column: 13 },
        source: "inline",
        original: { line: 1, column: 0 } },
      { name: undefined,
        generated: { line: 1, column: 16 },
        source: "inline",
        original: { line: 1, column: 15 } },
      { name: "bar",
        generated: { line: 2, column: 0 },
        source: "inline",
        original: { line: 1, column: 17 } },
      { name: undefined,
        generated: { line: 3, column: 0 },
        source: "inline",
        original: { line: 1, column: 23 } },
    ], "raw mappings were incorrectly generated");

    chai.expect(generated.code).to.equal(
      "function foo2() {\n  bar2;\n}",
      "code was incorrectly generated"
    );
  });

  it("lazy source map generation", function() {
    const code = "function hi (msg) { console.log(msg); }\n";

    const ast = parse(code, { filename: "a.js" }).program;
    const generated = generate(ast, {
      sourceFileName: "a.js",
      sourceMaps: true,
    });

    chai.expect(generated.rawMappings).to.be.an("array");

    chai.expect(generated).ownPropertyDescriptor("map").not.to.have.property("value");

    chai.expect(generated.map).to.be.an("object");
  });
});


describe("programmatic generation", function() {
  it("numeric member expression", function() {
    // Should not generate `0.foo`
    const mem = t.memberExpression(t.numericLiteral(60702), t.identifier("foo"));
    new Function(generate(mem).code);
  });

  it("nested if statements needs block", function() {
    const ifStatement = t.ifStatement(
      t.stringLiteral("top cond"),
      t.whileStatement(
        t.stringLiteral("while cond"),
        t.ifStatement(
          t.stringLiteral("nested"),
          t.expressionStatement(t.numericLiteral(1))
        )
      ),
      t.expressionStatement(t.stringLiteral("alt"))
    );

    const ast = parse(generate(ifStatement).code);
    assert.equal(ast.program.body[0].consequent.type, "BlockStatement");
  });

  it("prints directives in block with empty body", function() {
    const blockStatement = t.blockStatement(
      [],
      [t.directive(t.directiveLiteral("use strict"))]
    );

    const output = generate(blockStatement).code;
    assert.equal(output, [
      "{",
      "  \"use strict\";",
      "}"
    ].join("\n"));
  });

  it("flow object indentation", function() {
    const objectStatement = t.objectTypeAnnotation(
      [
        t.objectTypeProperty(
          t.identifier("bar"),
          t.stringTypeAnnotation()
        ),
      ],
      null,
      null
    );

    const output = generate(objectStatement).code;
    assert.equal(output, [
      "{",
      "  bar: string;",
      "}",
    ].join("\n"));
  });

  it("flow object indentation with empty leading ObjectTypeProperty", function() {
    const objectStatement = t.objectTypeAnnotation(
      [],
      [
        t.objectTypeIndexer(
          t.identifier("key"),
          t.anyTypeAnnotation(),
          t.identifier("Test"),
        ),
      ]
    );

    const output = generate(objectStatement).code;

    assert.equal(output, [
      "{",
      "  [key: any]: Test;",
      "}",
    ].join("\n"));
  });
});

describe("whitespace", function () {
  it("empty token list", function () {
    const w = new Whitespace([]);
    assert.equal(w.getNewlinesBefore(t.stringLiteral("1")), 0);
  });
});

const suites = fixtures(`${__dirname}/fixtures`);

suites.forEach(function (testSuite) {
  describe("generation/" + testSuite.title, function () {
    testSuite.tests.forEach(function (task) {
      it(task.title, !task.disabled && function () {
        const expect = task.expect;
        const actual = task.actual;
        const actualCode = actual.code;

        if (actualCode) {
          const actualAst = parse(actualCode, {
            filename: actual.loc,
            plugins: ["*"],
            strictMode: false,
            sourceType: "module",
          });
          const result = generate(actualAst, task.options, actualCode);

          if (
            !expect.code && result.code && fs.statSync(path.dirname(expect.loc)).isDirectory() &&
            !process.env.CI
          ) {
            console.log(`New test file created: ${expect.loc}`);
            fs.writeFileSync(expect.loc, result.code);
          } else {
            chai.expect(result.code).to.be.equal(expect.code, actual.loc + " !== " + expect.loc);
          }
        }
      });
    });
  });
});
