/**
 * @fileoverview
 * 
 * <p>Copyright (c) 2006 Peter Michaux All rights reserved<br />
 * All rights reserved <br />
 * This file is not licensed for use or redistribution.</p>
 *
 * @author Peter Michaux
 * @version 0.1 (June 2006)
 */

/**
 * @class
 * Abstract base class for PM.Draggable and PM.Target.
 * A PM.Cluster instance is a collection of PM.Duals.
 * A cluster is associated with none, one or more interaction groups.
 * A cluster can be locked so it's dragdrop functionality is suspended.
 * @constructor
 *
 * <h4>Dependencies</h4>
 * PM.Object
 * PM.Dual
 *
 * @param {HTMLElement|String|Array} elements the DOM element the instance will
 * represent. For convenience this parameter can alternately be 
 * the string id of the HTMLElement. Can also be an array with a mixture of
 * DOM elements and string ids.
 * @param {String|Array} groups the interaction group string or groups array of strings
 * that to which this Draggable will belong.
 * @extends PM.Object
 */
PM.Cluster = function(elements, groups) {
  PM.Cluster.superclass.call(this);
  
  var i;
  
  /**
   * An object used as and associative array to store
   * the duals in this cluster. Property names are the
   * internalId of each stored dual. Property values are the
   * actual duals. Do not modify this directly. Use methods like 
   * PM.Cluster.prototype.addDual to 
   * manage the duals in this cluster.
   * @type Object
   * @final
   */
  this.duals = {};
  if (!elements) {
    // TODO throw error?
  } else if (elements.constructor !== Array) { // test if not an array
    this.addDual(elements);
  } else {
    for (i=0; i<elements.length; i++) {
      this.addDual(elements[i]);
    }
  }
  
  /**
   * Flag to suspend dragdrop functionality of Cluster instance.
   * You can directly set this to
   * true or false but to check if a cluster is locked use
   * PM.Cluster.prototype.isLocked() because all clusters can
   * also be locked by setting PM.Cluster.locked.
   * @type boolean
   * @see PM.Cluster#prototype.isLocked
   */
  this.locked = false;
  
  /**
   * An object used as and associative array to store
   * the group memberships of this Cluster instance. Property names are the
   * groups to which this cluster belongs. Property values are always
   * true. Do not modify this directly. Use PM.Cluster.prototype.addToGroup to 
   * manage the group memberships of this cluster instance. 
   * @type Object
   * @final
   */
  this.groups = {};
  if (!groups) {
    // TODO set a DragDrop boolean to decide what happens in this case?
    //this.addToGroup('default');
  } else if (typeof groups === 'string') {
    this.addToGroup(groups);
  } else {
    for (i=0; i<groups.length; i++) {
      this.addToGroup(groups[i]);
    }
  }
};
PM.extend(PM.Cluster, PM.Object);

/**
 * Call to determine if this cluster is currently locked. The cluster
 * may be individually locked or all Clusters may be locked.
 * Do not check PM.Cluster.prototype.locked to determine if the
 * cluster is locked.
 * @type boolean
 * @return Boolean value. True indicates Cluster instance is locked. False
 * indicates instance is not locked.
 */
PM.Cluster.prototype.isLocked = function() {
  // TODO check if group(s?) are locked
  return PM.Cluster.locked || this.locked;
};

/**
 * Set to true to lock all dragdrop functionality.
 * Check this property directly to determine if all
 * clusters are locked due to this mechanism. To determine if
 * an individual cluster instance is locked use 
 * PM.Cluster.prototype.isLocked
 * @type boolean
 * @see #prototype.isLocked
 */
PM.Cluster.locked = false;

/**
 * Allows you to add one dual to this cluster. Unknown behavior
 * if you add an HTMLElement to a cluster more than once.
 * @param {PM.Dual|HTMLElement|String} dual The dual, HTMLElement or element id to be
 * be added to this cluster.
 * @type PM.Dual
 * @return The PM.Dual instance added to the cluster.
 */
PM.Cluster.prototype.addDual = function(dual){
  if (!dual.isDual) {
    dual = new PM.Dual(dual);
  }
  this.duals[dual.internalId] = dual;
  return dual;
};

//PM.Cluster.prototype.removeEl = function(){
//  // TODO implement
//};

/**
 * Add a new interaction group membership to this cluster.
 * @param {String} group The group to add to this cluster.
 */
PM.Cluster.prototype.addToGroup = function(group) {
  this.groups[group] = true;
  PM.Cluster._addToGroup(this, group);
};

/**
 * An object used as an associative array to cache the cluster members of all groups.
 * Each property name is the name of a group.
 * Each property values is an array of clusters belonging to the particular group. Do
 * not modify this directly. Use PM.Cluster.prototype.addToGroup to manage
 * group membership of a particular cluster instance.
 * @type Object
 * @final
 */
PM.Cluster.groups = {};

/**
 * Cache a cluster as a member of a particular group.
 * Do not call this directly. use PM.Cluster.prototype.addToGroup to manage
 * group memberships of a particular cluster instance.
 * @param {PM.Cluster} cluster The cluster to cache.
 * @param {String} group The group to which the cluster is being added.
 * @type void
 * @private
 */
PM.Cluster._addToGroup = function(cluster, group) {
  if (!PM.Cluster.groups[group]) {
    PM.Cluster.groups[group] = {};
  }
  PM.Cluster.groups[group][cluster.internalId] = cluster;
};

/**
 * Determine the union of the groups to which a set of clusters belongs.
 * This method is useful for determining which targets to prime when a user 
 * mousedowns on a draggable.
 * @param {Object} clusters An object used as an associative array storing
 * the clusters of interest. Each property value is a cluster of interest.
 * @type Object
 * @return An object used as an associative array. Property names are groups
 * in the union of the input clusters. Property values are true.
 */
PM.Cluster.groupsUnion = function(clusters) {
  var union = {};
  for (var c in clusters) {
    for (var g in clusters[c].groups) {
      union[g] = true;
    }
  }
  return union;
};

/**
 * Determine the intersection of the groups to which a set of clusters belongs.
 * This method is useful for determining which targets to prime when a user 
 * mousedowns on a draggable.
 * @param {Object} clusters An object used as an associative array storing
 * the clusters of interest. Each property value is a cluster of interest.
 * @type Object
 * @return An object used as an associative array. Property names are groups
 * in the intersection of the groups of the input clusters.
 * Property values are true.
 */
PM.Cluster.groupsIntersection = function(clusters) {
  var intersection = PM.Cluster.groupsUnion(clusters);
  for (var c in clusters) {
    for (var i in intersection) {
      if (!clusters[c].groups[i]) {
        delete intersection[i];
        break;
      }
    }
  }
  return intersection;
};