/*
 * should.js - assertion library
 * Copyright(c) 2010-2013 TJ Holowaychuk <tj@vision-media.ca>
 * Copyright(c) 2013-2017 Denis Bardadym <bardadymchik@gmail.com>
 * MIT Licensed
 */

import { functionName } from "../util";

export default function(should, Assertion) {
  /**
   * Assert given object is number
   * @name Number
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Number", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be a number" };

    this.have.type("number");
  });

  /**
   * Assert given object is arguments
   * @name arguments
   * @alias Assertion#Arguments
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("arguments", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be arguments" };

    this.have.class("Arguments");
  });

  Assertion.alias("arguments", "Arguments");

  /**
   * Assert given object has some type using `typeof`
   * @name type
   * @memberOf Assertion
   * @param {string} type Type name
   * @param {string} [description] Optional message
   * @category assertion types
   */
  Assertion.add("type", function(type, description) {
    this.params = { operator: "to have type " + type, message: description };

    should(typeof this.obj).be.exactly(type);
  });

  /**
   * Assert given object is instance of `constructor`
   * @name instanceof
   * @alias Assertion#instanceOf
   * @memberOf Assertion
   * @param {Function} constructor Constructor function
   * @param {string} [description] Optional message
   * @category assertion types
   */
  Assertion.add("instanceof", function(constructor, description) {
    this.params = {
      operator: "to be an instance of " + functionName(constructor),
      message: description
    };

    this.assert(Object(this.obj) instanceof constructor);
  });

  Assertion.alias("instanceof", "instanceOf");

  /**
   * Assert given object is function
   * @name Function
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Function", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be a function" };

    this.have.type("function");
  });

  /**
   * Assert given object is object
   * @name Object
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Object", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be an object" };

    this.is.not.null().and.have.type("object");
  });

  /**
   * Assert given object is string
   * @name String
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("String", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be a string" };

    this.have.type("string");
  });

  /**
   * Assert given object is array
   * @name Array
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Array", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be an array" };

    this.have.class("Array");
  });

  /**
   * Assert given object is boolean
   * @name Boolean
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Boolean", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be a boolean" };

    this.have.type("boolean");
  });

  /**
   * Assert given object is error
   * @name Error
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Error", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be an error" };

    this.have.instanceOf(Error);
  });

  /**
   * Assert given object is a date
   * @name Date
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("Date", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be a date" };

    this.have.instanceOf(Date);
  });

  /**
   * Assert given object is null
   * @name null
   * @alias Assertion#Null
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("null", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be null" };

    this.assert(this.obj === null);
  });

  Assertion.alias("null", "Null");

  /**
   * Assert given object has some internal [[Class]], via Object.prototype.toString call
   * @name class
   * @alias Assertion#Class
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("class", function(cls) {
    this.params = { operator: "to have [[Class]] " + cls };

    this.assert(Object.prototype.toString.call(this.obj) === "[object " + cls + "]");
  });

  Assertion.alias("class", "Class");

  /**
   * Assert given object is undefined
   * @name undefined
   * @alias Assertion#Undefined
   * @memberOf Assertion
   * @category assertion types
   */
  Assertion.add("undefined", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be undefined" };

    this.assert(this.obj === void 0);
  });

  Assertion.alias("undefined", "Undefined");

  /**
   * Assert given object supports es6 iterable protocol (just check
   * that object has property Symbol.iterator, which is a function)
   * @name iterable
   * @memberOf Assertion
   * @category assertion es6
   */
  Assertion.add("iterable", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be iterable" };

    should(this.obj)
      .have.property(Symbol.iterator)
      .which.is.a.Function();
  });

  /**
   * Assert given object supports es6 iterator protocol (just check
   * that object has property next, which is a function)
   * @name iterator
   * @memberOf Assertion
   * @category assertion es6
   */
  Assertion.add("iterator", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be iterator" };

    should(this.obj)
      .have.property("next")
      .which.is.a.Function();
  });

  /**
   * Assert given object is a generator object
   * @name generator
   * @memberOf Assertion
   * @category assertion es6
   */
  Assertion.add("generator", function() {
    this.assertZeroArguments(arguments);
    this.params = { operator: "to be generator" };

    should(this.obj).be.iterable.and.iterator.and.it.is.equal(this.obj[Symbol.iterator]());
  });
}
