'use strict';



var CLASS_NAME = 'PropertyContext';



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

var ModelError = require('./model-error.js');

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



/**

 * @classdesc

 *    Provides the context for custom property functions.

 * @description

 *    Creates a new property context object.

 *      </br></br>

 *    <i><b>Warning:</b> Property context objects are created in models internally.

 *    They are intended only to make publicly available the context

 *    for custom property functions.</i>

 *

 * @memberof bo.shared

 * @constructor

 * @param {Array.<bo.shared.PropertyInfo>} properties - An array of property definitions.

 * @param {internal~getValue} [getValue] - A function that returns the current value of a property.

 * @param {internal~setValue} [setValue] - A function that changes the current value of a property.

 *

 * @throws {@link bo.system.ArgumentError Argument error}: The properties must be an array

 *    of PropertyInfo objects, or a single PropertyInfo object or null.

 * @throws {@link bo.system.ArgumentError Argument error}: The getValue argument must be a function.

 * @throws {@link bo.system.ArgumentError Argument error}: The setValue argument must be a function.

 */

function PropertyContext (properties, getValue, setValue) {

  var self = this;

  var primaryProperty = null;

  var check = Argument.inConstructor(CLASS_NAME);



  /**

   * The primary property of the custom function.

   * @name bo.shared.PropertyContext#primaryProperty

   * @type {bo.shared.PropertyInfo}

   * @readonly

   */

  Object.defineProperty(self, 'primaryProperty', {

    get: function () {

      return primaryProperty;

    },

    enumerable: true

  });



  /**

   * Array of property definitions that may used by the custom function.

   * @type {Array.<bo.shared.PropertyInfo>}

   * @readonly

   */

  this.properties = check(properties).forOptional('properties').asArray(PropertyInfo);

  getValue = check(getValue).forOptional('getValue').asFunction();

  setValue = check(setValue).forOptional('setValue').asFunction();



  /**

   * Sets the primary property of the custom function.

   *

   * @param {bo.shared.PropertyInfo} property - The primary property of the custom function.

   * @returns {bo.shared.PropertyContext} The property context object itself.

   */

  this.with = function (property) {

    primaryProperty = Argument.inMethod(CLASS_NAME, 'with')

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

    return this;

  };



  function getByName (name) {

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

      if (self.properties[i].name === name)

        return self.properties[i];

    }

    throw new ModelError('noProperty', properties.name, name);

  }



  /**

   * Gets the current value of a model property.

   *

   * @param {string} propertyName - The name of the property.

   * @returns {*} The value of the model property.

   *

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

   * @throws {@link bo.system.ArgumentError Argument error}: The model has no property with the given name.

   * @throws {@link bo.shared.ModelError Model error}: The property cannot be read.

   */

  this.getValue = function (propertyName) {

    propertyName = Argument.inMethod(CLASS_NAME, 'getValue')

        .check(propertyName).forMandatory('propertyName').asString();

    if (getValue)

      return getValue(getByName(propertyName));

    else

      throw new ModelError('readProperty', properties.name, propertyName);

  };



  /**

   * Sets the current value of a model property.

   *

   * @param {string} propertyName - The name of the property.

   * @param {*} value - The new value of the property.

   *

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

   * @throws {@link bo.system.ArgumentError Argument error}: The model has no property with the given name.

   * @throws {@link bo.shared.ModelError Model error}: The property cannot be written.

   */

  this.setValue = function (propertyName, value) {

    propertyName = Argument.inMethod(CLASS_NAME, 'setValue')

        .check(propertyName).forMandatory('propertyName').asString();

    if (setValue) {

      if (value !== undefined) {

        setValue(getByName(propertyName), value);

      }

    } else

      throw new ModelError('writeProperty', properties.name, propertyName);

  };



  // Immutable object.

  Object.freeze(this);

}



module.exports = PropertyContext;