"use strict";

var defineAssertion = require("./define-assertion");

// taken from https://stackoverflow.com/questions/2008279/validate-a-javascript-function-name/2008444#2008444
var validFunctionName = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;

function verifyArguments(name, options) {
  if (typeof name !== "string" || !validFunctionName.test(name)) {
    throw new TypeError(
      "'name' argument must be a non-empty string matching " +
        validFunctionName.toString()
    );
  }

  if (typeof options !== "object" || Object.keys(options).length === 0) {
    throw new TypeError("'options' argument must be a non-empty object");
  }

  if (typeof options.assert !== "function" || options.assert.length === 0) {
    throw new TypeError(
      "'assert' option must be a Function, taking at least one argument"
    );
  }

  if (
    typeof options.refute !== "undefined" &&
    (typeof options.refute !== "function" || options.refute.length === 0)
  ) {
    throw new TypeError(
      "'refute' option must be a Function, taking at least one argument"
    );
  }

  if (
    typeof options.assertMessage !== "string" ||
    options.assertMessage.length === 0
  ) {
    throw new TypeError("'assertMessage' option must be a non-empty String");
  }

  if (
    typeof options.refuteMessage !== "string" ||
    options.refuteMessage.length === 0
  ) {
    throw new TypeError("'refuteMessage' option must be a non-empty String");
  }
}

function createAdd(referee) {
  function add(name, options) {
    verifyArguments(name, options);

    var refuteArgs = options.refute
      ? options.refute.length
      : options.assert.length;

    if (!options.refute) {
      options.refute = function () {
        return !options.assert.apply(this, arguments);
      };
    }

    defineAssertion(
      referee,
      "assert",
      name,
      options.assert,
      options.assert.length,
      options.values
    );
    defineAssertion(
      referee,
      "refute",
      name,
      options.refute,
      refuteArgs,
      options.values
    );

    // Refactor: pass this down to where it is needed, so it is not part of the public api
    referee.assert[name].message = options.assertMessage;
    referee.refute[name].message = options.refuteMessage;

    if (!options.expectation) {
      return;
    }

    referee.assert[name].expectationName = options.expectation;
    referee.refute[name].expectationName = options.expectation;
  }

  return add;
}

module.exports = createAdd;
