'use strict';



var CLASS_NAME = 'AuthorizationRule';



var util = require('util');

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

var ArgumentError = require('../system/argument-error.js');

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

var RuleBase = require('./rule-base.js');

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

var AuthorizationAction = require('./authorization-action.js');

var AuthorizationResult = require('./authorization-result.js');

var AuthorizationError = require('./authorization-error.js');

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



/**

 * @classdesc

 *      Represents an authorization rule.

 * @description

 *      Creates a new authorization 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 AuthorizationRule (ruleName) {

  RuleBase.call(this, ruleName);



  var self = this;

  var noAccessBehavior = NoAccessBehavior.throwError;

  var propertyName = '';



  /**

   * The identifier of the authorization action. Generally it is the action value,

   * or when target is not empty, the action value and the target name separated by

   * a dot, respectively.

   * @type {string}

   * @readonly

   */

  this.ruleId = null;



  /**

   * The action to do when the rule fails.

   * @name bo.rules.AuthorizationRule#noAccessBehavior

   * @type {bo.rules.NoAccessBehavior}

   */

  Object.defineProperty(this, 'noAccessBehavior', {

    get: function () {

      return noAccessBehavior;

    },

    set: function (value) {

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

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

    },

    enumeration: true

  });



  /**

   * Sets the properties of the rule.

   *

   * @param {bo.rules.AuthorizationAction} action - The action to be authorized.

   * @param {(bo.shared.PropertyInfo|string|null)} [target] - Eventual parameter of the authorization action.

   * @param {string} message - Human-readable description of the rule failure.

   * @param {number} [priority=100] - 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 action must be a AuthorizationAction item.

   * @throws {@link bo.system.ArgumentError Argument error}: The target must be a PropertyInfo object in case of property read or write.

   * @throws {@link bo.system.ArgumentError Argument error}: The target must be a non-empty string in case of method execution.

   * @throws {@link bo.system.ArgumentError Argument error}: The target must be null in case of model actions.

   * @throws {@link bo.system.ArgumentError Argument error}: The message must be a non-empty string.

   */

  this.initialize = function (action, target, message, priority, stopsProcessing) {

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



    action = check(action).for('action').asEnumMember(AuthorizationAction, null);

    this.ruleId = AuthorizationAction.getName(action);



    if (action === AuthorizationAction.readProperty || action === AuthorizationAction.writeProperty) {

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

      propertyName = target.name;

      this.ruleId += '.' + target.name;



    } else if (action === AuthorizationAction.executeMethod) {

      target = check(target).forMandatory('target').asString();

      this.ruleId += '.' + target;



    } else {

      if (target !== null)

        throw new ArgumentError('m_null', CLASS_NAME, 'initialize', 'target');

    }



    // Initialize base properties.

    RuleBase.prototype.initialize.call(this, message, priority || 100, stopsProcessing);

  };



  function behaviorToSeverity() {

    switch (noAccessBehavior) {

      case NoAccessBehavior.showInformation:

        return RuleSeverity.information;

      case NoAccessBehavior.showWarning:

        return RuleSeverity.warning;

      default:

        return RuleSeverity.error;

    }

  }



  /**

   * Returns the result of the rule executed.

   * If the rule fails and the noAccessBehavior property is

   * {@link bo.rules.NoAccessBehavior#throwError}, throws an authorization error.

   *

   * @param {string} [message] - Human-readable description of the rule failure.

   * @param {bo.rules.RuleSeverity} severity - The severity of the failed rule.

   * @returns {bo.rules.AuthorizationResult} The result of the authorization rule.

   *

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

   */

  this.result = function (message, severity) {

    if (noAccessBehavior === NoAccessBehavior.throwError) {

      throw new AuthorizationError(message || this.message);

    } else {

      var result = new AuthorizationResult(this.ruleName, propertyName, message || this.message);

      result.severity = severity || behaviorToSeverity();

      result.stopsProcessing = this.stopsProcessing;

      result.isPreserved = true;

      return result;

    }

  };



  // Immutable object.

  Object.freeze(AuthorizationRule);

}

util.inherits(AuthorizationRule, RuleBase);



module.exports = AuthorizationRule;