//      public override bool SaveFiles()
//      {
//         if(MyExtensions.MySerialize.SaveObject<List
//      }

      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();

         if (command.Command == "drawsubmit")
         {
            //unsavedMessages.Add(new DrawingInfo() { Drawing = command.Arguments[0], User = user, postTime = DateTime.Now });
            UserMessageJSONObject drawingMessage = new UserMessageJSONObject(user, command.Arguments[0], command.tag);
            drawingMessage.encoding = Nickname;
            drawingMessage.spamValue = 0.50;
            drawingMessage.SetUnspammable();
            outputs.Add(drawingMessage);
         }

         return outputs; //base.ProcessCommand(command, user, users);
      }
//      protected override void OnError(ErrorEventArgs e)
//      {
//         if(e.Message != null)
//            Logger.Error("UID: " + uid + " - " + e.Message, "WebSocket");
//         if(e.Exception != null)
//            Logger.Error(e.Exception.ToString(), "WebSocket");
//
//         //OK let's see what happens here
//         //OnClose(null);
//         //base.OnError(e);
//      }

      //I guess this is WHENEVER it receives a message?
      public override void ReceivedMessage(string rawMessage)
      {
         //Log("Got message: " + rawMessage, MyExtensions.Logging.LogLevel.Debug);

         ResponseJSONObject response = new ResponseJSONObject();
         response.result = false;
         dynamic json = new Object();
         string type = "";

         //You HAVE to do this, even though it seems pointless. Users need to show up as banned immediately.
         ThisUser.PullInfoFromQueryPage();

         //Before anything else, log the amount of incoming data
         if (!string.IsNullOrEmpty(rawMessage))
         {
            manager.Bandwidth.AddIncoming(rawMessage.Length + HeaderSize);
         }

         //First, just try to parse the JSON they gave us. If it's absolute
         //garbage (or just not JSON), let them know and quit immediately.
         try
         {
            json = JsonConvert.DeserializeObject(rawMessage);
            type = json.type;
            response.from = type;
         }
         catch
         {
            response.errors.Add("Could not parse JSON");
         }

         //If we got a bind message, let's try to authorize this channel.
         if (type == "bind")
         {
            if (uid > 0)
            {
               response.errors.Add("Received another bind message, but you've already been authenticated.");
            }
            else
            {
               try
               {
                  //First, gather information from the JSON. This is so that if
                  //the json is invalid, it will fail as soon as possible
                  string key = (string)json.key;
                  int newUser = (int)json.uid;

                  //Oops, username was invalid
                  if (newUser <= 0)
                  {
                     Log("Tried to bind a bad UID: " + newUser);
                     response.errors.Add("UID was invalid");
                  }
                  else
                  {
                     //List<Chat> removals;
                     string error;

                     if (!manager.CheckAuthentication(newUser, key, out error))  //newUser, key, out removals, out error))
                     {
                        response.errors.Add(error);
                     }
                     else
                     {
                        //Before we do anything, remove other chatting sessions
                        foreach (Chat removeChat in GetAllUsers().Select(x => (Chat)x).Where(x => x.UID == newUser && x != this))
                           removeChat.CloseSelf();
                        
                           //Sessions.CloseSession(removeChat.ID);
                     
                        uid = newUser;

                        //BEFORE adding, broadcast the "whatever has entered the chat" message
                        if (ThisUser.ShowMessages)
                        {
                           manager.Broadcast(QuickParams(ChatTags.Join), new SystemMessageJSONObject(),
                              new List<Chat> { this });
                        }
                        
                        MySend(NewSystemMessageFromTag(QuickParams(ChatTags.Welcome)).ToString());
                        ChatTags enterSpamWarning = ThisUser.JoinSpam();

                        if (enterSpamWarning != ChatTags.None)
                           MySend(NewWarningFromTag(QuickParams(enterSpamWarning)).ToString());

                        //BEFORE sending out the user list, we need to perform onPing so that it looks like this user is active
                        sessionID = ThisUser.PerformOnChatEnter();
//                        if (!ThisUser.PerformOnChatEnter())
//                           Log("Invalid session entry. Sessions may be broken", LogLevel.Warning);

                        manager.BroadcastUserList();

                        Log("Authentication complete: UID " + uid + " maps to username " + ThisUser.Username +
                           (ThisUser.CanStaffChat ? "(staff)" : ""));
                        response.result = true;

                        List<JSONObject> outputs = new List<JSONObject>();
                        Dictionary<int, UserInfo> currentUsers = manager.UsersForModules();

                        //Also do some other crap
                        foreach (Module module in manager.GetModuleListCopy())
                        {
                           if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait))
                           {
                              try
                              {
                                 outputs.AddRange(module.OnUserJoin(currentUsers[ThisUser.UID], currentUsers));
                              }
                              finally
                              {
                                 Monitor.Exit(module.Lock);
                              }
                           }
                           else
                           {
                              Log("Skipped " + module.ModuleName + " join processing", 
                                 MyExtensions.Logging.LogLevel.Warning);
                           }
                        }

                        OutputMessages(outputs, ThisUser.UID);

                        //Finally, output the "Yo accept dis" thing if they haven't already.
                        if(!ThisUser.AcceptedPolicy)
                        {
                           MessageListJSONObject emptyMessages = new MessageListJSONObject();
                           UserListJSONObject emptyUsers = new UserListJSONObject();
                           ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy);
                           ModuleJSONObject accept = new ModuleJSONObject("\nYou must accept this chat policy before " +
                              "using the chat. Type /accept if you accept the chat policy\n");
                           MySend(emptyMessages.ToString(), true);
                           MySend(emptyUsers.ToString(), true);
                           MySend(policy.ToString(), true);
                           MySend(accept.ToString(), true);
                        }
                        else if(ThisUser.ShouldPolicyRemind)
                        {
                           ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy);
                           MySend(policy.ToString());
                           ThisUser.PerformOnReminder();
                        }

                        //Now set up the IRC relay. Oh boy, let's hope this works!
                        /*relay = new SimpleIRCRelay(manager.IrcServer, manager.IrcChannel, ThisUser.Username, Logger);
                        relay.ConnectAsync();*/
                        //relay.IRCRelayMessageEvent += OnIrcRelayMessage;
                     }
                  }
               }
               catch
               {
                  response.errors.Add("BIND message was missing fields");
               }
            }
         }
         else if (type == "ping")
         {
            lastPing = DateTime.Now;

            bool active = true;

            try
            {
               active = (bool)json.active;
               //relay.Ping();
            }
            catch (Exception messageError)
            {
               response.errors.Add("Internal server error: " + messageError/*.Message*/);
            }

            ThisUser.PerformOnPing(active);
            UpdateActiveUserList(null, null);
         }
         else if (type == "message")
         {
            try
            {
               //First, gather information from the JSON. This is so that if
               //the json is invalid, it will fail as soon as possible
               string key = json.key;
               string message = System.Security.SecurityElement.Escape((string)json.text);
               string tag = json.tag;

               //These first things don't increase spam score in any way
               if (string.IsNullOrWhiteSpace(message))
               {
                  response.errors.Add("No empty messages please");
               }
               else if (!manager.CheckKey(uid, key))
               {
                  Log("Got invalid key " + key + " from " + UserLogString);
                  response.errors.Add("Your key is invalid");
               }
               else if (!ThisUser.AcceptedPolicy)
               {
                  if(message != "/accept")
                  {
                     response.errors.Add("The only command available right now is /accept");
                  }
                  else
                  {
                     ModuleJSONObject acceptSuccess = new ModuleJSONObject("You have accepted the SmileBASIC Source " +
                        "chat policy. Please use the appropriate chat tab for discussion about SmileBASIC or off-topic " +
                        "subjects!");
                     MySend(acceptSuccess.ToString(), true);

                     Thread.Sleep(2000);
                     ThisUser.AcceptPolicy();
                     ThisUser.PerformOnReminder();
                     response.result = true;

                     MySend(manager.ChatUserList(UID));
                     MySend(manager.ChatMessageList(UID));
                  }
               }
               else if (ThisUser.Blocked)
               {
                  response.errors.Add(NewWarningFromTag(QuickParams(ChatTags.Blocked)).message);
               }
               else if (ThisUser.Banned)
               {
                  response.errors.Add("You are banned from chat for " +
                  StringExtensions.LargestTime(ThisUser.BannedUntil - DateTime.Now));
               }
               else if (tag == "admin" && !ThisUser.CanStaffChat ||
                        tag == manager.ChatSettings.GlobalTag && !ThisUser.CanGlobalChat)
               {
                  response.errors.Add("You can't post messages here. I'm sorry.");
               }
               else if (!manager.ValidTagForUser(UID, tag))
               {
                  response.errors.Add("Your post has an unrecognized tag. Cannot display");
               }
               else
               {
                  Dictionary<int, UserInfo> currentUsers = manager.UsersForModules();
                  List<JSONObject> outputs = new List<JSONObject>();
                  UserMessageJSONObject userMessage = new UserMessageJSONObject(ThisUser, message, tag);
                  UserCommand userCommand;
                  Module commandModule;
                  string commandError = "";

                  //Step 1: parse a possible command. If no command is parsed, no module will be written.
                  if (TryCommandParse(userMessage, out commandModule, out userCommand, out commandError))
                  {
                     Log("Trying to use module " + commandModule.ModuleName + " to process command " + 
                        userCommand.message + " from " + ThisUser.Username, MyExtensions.Logging.LogLevel.SuperDebug);

                     //We found a command. Send it off to the proper module and get the output
                     if (Monitor.TryEnter(commandModule.Lock, manager.ChatSettings.MaxModuleWait))
                     {
                        try
                        {
                           outputs.AddRange(commandModule.ProcessCommand(userCommand, currentUsers[ThisUser.UID], currentUsers));
                        }
                        finally
                        {
                           Monitor.Exit(commandModule.Lock);
                        }
                     }
                     else
                     {
                        response.errors.Add("The chat server is busy and can't process your command right now");
                        userMessage.SetHidden();
                        userMessage.SetUnspammable();
                     }

                     //do not update spam score if command module doesn't want it
                     if (!userCommand.MatchedCommand.ShouldUpdateSpamScore)
                        userMessage.SetUnspammable();

                     //For now, simply capture all commands no matter what.
                     userMessage.SetHidden();
                     //userMessage.SetCommand();

                     Log("Module " + commandModule.ModuleName + " processed command from " + UserLogString, 
                        MyExtensions.Logging.LogLevel.Debug);
                  }
                  else
                  {
                     //If an error was given, add it to our response
                     if (!string.IsNullOrWhiteSpace(commandError))
                     {
                        response.errors.Add("Command error: " + commandError);
                        userMessage.SetHidden();
                        userMessage.SetUnspammable();
                     }
                  }

                  ChatTags warning = manager.AddMessage(userMessage);

                  //Send off on relay
                  /*if(userMessage.Display && userMessage.tag == manager.IrcTag)
                  {
                     if(relay.SendMessage(userMessage.message))
                        Logger.LogGeneral("Sent message on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug);
                     else
                        Logger.LogGeneral("Couldn't send on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug);
                  }*/

                  if (warning != ChatTags.None)
                     outputs.Add(NewWarningFromTag(QuickParams(warning)));

                  response.result = response.errors.Count == 0;

                  //Now send out userlist if active status changed
                  UpdateActiveUserList(null, null);

                  //Since we added a new message, we need to broadcast.
                  if (response.result && userMessage.Display)
                     manager.BroadcastMessageList();        //CRASH ALMOST CERTAINLY HAPPENS HERE!!!!!!!###$$$$$!!!!!!!!!!*$*$*$*

                  //Step 2: run regular message through all modules' regular message processor (probably no output?)
                  if(manager.ChatSettings.AcceptedTags.Contains(userMessage.tag))
                  {
                     foreach (Module module in manager.GetModuleListCopy())
                     {
                        if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait))
                        {
                           try
                           {
                              module.ProcessMessage(userMessage, currentUsers[ThisUser.UID], currentUsers);
                           }
                           finally
                           {
                              Monitor.Exit(module.Lock);
                           }
                        }
                        else
                        {
                           Log("Skipped " + module.ModuleName + " message processing", 
                              MyExtensions.Logging.LogLevel.Warning);
                        }
                     }
                  }

                  //Step 3: run all modules' post processor (no message required)
                  //Is this even necessary? It was necessary before because the bot ran on a timer. Without a timer,
                  //each module can just specify that it wants to do things at random points with its own timer.

                  //Step 4: iterate over returned messages and send them out appropriately
                  OutputMessages(outputs, ThisUser.UID, tag);
                  //end of regular message processing
               }
            }
            catch (Exception messageError)
            {
               response.errors.Add("Internal server error: " + messageError/*.Message*/);
               //response.errors.Add("Message was missing fields");
            }
         }
//         else if (type == "createroom")
//         {
//            try
//            {
//               List<int> users = json.users.ToObject<List<int>>();
//               string error;
//
//               if(!manager.CreatePMRoom(new HashSet<int>(users), ThisUser.UID, out error))
//               {
//                  response.errors.Add(error);
//               }
//               else
//               {
//                  MySend((new SystemMessageJSONObject("You created a chat room for " + string.Join(", ", users.Select(x => manager.GetUser(x).Username)))).ToString());
//                  manager.BroadcastUserList();
//               }
//            }
//            catch
//            {
//               response.errors.Add("Could not parse PM creation room message");
//            }
//         }
         else if (type == "request")
         {
            try
            {
               string wanted = json.request;

               if(wanted == "userList")
               {
                  MySend(manager.ChatUserList(UID));
                  response.result = true;
               }
               else if (wanted == "messageList")
               {
                  MySend(manager.ChatMessageList(UID));
                  response.result = true;
               }
               else
               {
                  response.errors.Add("Invalid request field");
               }
            }
            catch
            {
               response.errors.Add("Request was missing fields");
            }
         }

         //Send the "OK" message back.
         MySend(response.ToString(), true);
      }