// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Common test utilities.

/**
 * Allows console.log output.
 */
var showConsoleLogOutput = false;

/**
 * Conditionally allow console.log output based off of showConsoleLogOutput.
 */
console.log = function() {
  var originalConsoleLog = console.log;
  return function() {
    if (showConsoleLogOutput) {
      originalConsoleLog.apply(console, arguments);
    }
  };
}();

function emptyMock() {}

// Container for event handlers added by mocked 'addListener' functions.
var mockEventHandlers = {};

/**
 * Mocks 'addListener' function of an API event. The mocked function will keep
 * track of handlers.
 * @param {Object} topLevelContainer Top-level container of the original
 *     function. Can be either 'chrome' or 'instrumented'.
 * @param {string} eventIdentifier Event identifier, such as
 *     'runtime.onSuspend'.
 */
function mockChromeEvent(topLevelContainer, eventIdentifier) {
  var eventIdentifierParts = eventIdentifier.split('.');
  var eventName = eventIdentifierParts.pop();
  var originalMethodContainer = topLevelContainer;
  var mockEventContainer = mockEventHandlers;
  eventIdentifierParts.forEach(function(fragment) {
    originalMethodContainer =
        originalMethodContainer[fragment] =
        originalMethodContainer[fragment] || {};
    mockEventContainer =
        mockEventContainer[fragment] =
        mockEventContainer[fragment] || {};
  });

  mockEventContainer[eventName] = [];
  originalMethodContainer[eventName] = {
    addListener: function(callback) {
      mockEventContainer[eventName].push(callback);
    }
  };
}

/**
 * Gets the array of event handlers added by a mocked 'addListener' function.
 * @param {string} eventIdentifier Event identifier, such as
 *     'runtime.onSuspend'.
 * @return {Array<Function>} Array of handlers.
 */
function getMockHandlerContainer(eventIdentifier) {
  var eventIdentifierParts = eventIdentifier.split('.');
  var mockEventContainer = mockEventHandlers;
  eventIdentifierParts.forEach(function(fragment) {
    mockEventContainer = mockEventContainer[fragment];
  });

  return mockEventContainer;
}

/**
 * MockPromise
 * The JS test harness expects all calls to complete synchronously.
 * As a result, we can't use built-in JS promises since they run asynchronously.
 * Instead of mocking all possible calls to promises, a skeleton
 * implementation is provided to get the tests to pass.
 *
 * This functionality and logic originates from ECMAScript 6's spec of promises.
 */
var Promise = function() {
  function PromisePrototypeObject(asyncTask) {
    function isThenable(value) {
      return (typeof value === 'object') && isCallable(value.then);
    }

    function isCallable(value) {
      return typeof value === 'function';
    }

    function callResolveRejectFunc(func) {
      var funcResult;
      var funcResolved = false;
      func(
          function(resolveResult) {
            funcResult = resolveResult;
            funcResolved = true;
          },
          function(rejectResult) {
            funcResult = rejectResult;
            funcResolved = false;
          });
      return { result: funcResult, resolved: funcResolved };
    }

    function then(onResolve, onReject) {
      var resolutionHandler =
          isCallable(onResolve) ? onResolve : function() { return result; };
      var rejectionHandler =
          isCallable(onReject) ? onReject : function() { return result; };
      var handlerResult =
          resolved ? resolutionHandler(result) : rejectionHandler(result);
      var promiseResolved = resolved;
      if (isThenable(handlerResult)) {
        var resolveReject = callResolveRejectFunc(handlerResult.then);
        handlerResult = resolveReject.result;
        promiseResolved = resolveReject.resolved;
      }

      if (promiseResolved) {
        return Promise.resolve(handlerResult);
      } else {
        return Promise.reject(handlerResult);
      }
    }

    // Promises use the function name "catch" to call back error handlers.
    // We can't use "catch" since function or variable names cannot use the word
    // "catch".
    function catchFunc(onRejected) {
      return this.then(undefined, onRejected);
    }

    var resolveReject = callResolveRejectFunc(asyncTask);
    var result = resolveReject.result;
    var resolved = resolveReject.resolved;

    if (isThenable(result)) {
      var thenResolveReject = callResolveRejectFunc(result.then);
      result = thenResolveReject.result;
      resolved = thenResolveReject.resolved;
    }

    return {then: then, catch: catchFunc, isPromise: true};
  }

  function all(arrayOfPromises) {
    var results = [];
    for (i = 0; i < arrayOfPromises.length; i++) {
      if (arrayOfPromises[i].isPromise) {
        arrayOfPromises[i].then(function(result) {
          results[i] = result;
        });
      } else {
        results[i] = arrayOfPromises[i];
      }
    }
    var promise = new PromisePrototypeObject(function(resolve) {
      resolve(results);
    });
    return promise;
  }

  function resolve(value) {
    var promise = new PromisePrototypeObject(function(resolve) {
      resolve(value);
    });
    return promise;
  }

  function reject(value) {
    var promise = new PromisePrototypeObject(function(resolve, reject) {
      reject(value);
    });
    return promise;
  }

  PromisePrototypeObject.all = all;
  PromisePrototypeObject.resolve = resolve;
  PromisePrototypeObject.reject = reject;
  return PromisePrototypeObject;
}();

/**
 * Sets up the test to expect a Chrome Local Storage call.
 * @param {Object} fixture Mock JS Test Object.
 * @param {Object} defaultObject Storage request default object.
 * @param {Object} result Storage result.
 * @param {boolean=} opt_AllowRejection Allow Promise Rejection
 */
function expectChromeLocalStorageGet(
    fixture, defaultObject, result, opt_AllowRejection) {
  if (opt_AllowRejection === undefined) {
    fixture.mockApis.expects(once()).
      fillFromChromeLocalStorage(eqJSON(defaultObject)).
      will(returnValue(Promise.resolve(result)));
  } else {
    fixture.mockApis.expects(once()).
      fillFromChromeLocalStorage(eqJSON(defaultObject), opt_AllowRejection).
      will(returnValue(Promise.resolve(result)));
  }
}
