'use strict';
var CLASS_NAME = 'ValidationRule';
var util = require('util');
var Argument = require('../system/argument-check.js');
var RuleBase = require('./rule-base.js');
var RuleSeverity = require('./rule-severity.js');
var ValidationResult = require('./validation-result.js');
var PropertyInfo = require('../shared/property-info.js');
/**
 * @classdesc
 *      Represents a validation rule.
 * @description
 *      Creates a new validation rule object.
 *      The rule instances should be frozen.
 *
 * @memberof bo.rules
 * @constructor
 * @param {string} ruleName - The name of the rule.
 *    It is typically the name of the constructor, without the Rule suffix.
 *
 * @extends bo.rules.RuleBase
 *
 * @throws {@link bo.system.ArgumentError Argument error}: The rule name must be a non-empty string.
 */
function ValidationRule (ruleName) {
  RuleBase.call(this, ruleName);
  /**
   * The definition of the property the rule relates to.
   * @type {bo.shared.PropertyInfo}
   * @readonly
   */
  this.primaryProperty = null;
  /**
   * Sets the properties of the rule.
   *
   * @param {bo.shared.PropertyInfo} primaryProperty - The property definition the rule relates to.
   * @param {string} message - Human-readable description of the rule failure.
   * @param {number} [priority=10] - The priority of the rule.
   * @param {boolean} [stopsProcessing=false] - Indicates the rule behavior in case of failure.
   *
   * @throws {@link bo.system.ArgumentError Argument error}: The primary property must be a PropertyInfo object.
   * @throws {@link bo.system.ArgumentError Argument error}: The message must be a non-empty string.
   */
  this.initialize = function (primaryProperty, message, priority, stopsProcessing) {
    this.primaryProperty = Argument.inMethod(CLASS_NAME, 'initialize')
        .check(primaryProperty).forMandatory('primaryProperty').asType(PropertyInfo);
    // Initialize base properties.
    RuleBase.prototype.initialize.call(this, message, priority, stopsProcessing);
  };
  var inputProperties = [];
  var affectedProperties = [];
  /**
   * Adds an additional property to the rule that will use its value.
   *
   * @param {bo.shared.PropertyInfo} property - An input property that value is used by the rule of.
   *
   * @throws {@link bo.system.ArgumentError Argument error}: The input property must be a PropertyInfo object.
   */
  this.addInputProperty = function (property) {
    property = Argument.inMethod(CLASS_NAME, 'addInputProperty')
        .check(property).forMandatory('property').asType(PropertyInfo);
    if (inputProperties.indexOf(property) < 0)
      inputProperties.push(property);
  };
  /**
   * Adds an additional property that is influenced by the rule.
   *
   * @param {bo.shared.PropertyInfo} property - An affected property influenced by the rule.
   *
   * @throws {@link bo.system.ArgumentError Argument error}: The affected property must be a PropertyInfo object.
   */
  this.addAffectedProperty = function (property) {
    property = Argument.inMethod(CLASS_NAME, 'addAffectedProperty')
        .check(property).forMandatory('property').asType(PropertyInfo);
    if (affectedProperties.indexOf(property) < 0)
      affectedProperties.push(property);
  };
  /**
   * Returns the values of the properties that are used by the rule.
   * This method is called by the rule manager internally to provide
   * the values of the input properties for the execute() method.
   *
   * @protected
   * @param {internal~getValue} getValue - A function that returns the value of a property.
   * @returns {object} An object that properties hold the values of the input properties of.
   *
   * @throws {@link bo.system.ArgumentError Argument error}: The getValue argument must be a function..
   */
  this.getInputValues = function (getValue) {
    getValue = Argument.inMethod(CLASS_NAME, 'getInputValues')
        .check(getValue).forMandatory('getValue').asFunction();
    var inputValues = {};
    var combined = new Array(this.primaryProperty).concat(inputProperties);
    for (var i = 0; i < combined.length; i++) {
      var property = combined[i];
      inputValues[property.name] = getValue(property);
    }
    return inputValues;
  };
  /**
   * Returns the result of the rule executed.
   *
   * @param {string} [message] - Human-readable description of the rule failure.
   * @param {bo.rules.RuleSeverity} severity - The severity of the failed rule.
   * @returns {bo.rules.ValidationResult} The result of the validation rule.
   */
  this.result = function (message, severity) {
    var result = new ValidationResult(this.ruleName, this.primaryProperty.name, message || this.message);
    result.severity = Argument.inMethod(CLASS_NAME, 'result')
        .check(severity).for('severity').asEnumMember(RuleSeverity, RuleSeverity.error);
    result.stopsProcessing = this.stopsProcessing;
    result.isPreserved = false;
    result.affectedProperties = affectedProperties;
    return result;
  };
  // Immutable object.
  Object.freeze(ValidationRule);
}
util.inherits(ValidationRule, RuleBase);
module.exports = ValidationRule;