/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * 
 */
'use strict';






const JasmineReporter = require('./reporter');

const jasmineAsync = require('./jasmine-async');
const snapshot = require('jest-snapshot');
const fs = require('graceful-fs');
const path = require('path');
const vm = require('vm');

const CALL_PRINT_LIMIT = 3;
const LAST_CALL_PRINT_LIMIT = 1;
const JASMINE_PATH = require.resolve('../vendor/jasmine-2.4.1.js');
const JASMINE_CHECK_PATH = require.resolve('./jasmine-check');

const jasmineScript = new vm.Script(fs.readFileSync(JASMINE_PATH, 'utf8'), {
  displayErrors: true,
  filename: JASMINE_PATH });


function isSpyLike(test) {
  return test.calls && test.calls.all !== undefined;
}

function isMockLike(test) {
  return test.mock !== undefined;
}

function getActualCalls(reporter, calls, limit) {
  const count = calls.length - limit;
  return (
    `\nActual call${ calls.length === 1 ? '' : 's' }:\n` +
    calls.slice(-limit).map(
    call => reporter.getFormatter().prettyPrint(call)).
    reverse().join(',\n') + (
    count > 0 ?
    `\nand ${ count } other call${ count === 1 ? '' : 's' }.` : ''));


}

function jasmine2(
config,
environment,
runtime,
testPath)
{
  const reporter = new JasmineReporter(config, environment, testPath);
  environment.runScript(jasmineScript);

  const requireJasmine = environment.global.jasmineRequire;
  const jasmine = requireJasmine.core(requireJasmine);

  const jasmineBuildExpectationResult = jasmine.buildExpectationResult;

  // https://github.com/facebook/jest/issues/429
  jasmine.buildExpectationResult = function (options) {
    if (!options.passed) {
      function shallowCopy(object) {
        if (
        typeof object !== 'object' ||
        object === null ||
        environment.global.Node &&
        object instanceof environment.global.Node &&
        object.nodeType > 0)

        {
          return object;
        }
        return jasmine.util.clone(object);
      }
      options.expected = shallowCopy(options.expected);
      options.actual = shallowCopy(options.actual);
    }

    return jasmineBuildExpectationResult.apply(jasmine, arguments);
  };

  const env = jasmine.getEnv();
  const jasmineInterface = requireJasmine.interface(jasmine, env);
  Object.assign(environment.global, jasmineInterface);
  env.addReporter(jasmineInterface.jsApiReporter);

  jasmineAsync.install(environment.global);
  environment.global.test = environment.global.it;

  if (config.setupTestFrameworkScriptFile) {
    runtime.requireModule(config.setupTestFrameworkScriptFile);
  }

  if (!jasmine || !env) {
    throw new Error('jasmine2 could not be initialized by Jest');
  }

  runtime.setMock(
  '',
  'jest-check',
  () => {
    const jasmineCheck = runtime.requireInternalModule(JASMINE_CHECK_PATH);
    return jasmineCheck(environment.global, config.testcheckOptions);
  },
  { virtual: true });


  env.beforeEach(() => {
    jasmine.addMatchers({
      toMatchSnapshot: snapshot.matcher(
      testPath,
      config,
      snapshotState) });



    jasmine.addMatchers({
      lastCalledWith: util => ({
        compare(actual) {
          const isSpy = isSpyLike(actual);
          if (!isSpy && !isMockLike(actual)) {
            throw Error(
            'lastCalledWith() should be used on a mock function or ' +
            'a jasmine spy.');

          }
          const calls = isSpy ?
          actual.calls.all().map(x => x.args) :
          actual.mock.calls;
          const expected = Array.prototype.slice.call(arguments, 1);
          const pass = util.equals(calls[calls.length - 1], expected);
          if (!pass) {
            return {
              pass,
              // $FlowFixMe - get/set properties not yet supported
              get message() {
                return (
                  `Wasn't last called with the expected values.\n` +
                  'Expected call:\n' +
                  reporter.getFormatter().prettyPrint(expected) +
                  getActualCalls(reporter, calls, LAST_CALL_PRINT_LIMIT));

              } };

          }
          return {
            pass,
            // $FlowFixMe - get/set properties not yet supported
            get message() {
              return (
                `Shouldn't have been last called with\n` +
                reporter.getFormatter().prettyPrint(expected));

            } };


        } }),


      toBeCalledWith: util => ({
        compare(actual) {
          const isSpy = isSpyLike(actual);
          if (!isMockLike(actual) && !isSpy) {
            throw Error(
            'toBeCalledWith() should be used on a mock function or ' +
            'a jasmine spy.');

          }
          const calls = isSpy ?
          actual.calls.all().map(x => x.args) :
          actual.mock.calls;
          const expected = Array.prototype.slice.call(arguments, 1);
          const pass = calls.some(call => util.equals(call, expected));
          if (!pass) {
            return {
              pass,
              // $FlowFixMe - get/set properties not yet supported
              get message() {
                return (
                  'Was not called with the expected values.\n' +
                  'Expected call:\n' +
                  reporter.getFormatter().prettyPrint(expected) +
                  getActualCalls(reporter, calls, CALL_PRINT_LIMIT));

              } };

          }
          return {
            pass,
            // $FlowFixMe - get/set properties not yet supported
            get message() {
              return (
                `Shouldn't have been called with\n` +
                reporter.getFormatter().prettyPrint(expected));

            } };

        } }) });



    if (config.resetModules) {
      runtime.resetModules();
    }
  });

  const snapshotState = snapshot.getSnapshotState(jasmine, testPath);

  env.addReporter(reporter);

  // `jest-matchers` should be required inside test environment (vm).
  // Otherwise if they throw, the `Error` class will differ from the `Error`
  // class of the test and `error instanceof Error` will return `false`.
  runtime.requireInternalModule(
  path.resolve(__dirname, './extendJasmineExpect.js'));


  if (config.timers === 'fake') {
    environment.fakeTimers.useFakeTimers();
  }

  runtime.requireModule(testPath);
  env.execute();
  return reporter.getResults().then(results => {
    const currentSnapshot = snapshotState.snapshot;
    const updateSnapshot = config.updateSnapshot;
    const uncheckedCount = currentSnapshot.getUncheckedCount();
    if (updateSnapshot) {
      currentSnapshot.removeUncheckedKeys();
    }
    const status = currentSnapshot.save(updateSnapshot);

    results.snapshot.fileDeleted = status.deleted;
    results.snapshot.added = snapshotState.added;
    results.snapshot.matched = snapshotState.matched;
    results.snapshot.unmatched = snapshotState.unmatched;
    results.snapshot.updated = snapshotState.updated;
    results.snapshot.unchecked = !status.deleted ? uncheckedCount : 0;
    return results;
  });
}

module.exports = jasmine2;