/**
 * Copyright (c) 2014, 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 chalk = require('chalk');













const EXPECTED_COLOR = chalk.green;
const RECEIVED_COLOR = chalk.red;

// get the type of a value with handling the edge cases like `typeof []`
// and `typeof null`
const getType = value => {
  if (typeof value === 'undefined') {
    return 'undefined';
  } else if (value === null) {
    return 'null';
  } else if (Array.isArray(value)) {
    return 'array';
  } else if (typeof value === 'boolean') {
    return 'boolean';
  } else if (typeof value === 'function') {
    return 'function';
  } else if (typeof value === 'number') {
    return 'number';
  } else if (typeof value === 'string') {
    return 'string';
  } else if (typeof value === 'object') {
    if (value.constructor === RegExp) {
      return 'regexp';
    }
    return 'object';
    // $FlowFixMe https://github.com/facebook/flow/issues/1015
  } else if (typeof value === 'symbol') {
    return 'symbol';
  }

  throw new Error(`value of unknown type: ${ value }`);
};

const stringifyValue = (value, visitedSet) => {
  if (value instanceof Error) {
    const name = value.constructor && value.constructor.name || 'Error';
    return `${ name }: ${ value.message }`;
  } else if (typeof value === 'object' && value !== null) {
    if (
    value &&
    value.constructor &&
    value.constructor.name === 'RegExp')
    {
      return value.toString();
    } else {
      if (visitedSet.has(value)) {
        return '[Circular]';
      }
      visitedSet.add(value);
    }
  } else if (typeof value === 'function') {
    return value.toString();
  } else if (typeof value === 'undefined') {
    return 'undefined';
    // $FlowFixMe symbols are not supported by flow yet
  } else if (typeof value === 'symbol') {
    return value.toString();
  } else if (value === Infinity) {
    return 'Infinity';
  } else if (value === -Infinity) {
    return '-Infinity';
  } else if (Number.isNaN(value)) {
    return 'NaN';
  }
  return value;
};

const stringifyDeep = obj => {
  const visitedSet = new Set();
  let result = null;
  try {
    result = JSON.stringify(
    obj,
    (_, value) => stringifyValue(value, visitedSet));

  } catch (err) {}
  return typeof result === 'string' ? result : null;
};

const stringifyShallow = obj => {
  let result = null;
  try {
    result = stringifyValue(obj, new Set());
  } catch (err) {}
  return typeof result === 'string' ? result : null;
};

// Convert to JSON removing circular references and
// converting JS values to strings.
const stringify = obj => {
  return (
    stringifyDeep(obj) ||
    stringifyShallow(obj) ||
    '[' + typeof obj + ']');

};

const printReceived = object => RECEIVED_COLOR(stringify(object));
const printExpected = value => EXPECTED_COLOR(stringify(value));

const printWithType = (
name,
received,
print) =>
{
  const type = getType(received);
  return (
    name + ':' + (
    type !== 'null' && type !== 'undefined' ?
    '\n  ' + type + ': ' :
    ' ') +
    print(received));

};

const ensureNoExpected = (expected, matcherName) => {
  matcherName || (matcherName = 'This');
  if (typeof expected !== 'undefined') {
    throw new Error(`${ matcherName } matcher does not accept any arguments.`);
  }
};

const ensureActualIsNumber = (actual, matcherName) => {
  matcherName || (matcherName = 'This matcher');
  if (typeof actual !== 'number') {
    throw new Error(
    `${ matcherName } actual value should be a number. ` +
    `'${ typeof actual }' was passed.`);

  }
};

const ensureExpectedIsNumber = (expected, matcherName) => {
  matcherName || (matcherName = 'This matcher');
  if (typeof expected !== 'number') {
    throw new Error(
    `${ matcherName } expected value should be a number. ` +
    `'${ typeof expected }' was passed.`);

  }
};

const ensureNumbers = (actual, expected, matcherName) => {
  ensureActualIsNumber(actual, matcherName);
  ensureExpectedIsNumber(expected, matcherName);
};

const pluralize =
(word, count) => `${ count } ${ word }${ count === 1 ? '' : 's' }`;

const matcherHint = function (
matcherName)


{let received = arguments.length <= 1 || arguments[1] === undefined ? 'received' : arguments[1];let expected = arguments.length <= 2 || arguments[2] === undefined ? 'expected' : arguments[2];
  return chalk.dim('expect(') + RECEIVED_COLOR(received) +
  chalk.dim(')' + matcherName + '(') +
  EXPECTED_COLOR(expected) + chalk.dim(')');
};

module.exports = {
  EXPECTED_COLOR,
  RECEIVED_COLOR,
  ensureActualIsNumber,
  ensureExpectedIsNumber,
  ensureNoExpected,
  ensureNumbers,
  getType,
  matcherHint,
  pluralize,
  printExpected,
  printReceived,
  printWithType,
  stringify };