Show:
/**
 * @module proact-properties
 */

/**
 * <p>
 *  Constructs a `ProAct.ObjectCore`.
 *  `ProAct.ObjectCore` is a {{#crossLink "ProAct.Core"}}{{/crossLink}} that manages all the
 *  {{#crossLink "ProAct.Property"}}{{/crossLink}} instances for a reactive ProAct.js object.
 * </p>
 * <p>
 *  It is responsible for all the {{#crossLink "ProAct.Property"}}{{/crossLink}} instances as well as
 *  initializing them and deciding which type of property corresponds to which field.
 * </p>
 * <p>
 *  `ProAct.ObjectCore` is part of the proact-properties module of ProAct.js.
 * </p>
 *
 * ```
 *  
 *  var object = {
 *    a: 4,
 *    b: 5,
 *    c: function () {
 *      return this.a + this.b;
 *    }
 *  };
 *  var core = new ProAct.ObjectCore(object);
 *
 *  console.log(object.c); // 9
 *
 *  object.a = 1;
 *  console.log(object.c); // 6
 *
 *  console.log(core.value('c')); // 6
 *
 *  core.set('b', 2));
 *  console.log(object.b); // 2
 *  console.log(object.c); // 3
 * ```
 *
 * @class ProAct.ObjectCore
 * @extends ProAct.Core
 * @constructor
 * @param {Object} object
 *      The shell objec arround this core. This should be plain JavaScript object.
 * @param {Object} meta
 *      Optional meta data to be used to define the observer-observable behavior of the <i>object</i>.
 *      For example transformations for its properties.
 */
function ObjectCore (object, meta) {
  this.properties = {};

  P.C.call(this, object, meta); // Super!
};
ProAct.ObjectCore = P.OC = ObjectCore;

ProAct.ObjectCore.prototype = P.U.ex(Object.create(P.C.prototype), {

  /**
   * Reference to the constructor of this object.
   *
   * @property constructor
   * @type ProAct.ObjectCore
   * @final
   * @for ProAct.ObjectCore
   */
  constructor: ProAct.ObjectCore,

  /**
   * A function to be set to the <i>shell</i> object's <b>p</b> field (if it is configured in {{#crossLink "ProAct.Configuration"}}{{/crossLink}}).
   * <p>
   *  It uses its <i>p</i> argument if it is string to return the right {{#crossLink "ProAct.Property"}}{{/crossLink}} for passed field name.
   * </p>
   * <p>
   *  If the <i>p</i> argument is <b>*</b> or empty <i>this</i> `ProAct.ObjectCore` instance is returned.
   * </p>
   *
   * ```
   *  core.value('a'); // returns the shell's 'a' value - shell.a.
   *  core.value('*'); // returns this.
   *  core.value(); // returns this.
   * ```
   *
   * @for ProAct.ObjectCore
   * @instance
   * @method value
   * @param {String} p
   *      The name of the managed {{#crossLink "ProAct.Property"}}{{/crossLink}} to retrieve.
   *      It can be set to <b>*</b> or skipped for <i>this</i> itself to be retrieved.
   * @return {Object}
   *      Managed {{#crossLink "ProAct.Property"}}{{/crossLink}} instance with field name equal to the passed <i>p</i> parameter or <i>this</i>.
   */
  value: function (p) {
    if (!p || p === '*') {
      return this;
    }

    return this.properties[p];
  },

  /**
   * Initializes all the {{#crossLink "ProAct.Property"}}{{/crossLink}} instances for the <i>shell</i>of <i>this</i> ProAct.ObjectCore.
   * <p>
   *  Using the types of the fields of the <i>shell</i> object the right {{#crossLink "ProAct.Property"}}{{/crossLink}} instances are created and stored
   *  in <i>this</i> using {{#crossLink "ProAct.Configuration/makeProp:method"}}{{/crossLink}}.
   * </p>
   *
   * @for ProAct.ObjectCore
   * @protected
   * @instance
   * @method setup
   */
  setup: function () {
    var object = this.shell,
        property;

    for (property in object) {
      this.makeProp(property, null, this.meta[property]);
    }
  },

  /**
   * Creates a {{#crossLink "ProAct.Property"}}{{/crossLink}} instance for <i>this</i>'s shell.
   *
   * ```
   *  var shell = {a: 3};
   *  var core = new ProAct.Core(shell);
   *
   *  shell.b = function () { return this.a + 5; };
   *  core.makeProp('b');
   *
   *  console.log(shell.b); // 8
   *
   *  shell.a = 5;
   *  console.log(shell.b); // 10
   * ```
   *
   * @for ProAct.ObjectCore
   * @instance
   * @method makeProp
   * @param {String} property
   *      The name of the property, the name of the field in the <i>shell</i>.
   * @param {Array} listeners
   *      Initial listeners for 'change' of the property, can be skipped.
   * @param {String|Array} meta
   *      Meta information for the property to create, for example if the meta contains 'noprop', no property is created,
   *      and the initial value of the field is preserved.
   * @return {ProAct.Property}
   *      The newly crated and stored in <i>this</i> property, or null if no property was created.
   * @throws {Error}
   *      If there is no field defined in the <i>shell</i> named as the passed <i>property</i>.
   */
  makeProp: function (property, listeners, meta) {
    var object = this.shell,
        conf = ProAct.Configuration,
        keyprops = conf.keyprops,
        keypropList = conf.keypropList,
        result;

    if (meta && (meta === 'noprop' || (meta.indexOf && meta.indexOf('noprop') >= 0))) {
      return null;
    }

    if (keyprops && keypropList.indexOf(property) !== -1) {
      throw Error('The property name ' + property + ' is a key word for pro objects! Objects passed to ProAct.prob can not contain properties named as keyword properties.');
      return null;
    }

    if (object.hasOwnProperty(property)) {
      result = P.PP.provide(this.queueName, object, property, meta);
    }

    if (!result) {
      return null;
    }

    if (listeners) {
      this.properties[property].listeners.change = this.properties[property].listeners.change.concat(listeners);
    }

    this.applyMeta(meta, result);

    return result;
  },

  /**
   * Applies meta information and actions on already created property.
   *
   * This method is called by the {{#crossLink "ProAct.ObjectCore/makeProp:method"}}{{/crossLink}} one,
   * other modules can inject logic by overriding it.
   *
   * @for ProAct.ObjectCore
   * @protected
   * @instance
   * @method applyMeta
   * @param {String|Array} meta
   *      Meta information for the property to modify with.
   * @param {ProAct.Property} property
   *      The property to update.
   */
  applyMeta: function (meta, property) {
  },

  /**
   * Sets the value of a managed property. The interesting thing here is that
   * if the property does not exist this method creates it and stores a new field in the <i>shell</i> object
   * with the passed <i>value</i>.
   * <p>
   *  The new field is reactive.
   * </p>
   *
   * ```
   *  var shell = {a: 3};
   *  var core = new ProAct.Core(shell);
   *
   *  core.set('b', function () { return this.a + 5; });
   *
   *  console.log(shell.b); // 8
   *
   *  shell.a = 5;
   *  console.log(shell.b); // 10
   * ```
   *
   * @for ProAct.ObjectCore
   * @instance
   * @method set
   * @param {String} property
   *      The name of the property to update/create.
   * @param {Object} value
   *      The value of the property to be set.
   */
  set: function (property, value) {
    var object = this.shell;

    object[property] = value;
    if (this.properties[property]) {
      return;
    }

    this.makeProp(property);
  }
});

function ObjectProbProvider () {
};

ObjectProbProvider.prototype = P.U.ex(Object.create(P.ProbProvider.prototype), {
  constructor: ObjectProbProvider,
  filter: function (data, meta) {
    return P.U.isObject(data) && !P.U.isArray(data);
  },
  provide: function (data, meta) {
    var core = new P.OC(data, meta);
    P.U.defValProp(data, '__pro__', false, false, false, core);

    core.prob();

    return data;
  }
});

P.ProbProvider.register(new ObjectProbProvider());