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