using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Microsoft.SPOT;

namespace WIFI
{

    // Simplifies usage of sockets in .NETMF
    public class IntegratedSocket : SimpleSocket
    {
        // Contains a reference to the socket
        private Socket _Sock;
        // Stores the hostname connected to
        private string _Hostname;
        // Stores the TCP port connected to
        private ushort _Port;
        // Contains the buffer of the read data
        private string _Buffer = "";

        // Set to true when Close() is called
        private bool _Closed = false;

        // Creates a new socket based on the integrated .NETMF socket TCP stack
        public IntegratedSocket(string Hostname, ushort Port)
        {
            // Stores the values to the memory
            this._Hostname = Hostname;
            this._Port = Port;
            // Default line ending values
            this.LineEnding = "";
            // Creates a new socket
        }

        // Listens on the port instead of connecting remotely
        public override void Listen()
        {
            Socket Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Listener.Bind(new IPEndPoint(IPAddress.Any, this._Port));
            Listener.Listen(1);
            // Accepts the first connection
            this._Sock = Listener.Accept();
            this._Closed = false;
            this._Hostname = ((IPEndPoint)this._Sock.RemoteEndPoint).Address.ToString();
            // Stops further listening
            Listener.Close();
        }

        // Requests the amount of bytes available in the buffer
        public override uint BytesAvailable
        {
            get
            {
                return (uint)this._Buffer.Length;
            }
        }

        // Connects to the remote host
        public override void Connect(SocketProtocol Protocol = SocketProtocol.TcpStream)
        {
            // Creates a new socket object
            if (Protocol == SocketProtocol.TcpStream)
                this._Sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            else
                this._Sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            // Resolves the hostname to an IP address
            IPHostEntry address = Dns.GetHostEntry(this._Hostname);
            // Creates the new IP end point
            EndPoint Destination = new IPEndPoint(address.AddressList[0], (int)this._Port);
            // Connects to the socket
            this._Sock.Connect(Destination);
            this._Closed = false;
        }


        // Closes the connection
        public override void Close()
        {
            this._Closed = true;
            this._Sock.Close();
        }

        // Sends binary data to the socket
        public override void SendBinary(byte[] Data)
        {
            this._Sock.Send(Data);

            // I implemented this sleep here for an issue I encountered.
            // For more details, read http://forums.netduino.com/index.php?/topic/4555-socket-error-10055-wsaenobufs/
            if (Data.Length < 32) Thread.Sleep(Data.Length * 10);
        }

        // Returns true when connected, otherwise false
        public override bool IsConnected
        {
            get
            {
                if (this._Closed) return false;
                // SelectRead returns true if:
                // - Listen has been called and a connection is pending; -or-
                // - if data is available for reading; -or-
                // - if the connection has been closed, reset, or terminated
                // We're not listening, so we only need to check if data is available for reading. If not, the connection is a goner.
                if (this._Sock.Poll(1, SelectMode.SelectRead) && this._Sock.Available == 0)
                    return false;
                else
                    return true;
            }
        }

        // Returns the hostname this socket is configured for
        public override string Hostname { get { return this._Hostname; } }

        // Returns the port number this socket is configured for
        public override ushort Port { get { return this._Port; } }

        // Receives data from the socket
        public override string Receive(bool Block = false)
        {
            string RetValue = "";
            do
            {
                // Do we need to read data?
                if (this._Sock.Available > 0)
                {
                    // Do we already have enough data for now? (saves us from early out of memory exceptions)
                    if (this.LineEnding.Length == 0 || this._Buffer.IndexOf(this.LineEnding) < 0)
                    {

                        // There is data, lets read it!
                        byte[] ReadBuffer = new byte[this._Sock.Available];
                        this._Sock.Receive(ReadBuffer);
                        // Lets add the data to the buffer
                        this._Buffer += new string(Tools.Bytes2Chars(ReadBuffer));
                    }
                }

                if (this.LineEnding.Length > 0)
                {
                    // We're going to do buffering
                    int Pos = this._Buffer.IndexOf(this.LineEnding);
                    // Appairently there's a line ending found, lets split the data up
                    if (Pos > -1)
                    {
                        RetValue = this._Buffer.Substring(0, Pos + this.LineEnding.Length);
                        this._Buffer = this._Buffer.Substring(Pos + this.LineEnding.Length);
                    }
                }
                else
                {
                    // We don't do buffering at this moment. We just send all data back.
                    RetValue = this._Buffer;
                    this._Buffer = "";
                }
            } while (Block && RetValue == "");

            return RetValue;
        }

        // Receives binary data from the socket (line endings aren't used with this method)
        public override byte[] ReceiveBinary(int Length)
        {
            byte[] RetValue = new byte[Length];
            this._Sock.Receive(RetValue);

            return RetValue;
        }

        // Checks if a feature is implemented
        public override bool FeatureImplemented(SocketFeatures Feature)
        {
            switch (Feature)
            {
                case SocketFeatures.TcpStream:
                case SocketFeatures.UdpDatagram:
                case SocketFeatures.TcpListener:
                    return true;
                default:
                    return false;
            }
        }
    }
}