1 var EventEmitter = require('events').EventEmitter;
  2 var nowUtil = require('./nowUtil').nowUtil;
  3 var Proxy = require('./proxy');
  4 var ScopeTable = require('./scopeTable').ScopeTable;
  5 
  6 exports.initialize = function (nowjs) {
  7   var fn = require('./function').init(nowjs);
  8 
  9   /**
 10    * @name Group
 11 
 12    * @class Represents a group containing some subset of all
 13      users. Each group has its own {@link Group#now} namespace.
 14 
 15    * @property {String} groupName The name associated with the group.
 16    * @see nowjs#getGroup
 17    * @see Group#now
 18    */
 19   var Group = function (groupName) {
 20     // all users in the group
 21     this.users = {};
 22  23 
    this.groupName = groupName;
 24 
 25     this.excludes = {};
 26     /**
 27      * @name Group#now
 28      * @namespace The now namespace for this particular group. Actions
 29      * to this namespace affect all users that are members of the
 30      * group. All functions that are called will be called in the
 31      * context of each individual user in the group.
 32 
 33      * @example everyone.now.prop = 42;
 34      * @example everyone.now.func = function () {
 35      *  console.log('hello!');
 36      * };
 37      */
 38     this.now = Proxy.wrap(this);
 39     // group scope table
 40     this.scopeTable = new ScopeTable();
 41   };
 42 
 43   Group.prototype.__proto__ = EventEmitter.prototype;
 44 
 45   /**
 46    * @name count
 47    * @function
 48    * @memberOf Group#
 49 
 50    * @description Used to find the cardinality of the group (how
 51    * many users it contains).
 52 
 53    * @param {Function} callback Called with a Number corresponding to
 54    * the group's user count.
 55 
 56    * @example everyone.count(function (ct) {
 57    *   console.log(ct);
 58    * });
 59    */
 60   Group.prototype.count = function (callback) {
 61     callback(Object.keys(this.users).length);
 62   };
 63 
 64   /**
 65    * @name getUsers
 66    * @function
 67    * @memberOf Group#
 68 
 69    * @description Used to retrieve a list of the client IDs
 70    * corresponding to all users in the group.
 71 
 72    * @param {Function} callback Called with an Array of Strings
 73    * corresponding to the client IDs of all users in the group.
 74 
 75    * @example everyone.getUsers(function (users) {
 76    *   for (var i = 0; i < users.length; i++) console.log(users[i]);
 77    * });
 78 
 79    * @see nowjs#getClient
 80    */
 81   Group.prototype.getUsers = function (callback) {
 82     callback(Object.keys(this.users));
 83   };
 84 
 85   /**
 86    * @name connected
 87    * @function
 88    * @memberOf Group#
 89    * @deprecated As of 0.7.0. Use nowjs:connect instead.
 90    */
 91   Group.prototype.connected = function (callback) {
 92     nowjs.on('connect', callback);
 93   };
 94 
 95   /**
 96    * @name disconnected
 97    * @function
 98    * @memberOf Group#
 99    * @deprecated As of 0.7.0. Use nowjs:disconnect instead.
100    */
101   Group.prototype.disconnected = function (callback) {
102     nowjs.on('disconnect', callback);
103   };
104 
105   /**
106    * @name addUser
107    * @function
108    * @memberOf Group#
109    * @description Adds the user identified by clientId to this group.
110 
111    * @param {String} clientId The client ID associated with the target
112    * user.
113 
114    * @example everyone.addUser('1234567890');
115    */
116   Group.prototype.addUser = function (clientId) {
117     var self = this;
118     this.hasClient(clientId, function (hasClient) {
119       if (hasClient) {
120         return;
121       }
122       if (self.excludes[clientId]) {
123         self.excludes[clientId] = false;
124       }
125       nowjs.getClient(clientId, function () {
126         // Scoping note: `self` refers to the group, `this` refers to
127         // the new client.
128         self.users[this.user.clientId] = this;
129         this.groups[self.groupName] = self;
130 
131         // Send the client any new variables that it does not already have
132         var toSend = {};
133         var keys = Object.keys(self.scopeTable.data);
134         var key, val;
135         for (var i = 0, ll = keys.length; i < ll; i++) {
136           key = keys[i];
137           val = self.scopeTable.get(key);
138           if (val !== nowUtil.noop && !Array.isArray(val) &&
139               !nowUtil.hasProperty(this.scopeTable.data, key)) {
140             toSend[key] = nowUtil.getValOrFqn(val, key);
141           }
142         }
143 
144         if (self.isSuperGroup || !nowUtil.isEmptyObj(toSend)) {
145           this.socket.emit('rv', toSend);
146         }
147         var user = nowUtil.clone(this, {'_events': self._events});
148 
149         /**
150 151          * @name Group#join
152          * @event
153          * @description Called in the context of a user who has just been
154          * added to the group.
155 
156          * @example everyone.on('join', function () {
157          *   otherGroup.addUser(this.user.clientId);
158          * });
159          */
160         self.emit.call(user, 'join');
161       });
162     });
163   };
164 
165   /**
166    * @name removeUser
167    * @function
168    * @memberOf Group#
169    * @version 0.7.0
170    * @description Removes the user identified by clientId from this group.
171 
172    * @param {String} clientId The client ID associated with the target
173    * user.
174 
175    * @example otherGroup.removeUser('1234567890');
176    */
177   Group.prototype.removeUser = function (clientId) {
178     var self = this;
179     this.hasClient(clientId, function (hasClient) {
180       if (!hasClient) {
181         return;
182       }
183       var user = nowUtil.clone(self.users[clientId], {'_events': self._events});
184 
185       /**
186        * @name Group#leave
187        * @event
188        * @version 0.7.0
189        * @description Called in the context of a user who has just been
190        * removed from the group.
191 
192        * @example otherGroup.on('leave', function () {
193        *   // Store the context, i.e. the user who has just left.
194        *   var self = this;
195        *   // Check that the user is still connected to the server.
196        *   everyone.hasClient(this.user.clientId, function (bool) {
197        *     if (bool) {
198        *       // Send parting words to the client.
199        *       this.now.receive('SERVER', 'Goodbye. I'll miss you dearly.');
200        *     }
201        *   });
202        * });
203        */
204       self.emit.call(user, 'leave');
205       // Delete all remote functions that are part of this group from
206       // the user.
207       var fqns = Object.keys(self.scopeTable.data);
208       for (var i = 0; i < fqns.length; i++) {
209         if (typeof self.scopeTable.data[fqns[i]] === 'function' &&
210             self.scopeTable.data[fqns[i]] !== nowUtil.noop &&
211             user.scopeTable.data[fqns[i]] === undefined) {
212           // Tell the user to delete his function.
213           user.deleteVar(fqns[i]);
214         }
215       }
216       delete self.users[clientId];
217       delete user.groups[self.groupName];
218     });
219   };
220 
221   /**
222    * @memberOf Group#
223    * @function
224    * @name exclude
225    * @version 0.7.0
226 
227    * @description Returns a new Group that is identical to the calling
228    * group, except with the specified clients excluded. The returned
229    * group automatically updates to accommodate any changes made to
230    * its parent group.
231 
232    * @param {Array} clientIds A list of client IDs corresponding to
233    * clients to exclude.
234 
235    * @example everyone.now.distribute = function (msg) {
236    *   everyone.exclude([this.user.clientId]).now.receive(this.now.name, msg);
237    * };
238    * @example factionOne.now.distribute = function (msg) {
239    *   factionTwo.getUsers(function (users) {
240    *     factionOne.exclude(users).now.receive(this.user.clientId, msg);
241    *   });
242    * };
243 
244    * @type Group
245    */
246   Group.prototype.exclude = function (clientIds) {
247     var excludes = {};
248     if(typeof clientIds === 'string') {
249       return this.exclude([clientIds]);
250     } else {
251       for (var i = 0; i < clientIds.length; i++) {
252         excludes[clientIds[i]] = true;
253       }
254     }
255     var group = nowUtil.clone(this, {excludes: nowUtil.clone(this.excludes, excludes)});
256     group.now = Proxy.wrap(group);
257     return group;
258   };
259 
260   /**
261    * @memberOf Group#
262    * @function
263    * @name hasClient
264 
265    * @description Used to determine a given user's membership in the
266 267    * group.
268 
269    * @param {String} clientId The client ID associated with the target
270    * user.
271 
272    * @param {Function} callback Called with a Boolean indicating the
273    * user's membership in the group.
274 
275    * @example group.hasClient('1234567890', function (bool) {
276    *   if (bool) {
277    *     console.log('User is a member of `group`.');
278    *   }
279    * });
280    */
281   Group.prototype.hasClient = function (clientId, callback) {
282     callback(this.users[clientId] !== undefined);
283 284   };
285 
286   /**
287    * @private
288    */
289   Group.prototype.get = function (fqn) {
290     var value = this.scopeTable.get(fqn);
291     // If this is a regular group, look in `everyone` for the value
292     if (value === undefined && !this.isSuperGroup) {
293       value = nowjs.getGroup('everyone').scopeTable.get(fqn);
294     }
295 
296     // If this is a function, return a multicaller.
297     if (typeof value === 'function') {
298       var group = nowUtil.clone(this, {fqn: fqn});
299       value = fn.multicall.bind(group);
300     }
301     return value;
302   };
303 
304   /**
305    * @private
306    */
307   Group.prototype.deleteVar = function (fqn) {
308     var obj = nowUtil.clone(this, {fqn: fqn});
309     nowjs.emit('groupdel', obj);
310   };
311 
312   /**
313    * @private
314    */
315   Group.prototype.set = function (fqn, val) {
316     var obj = nowUtil.clone(this, {fqn: fqn, val: val});
317     nowjs.emit('grouprv', obj);
318   };
319 
320   return Group;
321 };
322 
323 /**
324  * @name Group#connect
325  * @event
326  * @deprecated As of 0.7.0. Use nowjs#connect instead.
327  * @description Called in the context of a user when the server
328  * first receives a message from the given user.
329  */
330 
331 /**
332  * @name Group#disconnect
333  * @event
334  * @deprecated As of 0.7.0. Use nowjs#disconnect instead.
335  * @description Called in the context of a user who has just
336  * disconnected from the server.
337  */
338