// 
// Copyright (c) 2013-2016 Carsten Sonne Larsen <cs@innolan.dk>
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Ntp.Common.Log;

namespace Ntp.Analyzer.Monitor.Server
{
    public sealed class Listener : IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Listener" /> class.
        /// </summary>
        /// <param name="ip">IP Address to listen on.</param>
        /// <param name="port">Port number.</param>
        /// <param name="log">Log.</param>
        public Listener(string ip, int port, LogBase log)
        {
            IPAddress address = IPAddress.Parse(ip);
            endPoint = new IPEndPoint(address, port);
            this.log = log;
            shuttingDown = false;
        }

        private readonly IPEndPoint endPoint;
        private readonly LogBase log;
        private Socket listenSocket;
        private bool shuttingDown;

        /// <summary>
        /// Close this listener.
        /// </summary>
        public void Close()
        {
            shuttingDown = true;
            listenSocket.Close();
        }

        /// <summary>
        /// Open this listener.
        /// </summary>
        public void Open()
        {
            try
            {
                listenSocket = new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Stream,
                    ProtocolType.Tcp);

                listenSocket.Bind(endPoint);
                listenSocket.Listen(3);

                listenSocket.BeginAccept(AcceptCallback, null);
            }
            catch (Exception ex)
            {
                log.WriteLine("Error while initializing listener " + ToString(), Severity.Error);
                log.WriteLine(ex.Message, Severity.Debug);
                log.WriteLine(ex, Severity.Trace);
            }
        }

        public override string ToString()
        {
            return endPoint.ToString();
        }

        /// <summary>
        /// Accepts the callback from client.
        /// </summary>
        /// <param name="asyncAccept">Async accept.</param>
        private void AcceptCallback(IAsyncResult asyncAccept)
        {
            try
            {
                Socket serverSocket = listenSocket.EndAccept(asyncAccept);

                if (serverSocket.Connected == false)
                    return;

                var req = new Request(128, serverSocket);

                serverSocket.BeginReceive(
                    req.Buffer,
                    0,
                    req.Buffer.Length,
                    SocketFlags.None,
                    ReceiveCallback,
                    req);
            }
            catch (Exception ex)
            {
                if (!shuttingDown)
                {
                    log.WriteLine("Unexpected error in listener " + ToString(), Severity.Error);
                    log.WriteLine(ex.Message, Severity.Debug);
                    log.WriteLine(ex, Severity.Trace);
                }
            }
        }

        /// <summary>
        /// Receives the callback from client.
        /// </summary>
        /// <param name="asyncReceive">Async receive.</param>
        private void ReceiveCallback(IAsyncResult asyncReceive)
        {
            try
            {
                var req = (Request) asyncReceive.AsyncState;
                int size = req.Socket.EndReceive(asyncReceive);
                req.ReSize(size);

                // Process request
                Command command = CommandFactory.Create(req.Command, req.Arguments);

                // Force correct time format in strings, etc.
                Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

                byte[] sendBuffer;

                if (command == null)
                {
                    sendBuffer = Encoding.UTF8.GetBytes("Unknown command");
                }
                else
                    switch (command.CommandType)
                    {
                        case CommandType.Binary:
                            sendBuffer = (byte[]) command.Execute();
                            break;
                        case CommandType.Text:
                            var text = command.Execute() as string;
                            sendBuffer = text != null ? Encoding.UTF8.GetBytes(text) : new byte[] {0};
                            break;
                        default:
                            sendBuffer = Encoding.UTF8.GetBytes("Error in command module. Unknown command type.");
                            break;
                    }

                req.Socket.BeginSend(
                    sendBuffer,
                    0,
                    sendBuffer.Length,
                    SocketFlags.None,
                    SendCallback,
                    req.Socket);
            }
            catch (Exception ex)
            {
                log.WriteLine("Unexpected error in listener " + ToString(), Severity.Error);
                log.WriteLine(ex.Message, Severity.Debug);
                log.WriteLine(ex, Severity.Trace);
            }
        }

        /// <summary>
        /// Sends the callback response to client.
        /// </summary>
        /// <param name="asyncSend">Async send.</param>
        private void SendCallback(IAsyncResult asyncSend)
        {
            try
            {
                var serverSocket = (Socket) asyncSend.AsyncState;
                serverSocket.EndSend(asyncSend);
                serverSocket.Shutdown(SocketShutdown.Both);
                serverSocket.Close();

                // Start new worker socket.
                listenSocket.BeginAccept(AcceptCallback, null);
            }
            catch (Exception ex)
            {
                log.WriteLine("Unexpected error in listener " + ToString(), Severity.Error);
                log.WriteLine(ex.Message, Severity.Debug);
                log.WriteLine(ex, Severity.Trace);
            }
        }

        #region IDisposable Support

        private bool disposedValue;

        private void Dispose(bool disposing)
        {
            if (disposedValue)
                return;

            if (disposing && listenSocket != null)
            {
                if (listenSocket.Connected)
                {
                    listenSocket.Shutdown(SocketShutdown.Both);
                    listenSocket.Close();
                }

                listenSocket.Dispose();
                listenSocket = null;
            }

            disposedValue = true;
        }

        ~Listener()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}