using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace UEUserGuide
{
    /// <summary>
    /// Implement a TCP client/listener.
    /// </summary>
    public class NetworkHelper
    {
        ///////////////////////////////////////////////////////////////////////
        #region Internal fields

        /// <summary>
        /// IP address.
        /// </summary>
        private IPAddress _address;

        /// <summary>
        /// IP port.
        /// </summary>
        private int _port = -1;

        /// <summary>
        /// Server instance.
        /// </summary>
        private TcpListener _server;

        /// <summary>
        /// Client instance.
        /// </summary>
        private TcpClient _client;

        /// <summary>
        /// End point reference.
        /// </summary>
        private NetworkStream _stream;

        #endregion Internal fields
        
        ///////////////////////////////////////////////////////////////////////
        #region Static methods

        /// <summary>
        /// Create a new instance of TcpClient from specified instance.
        /// <remarks>This method acts like a copy ctor.</remarks>
        /// </summary>
        /// <param name="client">Client reference.</param>
        /// <returns>The new instance on success, null otherwise.</returns>
        private static NetworkHelper Create(TcpClient client)
        {
            // Sanity check.
            if (client == null)
            {
                UEUserGuide.Logger.Instance.ErrorLogger("NetworkHelper: Client instance is null.");
                return null;
            }

            return new NetworkHelper(client);
        }

        #endregion Static methods

        ///////////////////////////////////////////////////////////////////////
        #region ctors

        /// <summary>
        /// Default ctor.
        /// </summary>
        public NetworkHelper()
        {
            _address = null;
            _port = -1;
            _server = null;
            _client = null;
        }

        /// <summary>
        /// Builder ctor used in server part.
        /// </summary>
        /// <param name="client"></param>
        private NetworkHelper(TcpClient client)
        {
            _client = client;
            _client.NoDelay = true;
            _stream = _client.GetStream();
        }

        #endregion ctors

        ///////////////////////////////////////////////////////////////////////
        #region INetwork iface implementation

        ///////////////////////////////////////////////////////////////////////
        #region Initialization part

        /// <summary>
        /// Initialize the instance of TCP client or server.
        /// </summary>
        /// <param name="address">IP address to use for the communication.</param>
        /// <param name="port">Port number</param>
        /// <returns>true on success, false otherwise.</returns>
        public bool Initialize(string address, int port)
        {
            try
            {
                _port = port;
                _address = IPAddress.Parse(address);

                return true;
            }
            catch (Exception e)
            {
                Logger.Instance.ErrorLogger("NetworkTcp.Initialize: {0}", e);
            }

            return false;
        }

        /// <summary>
        /// Uninitialize the current instance.
        /// </summary>
        /// <returns>true on success, false otherwise.</returns>
        public bool Uninitialize()
        {
            Close();

            return true;
        }

        #endregion Initialization part

        ///////////////////////////////////////////////////////////////////////
        #region Server methods

        /// <summary>
        /// Start the TCP server.
        /// <seealso cref="Initialize"/> method must be called before.
        /// </summary>
        public void Start()
        {
            _server = new TcpListener(_address, _port);
            _server.ExclusiveAddressUse = false;
            _server.Server.NoDelay = true;
            _server.Start();
            // To prevent multiple listener instance.
            Thread.Sleep(1000);
        }

        /// <summary>
        /// Stop the TCP server.
        /// <seealso cref="Initialize"/> method must be called before.
        /// </summary>
        public void Stop()
        {
            if (_server != null)
            {
                _server.Stop();
                _server = null;
            }
        }

        /// <summary>
        /// Accept incoming connection.
        /// </summary>
        /// <returns>New client instance.</returns>
        public NetworkHelper AcceptClient()
        {
            TcpClient client = _server.AcceptTcpClient();
            return NetworkHelper.Create(client);
        }

        #endregion Server methods

        ///////////////////////////////////////////////////////////////////////
        #region Client methods

        /// <summary>
        /// Initiate a connection.
        /// </summary>
        public void Connect()
        {
            if (_client != null)
            {
               return;
            }

            _client = new TcpClient();
            _client.NoDelay = true;
            _client.Connect(_address, _port);
            // Get a stream object for reading and writing
            _stream = _client.GetStream();
        }

        /// <summary>
        /// Initiate a connection with login/password..
        /// </summary>
        /// <param name="login">Login to pass.</param>
        /// <param name="password">Password to pass.</param>
        public void Connect(string login, string password)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Terminate the connection.
        /// </summary>
        public void Close()
        {
            if (_client != null)
            {
                try
                {
                    if (_stream != null)
                    {
                        _stream.Close();
                    }
                    _client.Close();
                }
                catch
                {
                }
                finally
                {
                    _client = null;
                    _stream = null;
                }
            }
        }

        #endregion Client methods

        ///////////////////////////////////////////////////////////////////////
        #region Data access methods

        /// <summary>
        /// Received datas from remote.
        /// </summary>
        /// <param name="buffer">The buffer to store the datas. Set to null on error.</param>
        /// <returns>The number of bytes received on success, -1 otherwise.</returns>
        public int Receive(out byte[] buffer)
        {
            if (_stream != null)
            {
                // read size of the packet first.
                byte[] length = new byte[sizeof(int)];
                _stream.Read(length, 0, length.Length);
                int totalBytes = BitConverter.ToInt32(length, 0);
                buffer = new byte[totalBytes];
                int readBytes = _stream.Read(buffer, 0, buffer.Length);
                while (readBytes < totalBytes)
                {
                    byte[] next = new byte[totalBytes - readBytes];
                    readBytes += _stream.Read(next, 0, next.Length);
                    buffer.Concat<byte>(next);
                }

                return buffer.Length;
            }

            buffer = null;
            return -1;
        }

        /// <summary>
        /// Received strings from remote.
        /// </summary>
        /// <param name="buffer">The buffer to store the strings. Set to null on error.</param>
        /// <returns>The number of bytes received on success, -1 otherwise.</returns>
        public int Receive(out string buffer)
        {
            if (_stream != null)
            {
                if (_stream.DataAvailable)
                {
                    byte[] inbuf = new byte[512];

                    IAsyncResult ar = _stream.BeginRead(inbuf, 0, 512, null, null);
                    int bytesRead = _stream.EndRead(ar);
                    if (ar.IsCompleted)
                    {
                        buffer = System.Text.Encoding.ASCII.GetString(inbuf, 0, bytesRead);
                        return buffer.Length;
                    }
                }
                else
                {
                    buffer = "";
                    return 0;
                }
            }

            buffer = null;
            return -1;
        }

        /// <summary>
        /// Send data to remote.
        /// </summary>
        /// <param name="buffer">Data to send.</param>
        /// <param name="size">The number of byte to send.</param>
        /// <returns>The number of bytes sent on success, -1 otherwise.</returns>
        public int Send(byte[] buffer, int size)
        {
            if (_stream != null)
            {
                // Sanity checks.
                if ((buffer == null) || (size == 0))
                {
                    return 0;
                }

                // Send size of packet first.
                byte[] length = BitConverter.GetBytes(size);
                _stream.Write(length , 0, length.Length);
                // Send the packet after.
                _stream.Write(buffer, 0, size);
                _stream.Flush();
                return size;
            }

            return -1;
        }

        /// <summary>
        /// Send string to remote.
        /// </summary>
        /// <param name="buffer">String to send.</param>
        /// <returns>The received string length on success, -1 otherwise.</returns>
        public int Send(string buffer)
        {
            if (_stream != null)
            {
                // Sanity checks.
                if (string.IsNullOrEmpty(buffer))
                {
                    return 0;
                }
                byte[] output = System.Text.Encoding.ASCII.GetBytes(buffer);
                _stream.Write(output, 0, output.Length);
                _stream.Flush();
                return output.Length;
            }

            return -1;
        }

        #endregion Data access methods

        #endregion INetwork iface implementation
    }
}
