'use strict';



var CLASS_NAME = 'TransferContext';



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 transfer objects.

 * @description

 *    Creates a new transfer context object.

 *      </br></br>

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

 *    They are intended only to make publicly available the values of model properties

 *    for custom transfer objects.</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 TransferContext (properties, getValue, setValue) {

  var self = this;

  var check = Argument.inConstructor(CLASS_NAME);



  /**

   * Array of property definitions that may appear on the transfer object.

   * @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();



  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 a 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.

   */

  this.getValue = function (propertyName) {

    if (getValue) {

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

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

      return getValue(getByName(propertyName));

    } else

      throw new ModelError('getValue');

  };



  /**

   * 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.

   */

  this.setValue = function (propertyName, value) {

    if (setValue) {

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

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

      if (value !== undefined) {

        setValue(getByName(propertyName), value);

      }

    } else

      throw new ModelError('setValue');

  };



  // Immutable object.

  Object.freeze(this);

}



module.exports = TransferContext;