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          * @name Group#join
151          * @event
152          * @description Called in the context of a user who has just been
153          * added to the group.
154 
155          * @example everyone.on('join', function () {
156          *   otherGroup.addUser(this.user.clientId);
157          * });
158          */
159         self.emit.call(user, 'join');
160       });
161     });
162   };
163 
164   /**
165    * @name removeUser
166    * @function
167    * @memberOf Group#
168    * @version 0.7.0
169    * @description Removes the user identified by clientId from this group.
170 
171    * @param {String} clientId The client ID associated with the target
172    * user.
173 
174    * @example otherGroup.removeUser('1234567890');
175    */
176   Group.prototype.removeUser = function (clientId) {
177     var self = this;
178     this.hasClient(clientId, function (hasClient) {
179       if (!hasClient) {
180         return;
181       }
182       var user = nowUtil.clone(self.users[clientId], {'_events': self._events});
183 
184       /**
185        * @name Group#leave
186        * @event
187        * @version 0.7.0
188        * @description Called in the context of a user who has just been
189        * removed from the group.
190 
191        * @example otherGroup.on('leave', function () {
192        *   // Store the context, i.e. the user who has just left.
193        *   var self = this;
194        *   // Check that the user is still connected to the server.
195        *   everyone.hasClient(this.user.clientId, function (bool) {
196        *     if (bool) {
197        *       // Send parting words to the client.
198        *       this.now.receive('SERVER', 'Goodbye. I'll miss you dearly.');
199        *     }
200        *   });
201        * });
202        */
203       self.emit.call(user, 'leave');
204       // Delete all remote functions that are part of this group from
205       // the user.
206       var fqns = Object.keys(self.scopeTable.data);
207       for (var i = 0; i < fqns.length; i++) {
208         if (typeof self.scopeTable.data[fqns[i]] === 'function' &&
209             self.scopeTable.data[fqns[i]] !== nowUtil.noop &&
210             user.scopeTable.data[fqns[i]] === undefined) {
211           // Tell the user to delete his function.
212           user.deleteVar(fqns[i]);
213         }
214       }
215       delete self.users[clientId];
216       delete user.groups[self.groupName];
217     });
218   };
219 
220   /**
221    * @memberOf Group#
222    * @function
223    * @name exclude
224    * @version 0.7.0
225 
226    * @description Returns a new Group that is identical to the calling
227    * group, except with the specified clients excluded. The returned
228    * group automatically updates to accommodate any changes made to
229    * its parent group.
230 
231    * @param {Array} clientIds A list of client IDs corresponding to
232    * clients to exclude.
233 
234    * @example everyone.now.distribute = function (msg) {
235    *   everyone.exclude([this.user.clientId]).now.receive(this.now.name, msg);
236    * };
237    * @example factionOne.now.distribute = function (msg) {
238    *   factionTwo.getUsers(function (users) {
239    *     factionOne.exclude(users).now.receive(this.user.clientId, msg);
240    *   });
241    * };
242 
243    * @type Group
244    */
245   Group.prototype.exclude = function (clientIds) {
246     var excludes = {};
247     if(typeof clientIds === 'string') {
248       return this.exclude([clientIds]);
249     } else {
250       for (var i = 0; i < clientIds.length; i++) {
251         excludes[clientIds[i]] = true;
252       }
253     }
254     var group = nowUtil.clone(this, {excludes: nowUtil.clone(this.excludes, excludes)});
255     group.now = Proxy.wrap(group);
256     return group;
257   };
258 
259   /**
260    * @memberOf Group#
261    * @function
262    * @name hasClient
263 
264    * @description Used to determine a given user's membership in the
265    * group.
266 
267    * @param {String} clientId The client ID associated with the target
268    * user.
269 
270    * @param {Function} callback Called with a Boolean indicating the
271    * user's membership in the group.
272 
273    * @example group.hasClient('1234567890', function (bool) {
274    *   if (bool) {
275    *     console.log('User is a member of `group`.');
276    *   }
277    * });
278    */
279   Group.prototype.hasClient = function (clientId, callback) {
280     callback(this.users[clientId] !== undefined);
281   };
282 
283   /**
284    * @private
285    */
286   Group.prototype.get = function (fqn) {
287     var value = this.scopeTable.get(fqn);
288     // If this is a regular group, look in `everyone` for the value
289     if (value === undefined && !this.isSuperGroup) {
290       value = nowjs.getGroup('everyone').scopeTable.get(fqn);
291     }
292 
293     // If this is a function, return a multicaller.
294     if (typeof value === 'function') {
295       var group = nowUtil.clone(this, {fqn: fqn});
296       value = fn.multicall.bind(group);
297     }
298     return value;
299   };
300 
301   /**
302    * @private
303    */
304   Group.prototype.deleteVar = function (fqn) {
305     var obj = nowUtil.clone(this, {fqn: fqn});
306     nowjs.emit('groupdel', obj);
307   };
308 
309   /**
310    * @private
311    */
312   Group.prototype.set = function (fqn, val) {
313     var obj = nowUtil.clone(this, {fqn: fqn, val: val});
314     nowjs.emit('grouprv', obj);
315   };
316 
317   return Group;
318 };
319 
320 /**
321  * @name Group#connect
322  * @event
323  * @deprecated As of 0.7.0. Use nowjs#connect instead.
324  * @description Called in the context of a user when the server
325  * first receives a message from the given user.
326  */
327 
328 /**
329  * @name Group#disconnect
330  * @event
331  * @deprecated As of 0.7.0. Use nowjs#disconnect instead.
332  * @description Called in the context of a user who has just
333  * disconnected from the server.
334  */
335