/// <summary>
 /// Handles the <see cref="MessageHandler.MessageParsed"/> event
 /// </summary>
 /// <param name="mh">The <see cref="MessageHandler"/> that parsed the message</param>
 /// <remarks>
 /// This method starts a new thread to peform the actual message handling
 /// </remarks>
 private void mh_MessageParsed(MessageHandler mh)
 {
     ParameterizedThreadStart pts = new ParameterizedThreadStart(HandleParsedMessage);
     Thread t = new Thread(pts);
     t.Start(mh);
 }
        /// <summary>
        /// Handles the <see cref="AsyncSocket.DidAccept"/> event.
        /// </summary>
        /// <param name="sender">The listening <see cref="AsyncSocket"/></param>
        /// <param name="newSocket">The new <see cref="AsyncSocket"/> that was accepted</param>
        private void listenSocket_DidAccept(AsyncSocket sender, AsyncSocket newSocket)
        {
            LogInfo("Accepted client {0}:{1}", newSocket.RemoteAddress, newSocket.RemotePort);

            // check origin
            bool isLocal = IPAddress.IsLoopback(newSocket.RemoteAddress);
            bool isLAN = Growl.CoreLibrary.IPUtilities.IsInSameSubnet(newSocket.LocalAddress, newSocket.RemoteAddress);
            if (!this.allowNetworkNotifications && !isLocal)
            {
                // remote connections not allowed - Should we return a GNTP error response? i think this is better (no reply at all)
                LogInfo("Blocked network request from '{0}'", newSocket.RemoteAddress);
                newSocket.Close();
                return;
            }

            bool passwordRequired = true;
            if (isLocal && !this.RequireLocalPassword) passwordRequired = false;
            else if (isLAN && !this.RequireLANPassword) passwordRequired = false;

            // SUPER IMPORTANT
            newSocket.AllowMultithreadedCallbacks = true;

            MessageHandler mh = new MessageHandler(this.serverName, this.passwordManager, passwordRequired, this.logFolder, this.loggingEnabled, this.allowNetworkNotifications, this.allowWebNotifications, this.allowSubscriptions);
            newSocket.DidClose += new AsyncSocket.SocketDidClose(newSocket_DidClose);
            mh.MessageParsed += new MessageHandler.MessageHandlerMessageParsedEventHandler(mh_MessageParsed);
            mh.Error += new MessageHandler.MessageHandlerErrorEventHandler(mh_Error);
            mh.SocketUsageComplete += new MessageHandler.MessageHandlerSocketUsageCompleteEventHandler(mh_SocketUsageComplete);

            // lock here since in very rare cases, we can get flooded with so many incoming sockets that
            // the Add() throws an IndexOutOfRange exception (only ever happened when running GrowlHammer with loads of
            // simultaneous connections)
            lock (syncLock)
            {
                connectedSockets.Add(new ConnectedSocket(newSocket));
                connectedHandlers.Add(newSocket, mh);
            }

            mh.InitialRead(newSocket);
        }
        /// <summary>

        /// Handles the <see cref="AsyncSocket.DidAccept"/> event.

        /// </summary>

        /// <param name="sender">The listening <see cref="AsyncSocket"/></param>

        /// <param name="newSocket">The new <see cref="AsyncSocket"/> that was accepted</param>

        private void listenSocket_DidAccept(AsyncSocket sender, AsyncSocket newSocket)

        {
            LogInfo("Accepted client {0}:{1}", newSocket.RemoteAddress, newSocket.RemotePort);



            // check origin

            bool isLocal = IPAddress.IsLoopback(newSocket.RemoteAddress);

            bool isLAN = Growl.CoreLibrary.IPUtilities.IsInSameSubnet(newSocket.LocalAddress, newSocket.RemoteAddress);

            if (!this.allowNetworkNotifications && !isLocal)

            {
                // remote connections not allowed - Should we return a GNTP error response? i think this is better (no reply at all)

                LogInfo("Blocked network request from '{0}'", newSocket.RemoteAddress);

                newSocket.Close();

                return;
            }



            bool passwordRequired = true;

            if (isLocal && !this.RequireLocalPassword)
            {
                passwordRequired = false;
            }

            else if (isLAN && !this.RequireLANPassword)
            {
                passwordRequired = false;
            }



            // SUPER IMPORTANT

            newSocket.AllowMultithreadedCallbacks = true;



            MessageHandler mh = new MessageHandler(this.serverName, this.passwordManager, passwordRequired, this.logFolder, this.loggingEnabled, this.allowNetworkNotifications, this.allowWebNotifications, this.allowSubscriptions);

            newSocket.DidClose += new AsyncSocket.SocketDidClose(newSocket_DidClose);

            mh.MessageParsed += new MessageHandler.MessageHandlerMessageParsedEventHandler(mh_MessageParsed);

            mh.Error += new MessageHandler.MessageHandlerErrorEventHandler(mh_Error);

            mh.SocketUsageComplete += new MessageHandler.MessageHandlerSocketUsageCompleteEventHandler(mh_SocketUsageComplete);



            // lock here since in very rare cases, we can get flooded with so many incoming sockets that

            // the Add() throws an IndexOutOfRange exception (only ever happened when running GrowlHammer with loads of

            // simultaneous connections)

            lock (syncLock)

            {
                connectedSockets.Add(new ConnectedSocket(newSocket));

                connectedHandlers.Add(newSocket, mh);
            }



            mh.InitialRead(newSocket);
        }
        /// <summary>

        /// Handles the <see cref="AsyncSocket.DidClose"/> event

        /// </summary>

        /// <param name="sender">The <see cref="AsyncSocket"/> that disconnected</param>

        void newSocket_DidClose(AsyncSocket sender)

        {
            if (sender != null)

            {
                lock (syncLock)

                {
                    if (sender != null)

                    {
                        if (this.connectedHandlers.ContainsKey(sender))

                        {
                            MessageHandler mh = this.connectedHandlers[sender];

                            this.connectedHandlers.Remove(sender);



                            if (this.connectedSockets.Contains(sender))

                            {
                                ConnectedSocket cs = this.connectedSockets[sender];

                                this.connectedSockets.Remove(sender);



                                if (cs.Socket != null)

                                {
                                    cs.Socket.DidClose -= new AsyncSocket.SocketDidClose(newSocket_DidClose);

                                    //cs.Socket.DidRead -= new AsyncSocket.SocketDidRead(mh.SocketDidRead);
                                }



                                cs = null;
                            }



                            if (mh != null)

                            {
                                mh.MessageParsed -= new MessageHandler.MessageHandlerMessageParsedEventHandler(mh_MessageParsed);

                                mh.Error -= new MessageHandler.MessageHandlerErrorEventHandler(mh_Error);

                                mh.SocketUsageComplete -= new MessageHandler.MessageHandlerSocketUsageCompleteEventHandler(mh_SocketUsageComplete);
                            }



                            mh = null;
                        }
                    }
                }



                sender = null;
            }
        }
        /// <summary>

        /// Writes back the GNTP response to the requesting application

        /// </summary>

        /// <param name="cbInfo">The <see cref="CallbackInfo"/> associated with the response</param>

        /// <param name="response">The <see cref="Response"/> to be written back</param>

        public void WriteResponse(CallbackInfo cbInfo, Response response)

        {
            if (!cbInfo.AlreadyResponded)

            {
                cbInfo.AlreadyResponded = true;

                MessageHandler mh = cbInfo.MessageHandler;

                GNTPRequest request = mh.Request;

                ResponseType responseType = ResponseType.ERROR;

                if (response != null)

                {
                    if (response.IsCallback)
                    {
                        responseType = ResponseType.CALLBACK;
                    }

                    else if (response.IsOK)
                    {
                        responseType = ResponseType.OK;
                    }
                }

                else

                {
                    response = new Response(ErrorCode.INTERNAL_SERVER_ERROR, ErrorDescription.INTERNAL_SERVER_ERROR);
                }



                if (cbInfo.AdditionalInfo != null)

                {
                    foreach (KeyValuePair <string, string> item in cbInfo.AdditionalInfo)

                    {
                        response.CustomTextAttributes.Add(item.Key, item.Value);
                    }
                }



                AddServerHeaders(response);

                MessageBuilder mb = new MessageBuilder(responseType);

                HeaderCollection responseHeaders = response.ToHeaders();

                foreach (Header header in responseHeaders)

                {
                    mb.AddHeader(header);
                }

                // return any application-specific data headers that were received

                RequestData rd = RequestData.FromHeaders(request.Headers);

                AddRequestData(mb, rd);



                mh.WriteResponse(mb, true);
            }
        }
        /// <summary>

        /// Handles the parsed message after it is received

        /// </summary>

        /// <param name="obj">The <see cref="MessageHandler"/> object that parsed the message</param>

        private void HandleParsedMessage(object obj)

        {
            MessageHandler mh = (MessageHandler)obj;

            GNTPRequest request = mh.Request;



            try

            {
                Response response = null;

                switch (request.Directive)

                {
                case RequestType.REGISTER:

                    Application application = Application.FromHeaders(request.Headers);

                    List <NotificationType> notificationTypes = new List <NotificationType>();

                    for (int i = 0; i < request.NotificationsToBeRegistered.Count; i++)

                    {
                        HeaderCollection headers = request.NotificationsToBeRegistered[i];

                        notificationTypes.Add(NotificationType.FromHeaders(headers));
                    }

                    response = this.OnRegisterReceived(application, notificationTypes, mh.RequestInfo);

                    break;

                case RequestType.NOTIFY:

                    Notification notification = Notification.FromHeaders(request.Headers);

                    mh.CallbackInfo.NotificationID = notification.ID;

                    response = this.OnNotifyReceived(notification, mh.CallbackInfo, mh.RequestInfo);

                    break;

                case RequestType.SUBSCRIBE:

                    Subscriber subscriber = Subscriber.FromHeaders(request.Headers);

                    subscriber.IPAddress = mh.Socket.RemoteAddress.ToString();

                    subscriber.Key = new SubscriberKey(request.Key, subscriber.ID, request.Key.HashAlgorithm, request.Key.EncryptionAlgorithm);

                    response = this.OnSubscribeReceived(subscriber, mh.RequestInfo);

                    break;
                }



                ResponseType responseType = ResponseType.ERROR;

                if (response != null && response.IsOK)

                {
                    responseType = ResponseType.OK;

                    response.InResponseTo = request.Directive.ToString();
                }



                // no response

                if (response == null)
                {
                    response = new Response(ErrorCode.INTERNAL_SERVER_ERROR, ErrorDescription.INTERNAL_SERVER_ERROR);
                }



                AddServerHeaders(response);

                MessageBuilder mb = new MessageBuilder(responseType);

                HeaderCollection responseHeaders = response.ToHeaders();

                foreach (Header header in responseHeaders)

                {
                    mb.AddHeader(header);
                }

                // return any application-specific data headers that were received

                RequestData rd = RequestData.FromHeaders(request.Headers);

                AddRequestData(mb, rd);



                bool requestComplete = !mh.CallbackInfo.ShouldKeepConnectionOpen();

                mh.WriteResponse(mb, requestComplete);
            }

            catch (GrowlException gEx)

            {
                mh.WriteError(gEx.ErrorCode, gEx.Message, gEx.AdditionalInfo);
            }

            catch (Exception ex)

            {
                mh.WriteError(ErrorCode.INTERNAL_SERVER_ERROR, ex.Message);
            }
        }