'use strict';



var CLASS_NAME = 'BrokenRuleList';



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

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

var BrokenRule = require('./broken-rule.js');

var BrokenRulesOutput = require('./broken-rules-output.js');

var RuleNotice = require('./rule-notice.js');

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



/**

 * @classdesc Represents the lists of broken rules.

 * @description Creates a new broken rule list instance.

 *

 * @memberof bo.rules

 * @constructor

 * @param {string} modelName - The name of the model.

 *

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

 */

var BrokenRuleList = function (modelName) {



  modelName = Argument.inConstructor(CLASS_NAME).check(modelName).forMandatory('modelName').asString();



  var items = {};

  var length = 0;



  /**

   * Adds a broken rule to the list.

   *

   * @param {bo.rules.BrokenRule} brokenRule - A broken rule to add.

   *

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

   */

  this.add = function (brokenRule) {

    brokenRule = Argument.inMethod(CLASS_NAME, 'add')

        .check(brokenRule).forMandatory('brokenRule').asType(BrokenRule);



    if (items[brokenRule.propertyName])

      items[brokenRule.propertyName].push(brokenRule);

    else {

      items[brokenRule.propertyName] = new Array(brokenRule);

      length++;

    }

  };



  /**

   * Removes the broken rules of a property except of the retained ones.

   *

   * @param {string} propertyName - The name of the property that broken rules are deleted of.

   */

  function clearFor (propertyName) {

    if (items[propertyName]) {

      var preserved = items[propertyName].filter(function (item) {

        return item.isPreserved;

      });

      if (preserved.length)

        items[propertyName] = preserved;

      else {

        delete items[propertyName];

        length--;

      }

    }

  }



  /**

   * Removes the broken rules of a property except of the retained ones.

   * If property is omitted, all broken rules are removed

   * except of the retained ones.

   *

   * @param {bo.rules.PropertyInfo} [property] - A property definition.

   */

  this.clear = function (property) {

    if (property instanceof PropertyInfo)

      clearFor(property.name);

    else

      for (var propertyName in items) {

        if (items.hasOwnProperty(propertyName))

          clearFor(propertyName);

      }

  };



  /**

   * Removes the broken rules of a property, including retained ones.

   * If property is omitted, all broken rules are removed.

   *

   * @param property

   */

  this.clearAll = function (property) {

    if (property instanceof PropertyInfo) {

      delete items[property.name];

      length--;

    } else {

      items = {};

      length = 0;

    }

  };



  /**

   * Determines if the model is valid. The model is valid when it has no

   * broken rule with error severity.

   *

   * @returns {boolean} - True if the model is valid, otherwise false.

   */

  this.isValid = function () {

    for (var propertyName in items) {

      if (items.hasOwnProperty(propertyName)) {



        if (items[propertyName].some(function (item) {

              return item.severity === RuleSeverity.error;

            }))

          return false;

      }

    }

    return true;

  };



  /**

   * Transforms the broken rules into a format that can be sent to the client.

   *

   * @param {string} [namespace] - The namespace of the message keys when messages are localizable.

   * @returns {bo.rules.BrokenRulesOutput} The response object to send.

   *

   * @throws {@link bo.system.ArgumentError Argument error}: The namespace must be a string.

   */

  this.output = function (namespace) {



    namespace = Argument.inMethod(CLASS_NAME, 'output')

        .check(namespace).forOptional('namespace').asString();



    var data = new BrokenRulesOutput();

    if (length) {



      var ns = namespace ? namespace + ':' : '';

      for (var property in items) {

        if (items.hasOwnProperty(property)) {



          items[property].forEach(function(brokenRule) {



            var propertyName = modelName + '.' + brokenRule.propertyName;

            var message = brokenRule.message || ns + propertyName + '.' + brokenRule.ruleName;

            var notice = new RuleNotice(message, brokenRule.severity);



            data.add(propertyName, notice);

          });

        }

      }

    }

    return data;

  };

};



module.exports = BrokenRuleList;