'use strict';



var CLASS_NAME = 'DataPortalContext';



var config = require('./configuration-reader.js');

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

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

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

var UserInfo = require('../system/user-info.js');



/**

 * @classdesc

 *    Provides the context for custom data portal actions.

 * @description

 *    Creates a new data context object.

 *      </br></br>

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

 *    They are intended only to make publicly available the context

 *    for custom data portal actions.</i>

 *

 * @memberof bo.shared

 * @constructor

 * @param {object} dao - The data access object of the current model.

 * @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 dao argument must be an object.

 * @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 DataPortalContext (dao, properties, getValue, setValue) {

  var self = this;

  var isDirty = false;

  var daConnection = null;

  var check = Argument.inConstructor(CLASS_NAME);



  /**

   * The data access object of the current model.

   * @type {object}

   * @readonly

   */

  this.dao = check(dao || {}).forMandatory('dao').asObject();

  /**

   * Array of property definitions that may appear on the data 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();



  /**

   * The current user.

   * @type {bo.system.UserInfo}

   * @readonly

   */

  this.user = config.getUser();

  /**

   * The current locale.

   * @type {string}

   * @readonly

   */

  this.locale = config.getLocale();



  /**

   * The connection object for the data source.

   * @name bo.shared.DataPortalContext#connection

   * @type {object}

   * @readonly

   */

  Object.defineProperty(self, 'connection', {

    get: function () {

      return daConnection;

    },

    enumerable: true

  });



  /**

   * Indicates whether the current model itself has been changed.

   * @name bo.shared.DataPortalContext#isSelfDirty

   * @type {boolean}

   * @readonly

   */

  Object.defineProperty(self, 'isSelfDirty', {

    get: function () {

      return isDirty;

    },

    enumerable: true

  });



  /**

   * Sets the current state of the model.

   *

   * @param {object} [connection] - The current connection for the data store.

   * @param {boolean} [isSelfDirty] - Indicates whether the current model itself has been changed.

   * @returns {bo.shared.DataPortalContext} The data context object itself.

   */

  this.setState = function (connection, isSelfDirty) {

    daConnection = connection || null;

    isDirty = isSelfDirty === true;

    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}: Cannot read the properties of a collection.

   */

  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('readCollection', 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}: Cannot write the properties of a collection.

   */

  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('writeCollection', properties.name, propertyName);

  };



  // Immutable object.

  Object.freeze(this);

}



module.exports = DataPortalContext;