/* Copyright 2014 Gustav Hartvigsson <gustav.hartvigsson<at>gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Lang = imports.lang;

const ValueTypes = {
  OBJECT: typeof({}),
  NUMBER: typeof(1),
  BOOLEAN: typeof(true),
  STRING: typeof("abc"),
  UNDEFINED: undefined
}

/** @class Values
 * A special class that can hold value and can do multicast listening events.
 *
 * The reasoning behind this class is that sometimes we want to store values
 * in an easier way then the one that is in GObject.
 * 
 * Also, this class provides broadcast like capabilities for others components
 * to listen to. This is something that can not be done in the GObject
 * signal system, where only one callback can be used for each signal.
 *
 * please note that this may be very slow.
 */
const Values = new Lang.Class ({
  Name: 'Values',
  
  /**
   * Name to keep track of what object we are using.
   */
  domain: "non",
  
  
  /** @structure _value_defs
   * holds the definitions of values with their name, type, and optionally
   * user defined data that may be needed when dealing with getting and
   * setting of the values:
   * <code>
    {"value1": {type: "string",
                setter: function1,
                getter: function2,
                user_data: {any}},
     "value2": {type: "boolean",
                setter: function3,
                getter: function4,
                user_data: {any}}
    }
   </code>
   */
  _value_defs: {},
  
  /** @structure
   * Holds the holds of the tells that listeners can listen to.
   */
  _tellers: [],
  
  /** @structure _listeners
   * Holds a list of listeners in a multi map:
   * <code>
   { "tell1": [callback0, callback1, callback2], "tell2": [callback4]}
   </code>
   */
  _listeners: {},
  
  _init: function (params) {
    
    
    if (params != undefined) {
      if (params["domain"] != undefined) {
        this.domain = params["domain"];
      } else {
        print ("It is recommended that a Values object has a domain.");
      }
      
      if (params["tellers"] != undefined) {
        this._tellers = params["tellers"]
      }
      
      if (params["value_defs"] != undefined) {
        this._value_defs = params["value_defs"];
      }
    }
  },
  
  /** @method
   * adds a setter to a value.
   *
   * @param name The name of the value to add a setter to.
   * @param func the function that is used for setting of data.
   *
   * @return true on success
   * @return false on fail
   */
  add_setter: function (name, func) {
    if (name == undefined ||
        func == undefined ||
        this._value_defs[name] == undefined ||
        !(func instanceof Function)) {
      return false;
    }
    this._value_defs[name]["setting"] = func;
    
    return true;
  },
  
  /** @method add_value_def
   * adds a value defined to the value definitions list.
   *
   * @param name the name of the value to add
   * @param type the type of the value (string)
   * @param user_data any extra data that may be used with the value's setting
   *                  and getting.
   */
  add_value_def: function (name, type, user_data) {
    if (name == undefined ||
        func == undefined ||
        this._value_defs[name] == undefined ||
        !(func instanceof Function)) {
      return false;
    }
    
    return true;
  },
  
  /** @method
   * Method to get the value stored.
   */
  get_value: function (name) {
    if (name == undefined ||
        value == undefined) {
      return undefined;
    }
    if (this._value_defs[name == undefined]) {
      return undefined;
    }
    
    return this._value_defs[name]["getter"]();
  },
  
  /** @method set_value
   * Sets a value.
   */
  set_value: function (name, value) {
    if (name == undefined ||
        value == undefined) {
      return false;
    }
    if (this._value_defs[name == undefined]) {
      return false;
    }
    this._value_defs[name]["setter"] (value, this._value_defs[name]["user_data"]);
    return true;
  },
  
  /** @method tell
   * Sends a "tell" to the listeners.
   * @param name the name of the tell
   * @param user_data user data to send to the listeners
   */
  tell: function (name, user_data) {
    if (this._listeners[name] == undefined || this._tellers.indexOf (name) < 0 ) {
      return false;
    }
    for (let i = 0; i < this._listeners[name].length; i++) {
      this._listeners[name][i](this, user_data);
    }
    return true;
  },
  
  /** @method listen
   * add a listener function to the object.
   */
  listen: function (name, func) {
    if (name == undefined ||
        func == undefined ||
        this._tellers.indexOf (name) < 0 ||
        !(func instanceof Function)) {
      return false;
    }
    if (this._listeners[name] == undefined) {
      this._listeners[name] = [];
    }
    this._listeners[name].push (func);
    return true;
  }
});
