/// <summary>
        /// This is called every so often to check if users Names or Email address has changed, or User has been Deleted
        /// </summary>
        /// <param name="campfireInfo"></param>
        private static void Work_ScanForUserChanges(CampfireState campfireInfo, ICampfireAPI api)
        {
            // iterate of all known users... and see if name or email address has changed
            // Must do this in a thread safe fashion by first getting all the ids.
            List <int> userIds = campfireInfo.UserIds();

            foreach (int uid in userIds)
            {
                User user = api.GetUser(uid);
                if (user != null && !string.IsNullOrEmpty(user.Name) && !string.IsNullOrEmpty(user.Email))
                {
                    campfireInfo.UpdateUser(user.Id, user.Name, user.Email);
                }
                else
                {
                    campfireInfo.DeleteUser(uid);
                }
            }
        }
        /// <summary>
        /// This is called every so often to update the known list of rooms
        /// </summary>
        /// <param name="campfireInfo"></param>
        private static void Work_ScanForAddOrRemoveRooms(CampfireState campfireInfo, ICampfireAPI api)
        {
            List <int> roomIds = campfireInfo.RoomIds();

            List <Room> rooms = api.Rooms();

            foreach (Room r in rooms)
            {
                // since r.id exists, remove it from 'roomIds'
                roomIds.Remove(r.Id);
            }
            List <int> roomsToDelete = roomIds;

            foreach (int roomToDeleteId in roomsToDelete)
            {
                campfireInfo.DeleteRoom(roomToDeleteId);
            }

            campfireInfo.AddRooms(rooms);
        }
        /// <summary>
        /// This is called every so often to check if users Names or Email address has changed, or User has been Deleted
        /// </summary>
        /// <param name="campfireInfo"></param>
        private static void Work_ScanForUserChanges(CampfireState campfireInfo, ICampfireAPI api)
        {
            // iterate of all known users... and see if name or email address has changed
            // Must do this in a thread safe fashion by first getting all the ids.
            List<int> userIds = campfireInfo.UserIds();

            foreach (int uid in userIds)
            {
                User user = api.GetUser(uid);
                if (user != null && !string.IsNullOrEmpty(user.Name) && !string.IsNullOrEmpty(user.Email))
                {
                    campfireInfo.UpdateUser(user.Id, user.Name, user.Email);
                }
                else
                {
                    campfireInfo.DeleteUser(uid);
                }
            }
        }
        /// <summary>
        /// This is called every so often to update the known list of rooms
        /// </summary>
        /// <param name="campfireInfo"></param>
        private static void Work_ScanForAddOrRemoveRooms(CampfireState campfireInfo, ICampfireAPI api)
        {
            List<int> roomIds = campfireInfo.RoomIds();

            List<Room> rooms = api.Rooms();

            foreach (Room r in rooms)
            {
                // since r.id exists, remove it from 'roomIds'
                roomIds.Remove(r.Id);
            }
            List<int> roomsToDelete = roomIds;
            foreach (int roomToDeleteId in roomsToDelete)
            {
                campfireInfo.DeleteRoom(roomToDeleteId);
            }

            campfireInfo.AddRooms(rooms);
        }
 private static void ProcessEnterMessage(CampfireState campfireInfo, Message msg, ICampfireAPI api)
 {
     // remove any pending notifications for this user in the room in which this Enter message appeared
     campfireInfo.RemovePendingNotification(msg.UserId, msg.RoomId, msg.PostedAt, true);
 }
        private static void ProcessTextMessage(CampfireState campfireInfo, Message msg, ICampfireAPI api)
        {
            // The person that posted this message... If they have a pending notification in the room... then cancel it... they've spoken
            campfireInfo.RemovePendingNotification(msg.UserId, msg.RoomId, msg.PostedAt, true);

            IList<CampfireState.UserInfo> allUsers = campfireInfo.Users;

            IList<CampfireState.UserReference> lazyNotificationUsers;
            IList<CampfireState.UserReference> immediateNotificationUsers;
            IList<CampfireState.UserReference> smokeSignalReferences;
            Utils.FindUserReferences(msg.Body, allUsers, out lazyNotificationUsers, out immediateNotificationUsers, out smokeSignalReferences);

            CampfireState.UserInfo source = allUsers.FirstOrDefault(u => u.Id == msg.UserId);
            CampfireState.RoomInfo room = CampfireState.Instance.Rooms.FirstOrDefault(r => r.Id == msg.RoomId);

            // special smoke signal commands only make sense if a legitimate user issued them
            if (source != null)
            {
                foreach (CampfireState.UserReference ri in smokeSignalReferences)
                {
                    ri.SourceUser = source;
                    ri.Room = room;
                    ProcessSmokeSignalCommands(source.Id, ri);
                }
            }

            foreach (CampfireState.UserReference ri in immediateNotificationUsers)
            {
                ri.SourceUser = source;
                ri.Room = room;
                campfireInfo.AddPendingNotification(ri, msg.PostedAt.AddSeconds(0));
            }
            foreach (CampfireState.UserReference ri in lazyNotificationUsers)
            {
                ri.SourceUser = source;
                ri.Room = room;

                int delay = ri.TargetUser.DelayInMinutes > 0 ? ri.TargetUser.DelayInMinutes : SmokeSignalConfig.Instance.DelayBeforeSmokeSignalInMinutes;
                campfireInfo.AddPendingNotification(ri, msg.PostedAt.AddSeconds(delay * 60));
            }
        }
        private static void CheckForUnknownUserOrRoom(CampfireState campfireInfo, Message msg, ICampfireAPI api)
        {
            // If this userId isn't already known...
            if (!campfireInfo.IsUserKnown(msg.UserId))
            {
                // fetch all the user Info and then add
                User newUser = api.GetUser(msg.UserId);
                if ((newUser != null) && (newUser.Type == User.UserType.Member) && !string.IsNullOrEmpty(newUser.Name) && !string.IsNullOrEmpty(newUser.Email))
                {
                    Utils.TraceVerboseMessage(string.Format("Found a new User: {0}, Id: {1}", newUser.Name, newUser.Id));
                    campfireInfo.AddUser(newUser.Id, newUser.Name, newUser.Email, string.Empty);
                }
            }

            // If this roomId isn't already known...
            if (!campfireInfo.IsRoomKnown(msg.RoomId))
            {
                // fetch all the user Info and then add
                Room newRoom = api.GetRoom(msg.RoomId);
                if (newRoom != null)
                {
                    Utils.TraceVerboseMessage(string.Format("Found a new Room: {0}, Id: {1}", newRoom.Name, newRoom.Id));
                    campfireInfo.AddRoom(newRoom.Id, newRoom.Name, 0);
                }
            }
        }
        private static void ProcessMessages_ProcessQueuedMessages(CampfireState campfireInfo, ICampfireAPI api)
        {
            /*
             For each message that comes from any room:
                 Remove any entry in “PendingNotification” collection where PendingNotification.user is message.author (and room's match)
                     (e.g he’s already participating after being called out so no need to notify)
                 If there’s a User Reference match
                     Enter (user, time, room) into a PendingNotification collection 
                         (unless user/room already exists in collection)
                    
             For each entry in PendingNotification
                 If entry.timestamp  is < Now – entry.user.ConfiguredDelay then
                      Send that user a notification that he’s been called out in entry.room
            */
            
            Message msg;
            while ((msg = campfireInfo.PopMessage()) != null)
            {
                CheckForUnknownUserOrRoom(campfireInfo, msg, api);

                if (msg.Type == Message.MType.EnterMessage)
                {
                    ProcessEnterMessage(campfireInfo, msg, api);
                }
                else if (msg.Type == Message.MType.TextMessage)
                {
                    ProcessTextMessage(campfireInfo, msg, api);
                }

            }

            TriggerNotifications(campfireInfo);         // Now look to fire any pending notifications
        }
        private static void ProcessMessages_FetchNewMessages(CampfireState campfireInfo, ICampfireAPI api)
        {
            IList<CampfireState.RoomInfo> rooms = campfireInfo.Rooms;

            foreach (CampfireState.RoomInfo room in rooms)
            {
                List<Message> msgs;
                try
                {
                    msgs = api.RecentMessages(room.Id, room.LastMessageId, Message.MType.TextMessage | Message.MType.EnterMessage);
                }
                catch (System.Net.WebException ex)
                {
                    // oops. Let's break out and then try again later
                    Utils.TraceException(TraceLevel.Warning, ex, "Exception calling API.RecentMessages");
                    break;
                }

                int lastMsgId = 0;
                foreach (Message msg in msgs)
                {
                    lastMsgId = msg.Id;
                    QueueMessage(campfireInfo, msg);
                }

                if (lastMsgId != 0)
                {
                    // remember that we've now processed through this message Id. So next time we're only fetch newer messages
                    campfireInfo.UpdateLastMessageId(room.Id, lastMsgId);
                }
            }
        }
 /// <summary>
 /// Retrieve all 'new' messages from each room. And then "process" them.
 /// </summary>
 /// <param name="campfireInfo"></param>
 /// <param name="api"></param>
 private static void Work_ProcessNewMessagesForAllRooms(CampfireState campfireInfo, ICampfireAPI api)
 {
     /*
      * 2 phases:
      *  - fetch all new message for all the rooms. Plase messages into appropriate queue
      *  - Process all those queue up messages, in order
      *  
      * Structuring this as 2 phases in case they are even executes by different threads. E.g. a thread for each room "waiting" on messages.
      */
     ProcessMessages_FetchNewMessages(campfireInfo, api);
     ProcessMessages_ProcessQueuedMessages(campfireInfo, api);
 }
 private static void ProcessEnterMessage(CampfireState campfireInfo, Message msg, ICampfireAPI api)
 {
     // remove any pending notifications for this user in the room in which this Enter message appeared
     campfireInfo.RemovePendingNotification(msg.UserId, msg.RoomId, msg.PostedAt, true);
 }
        private static void ProcessTextMessage(CampfireState campfireInfo, Message msg, ICampfireAPI api)
        {
            // The person that posted this message... If they have a pending notification in the room... then cancel it... they've spoken
            campfireInfo.RemovePendingNotification(msg.UserId, msg.RoomId, msg.PostedAt, true);

            IList <CampfireState.UserInfo> allUsers = campfireInfo.Users;

            IList <CampfireState.UserReference> lazyNotificationUsers;
            IList <CampfireState.UserReference> immediateNotificationUsers;
            IList <CampfireState.UserReference> smokeSignalReferences;

            Utils.FindUserReferences(msg.Body, allUsers, out lazyNotificationUsers, out immediateNotificationUsers, out smokeSignalReferences);

            CampfireState.UserInfo source = allUsers.FirstOrDefault(u => u.Id == msg.UserId);
            CampfireState.RoomInfo room   = CampfireState.Instance.Rooms.FirstOrDefault(r => r.Id == msg.RoomId);

            // special smoke signal commands only make sense if a legitimate user issued them
            if (source != null)
            {
                foreach (CampfireState.UserReference ri in smokeSignalReferences)
                {
                    ri.SourceUser = source;
                    ri.Room       = room;
                    ProcessSmokeSignalCommands(source.Id, ri);
                }
            }

            foreach (CampfireState.UserReference ri in immediateNotificationUsers)
            {
                ri.SourceUser = source;
                ri.Room       = room;
                campfireInfo.AddPendingNotification(ri, msg.PostedAt.AddSeconds(0));
            }
            foreach (CampfireState.UserReference ri in lazyNotificationUsers)
            {
                ri.SourceUser = source;
                ri.Room       = room;

                int delay = ri.TargetUser.DelayInMinutes > 0 ? ri.TargetUser.DelayInMinutes : SmokeSignalConfig.Instance.DelayBeforeSmokeSignalInMinutes;
                campfireInfo.AddPendingNotification(ri, msg.PostedAt.AddSeconds(delay * 60));
            }
        }
        private static void CheckForUnknownUserOrRoom(CampfireState campfireInfo, Message msg, ICampfireAPI api)
        {
            // If this userId isn't already known...
            if (!campfireInfo.IsUserKnown(msg.UserId))
            {
                // fetch all the user Info and then add
                User newUser = api.GetUser(msg.UserId);
                if ((newUser != null) && (newUser.Type == User.UserType.Member) && !string.IsNullOrEmpty(newUser.Name) && !string.IsNullOrEmpty(newUser.Email))
                {
                    Utils.TraceVerboseMessage(string.Format("Found a new User: {0}, Id: {1}", newUser.Name, newUser.Id));
                    campfireInfo.AddUser(newUser.Id, newUser.Name, newUser.Email, string.Empty);
                }
            }

            // If this roomId isn't already known...
            if (!campfireInfo.IsRoomKnown(msg.RoomId))
            {
                // fetch all the user Info and then add
                Room newRoom = api.GetRoom(msg.RoomId);
                if (newRoom != null)
                {
                    Utils.TraceVerboseMessage(string.Format("Found a new Room: {0}, Id: {1}", newRoom.Name, newRoom.Id));
                    campfireInfo.AddRoom(newRoom.Id, newRoom.Name, 0);
                }
            }
        }
        private static void ProcessMessages_ProcessQueuedMessages(CampfireState campfireInfo, ICampfireAPI api)
        {
            /*
             * For each message that comes from any room:
             *   Remove any entry in “PendingNotification” collection where PendingNotification.user is message.author (and room's match)
             *       (e.g he’s already participating after being called out so no need to notify)
             *   If there’s a User Reference match
             *       Enter (user, time, room) into a PendingNotification collection
             *           (unless user/room already exists in collection)
             *
             * For each entry in PendingNotification
             *   If entry.timestamp  is < Now – entry.user.ConfiguredDelay then
             *        Send that user a notification that he’s been called out in entry.room
             */

            Message msg;

            while ((msg = campfireInfo.PopMessage()) != null)
            {
                CheckForUnknownUserOrRoom(campfireInfo, msg, api);

                if (msg.Type == Message.MType.EnterMessage)
                {
                    ProcessEnterMessage(campfireInfo, msg, api);
                }
                else if (msg.Type == Message.MType.TextMessage)
                {
                    ProcessTextMessage(campfireInfo, msg, api);
                }
            }

            TriggerNotifications(campfireInfo);         // Now look to fire any pending notifications
        }
        private static void ProcessMessages_FetchNewMessages(CampfireState campfireInfo, ICampfireAPI api)
        {
            IList <CampfireState.RoomInfo> rooms = campfireInfo.Rooms;

            foreach (CampfireState.RoomInfo room in rooms)
            {
                List <Message> msgs;
                try
                {
                    msgs = api.RecentMessages(room.Id, room.LastMessageId, Message.MType.TextMessage | Message.MType.EnterMessage);
                }
                catch (System.Net.WebException ex)
                {
                    // oops. Let's break out and then try again later
                    Utils.TraceException(TraceLevel.Warning, ex, "Exception calling API.RecentMessages");
                    break;
                }

                int lastMsgId = 0;
                foreach (Message msg in msgs)
                {
                    lastMsgId = msg.Id;
                    QueueMessage(campfireInfo, msg);
                }

                if (lastMsgId != 0)
                {
                    // remember that we've now processed through this message Id. So next time we're only fetch newer messages
                    campfireInfo.UpdateLastMessageId(room.Id, lastMsgId);
                }
            }
        }
 /// <summary>
 /// Retrieve all 'new' messages from each room. And then "process" them.
 /// </summary>
 /// <param name="campfireInfo"></param>
 /// <param name="api"></param>
 private static void Work_ProcessNewMessagesForAllRooms(CampfireState campfireInfo, ICampfireAPI api)
 {
     /*
      * 2 phases:
      *  - fetch all new message for all the rooms. Plase messages into appropriate queue
      *  - Process all those queue up messages, in order
      *
      * Structuring this as 2 phases in case they are even executes by different threads. E.g. a thread for each room "waiting" on messages.
      */
     ProcessMessages_FetchNewMessages(campfireInfo, api);
     ProcessMessages_ProcessQueuedMessages(campfireInfo, api);
 }