'use strict';



var CLASS_NAME = 'RuleManager';



var Argument = require('../system/argument-check.js');

var MethodError = require('../system/method-error.js');

var RuleList = require('./rule-list.js');

var ValidationRule = require('./validation-rule.js');

var ValidationContext = require('./validation-context.js');

var AuthorizationRule = require('./authorization-rule.js');

var AuthorizationContext = require('./authorization-context.js');

var RuleSeverity = require('./rule-severity.js');

var NoAccessBehavior = require('./no-access-behavior.js');

var PropertyInfo = require('../shared/property-info.js');



/**

 * @classdesc Provides methods to manage the rules of a business object model.

 * @description Creates a new rule manager object.

 *

 * @memberof bo.rules

 * @constructor

 */

var RuleManager = function () {



  var self = this;

  var validationRules = new RuleList();

  var authorizationRules = new RuleList();

  var noAccessBehavior = null;



  /**

   * Defines the default behavior for unauthorized operations.

   * @name bo.rules.RuleManager#noAccessBehavior

   * @type {bo.rules.NoAccessBehavior}

   * @default {bo.rules.NoAccessBehavior#throwError}

   */

  Object.defineProperty(this, 'noAccessBehavior', {

    get: function () {

      return noAccessBehavior;

    },

    set: function (value) {

      noAccessBehavior = Argument.inProperty(CLASS_NAME, 'noAccessBehavior')

          .check(value).for().asEnumMember(NoAccessBehavior, NoAccessBehavior.throwError);

    },

    enumeration: true

  });



  /**

   * Initializes the rule manager: sorts the rules by priority and

   * sets the default behavior for unauthorized operations.

   *

   * @param {bo.rules.NoAccessBehavior} defaultBehavior - The default behavior for unauthorized operations.

   *

   * @throws {@link bo.system.ArgumentError Argument error}: The severity must be a NoAccessBehavior item.

   */

  this.initialize = function (defaultBehavior) {

    this.noAccessBehavior = defaultBehavior;

    validationRules.sort();

    authorizationRules.sort();



    for (var id in authorizationRules) {

      if (authorizationRules.hasOwnProperty(id) && authorizationRules[id] instanceof Array) {

        authorizationRules[id].forEach(function (rule) {

          rule.noAccessBehavior = noAccessBehavior;

        });

      }

    }

  };



  /**

   * Adds a new rule to the business object model.

   *

   * @param {(bo.rules.ValidationRule|bo.rules.AuthorizationRule)} rule - The rule to add.

   *

   * @throws {@link bo.system.ArgumentError Argument error}: The rule must be a Rule object.

   * @throws {@link bo.system.ArgumentError Argument error}: The rule is not initialized.

   */

  this.add = function (rule) {



    if (rule instanceof ValidationRule) {

      if (!rule.primaryProperty)

        throw new ArgumentError('notInitRule', CLASS_NAME, 'add', 'rule');

      validationRules.add(rule.primaryProperty.name, rule);



    } else if (rule instanceof AuthorizationRule) {

      if (!rule.ruleId)

        throw new ArgumentError('notInitRule', CLASS_NAME, 'add', 'rule');

      authorizationRules.add(rule.ruleId, rule);



    } else

      throw new MethodError('manType', CLASS_NAME, 'add', 'rule', 'Rule');

  };



  /**

   * Validates a property - executes all validation rules of the property.

   *

   * @param {bo.shared.PropertyInfo} property - The model property to validate.

   * @param {bo.rules.ValidationContext} context - The context of the property validation.

   *

   * @throws {@link bo.system.ArgumentError Argument error}: The property must be a PropertyInfo object.

   * @throws {@link bo.system.ArgumentError Argument error}: The context must be a ValidationContext object.

   */

  this.validate = function (property, context) {

    var check = Argument.inMethod(CLASS_NAME, 'validate');



    property = check(property).forMandatory('property').asType(PropertyInfo);

    context = check(context).forMandatory('context').asType(ValidationContext);



    context.brokenRules.clear(property);



    var rules = validationRules[property.name];

    if (rules) {

      for (var i = 0; i < rules.length; i++) {

        var rule = rules[i];



        var result = rule.execute(rule.getInputValues(context.getValue));



        if (result) {

          if (result.severity !== RuleSeverity.success) {

            context.brokenRules.add(result.toBrokenRule());

          }

          if (result.affectedProperties) {

            result.affectedProperties.forEach(function (affectedProperty) {

              self.validate(affectedProperty, context);

            });

          }

          if (result.stopsProcessing)

            break;

        }

      }

    }

  };



  /**

   * Validates a property - executes all validation rules of the property.

   *

   * @param {bo.rules.AuthorizationContext} context - The context of the action authorization.

   *

   * @throws {@link bo.system.ArgumentError Argument error}: The context must be a AuthorizationContext object.

   * @throws {@link bo.rules.AuthorizationError Authorization error}: The user has no permission to execute the action.

   */

  this.hasPermission = function (context) {



    context = Argument.inMethod(CLASS_NAME, 'hasPermission')

        .check(context).forMandatory('context').asType(AuthorizationContext);

    var isAllowed = true;



    var rules = authorizationRules[context.ruleId];

    if (rules) {

      for (var i = 0; i < rules.length; i++) {



        var result = rules[i].execute(context.user);



        if (result) {

          context.brokenRules.push(result.toBrokenRule());

          isAllowed = false;

          if (result.stopsProcessing)

            break;

        }

      }

    }

    return isAllowed;

  };



  var args = Array.prototype.slice.call(arguments);

  args.forEach(function (arg) {

    self.add(arg);

  });



  // Immutable object.

  Object.freeze(this);

};



module.exports = RuleManager;