// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CSharpSocket.cs" company="Exit Games GmbH">
//   Protocol & Photon Client Lib - Copyright (C) 2013 Exit Games GmbH
// </copyright>
// <summary>
//   Uses the UDP socket for a peer to send and receive enet/Photon messages.
// </summary>
// <author>developer@exitgames.com</author>
// --------------------------------------------------------------------------------------------------------------------

#if UNITY_EDITOR || (!UNITY_ANDROID && !UNITY_IPHONE && !UNITY_PS3 && !UNITY_WINRT && !UNITY_WP8)

namespace ExitGames.Client.Photon
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Security;
    using System.Threading;

    /// <summary> Internal class to encapsulate the network i/o functionality for the realtime libary.</summary>
    internal class SocketUdp : IPhotonSocket, IDisposable
    {
        private Socket sock;

        private readonly object syncer = new object();

        public SocketUdp(PeerBase npeer) : base(npeer)
        {
            if (this.ReportDebugOfLevel(DebugLevel.ALL))
            {
                this.Listener.DebugReturn(DebugLevel.ALL, "CSharpSocket: UDP, Unity3d.");
            }

            //this.Protocol = ConnectionProtocol.Udp;
            this.PollReceive = false;
        }

        public void Dispose()
        {
            this.State = PhotonSocketState.Disconnecting;

            if (this.sock != null)
            {
                try
                {
                    if (this.sock.Connected) this.sock.Close();
                }
                catch (Exception ex)
                {
                    this.EnqueueDebugReturn(DebugLevel.INFO, "Exception in Dispose(): " + ex);
                }
            }

            this.sock = null;
            this.State = PhotonSocketState.Disconnected;
        }

        public override bool Connect()
        {
            lock (this.syncer)
            {
                bool baseOk = base.Connect();
                if (!baseOk)
                {
                    return false;
                }

                this.State = PhotonSocketState.Connecting;

                Thread dns = new Thread(this.DnsAndConnect);
                dns.Name = "photon dns thread";
                dns.IsBackground = true;
                dns.Start();

                return true;
            }
        }

        public override bool Disconnect()
        {
            if (this.ReportDebugOfLevel(DebugLevel.INFO))
            {
                this.EnqueueDebugReturn(DebugLevel.INFO, "CSharpSocket.Disconnect()");
            }

            this.State = PhotonSocketState.Disconnecting;

            lock (this.syncer)
            {
                if (this.sock != null)
                {
                    try
                    {
                        this.sock.Close();
                    }
                    catch (Exception ex)
                    {
                        this.EnqueueDebugReturn(DebugLevel.INFO, "Exception in Disconnect(): " + ex);
                    }

                    this.sock = null;
                }
            }

            this.State = PhotonSocketState.Disconnected;
            return true;
        }

        /// <summary>used by PhotonPeer*</summary>
        public override PhotonSocketError Send(byte[] data, int length)
        {
            lock (this.syncer)
            {
                if (this.sock == null || !this.sock.Connected)
                {
                    return PhotonSocketError.Skipped;
                }

                try
                {
                    sock.Send(data, 0, length, SocketFlags.None);
                }
                catch (Exception e)
                {
                    if (this.ReportDebugOfLevel(DebugLevel.ERROR))
                    {
                        this.EnqueueDebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ServerAddress + ". " + e.Message);
                    }
                    return PhotonSocketError.Exception;
                }
            }

            return PhotonSocketError.Success;
        }

        public override PhotonSocketError Receive(out byte[] data)
        {
            data = null;
            return PhotonSocketError.NoData;
        }

        internal void DnsAndConnect()
        {
            IPAddress ipAddress = null;
            try
            {
                ipAddress = IPhotonSocket.GetIpAddress(this.ServerAddress);
                if (ipAddress == null)
                {
                    // this covers cases of failed DNS lookup and bad addresses.
                    throw new ArgumentException("Invalid IPAddress. Address: " + this.ServerAddress);
                }

                lock (this.syncer)
                {
                    if (this.State == PhotonSocketState.Disconnecting || this.State == PhotonSocketState.Disconnected)
                    {
                        return;
                    }

                    this.sock = new Socket(ipAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
                    this.sock.Connect(ipAddress, this.ServerPort);

                    this.AddressResolvedAsIpv6 = this.IsIpv6SimpleCheck(ipAddress);
                    this.State = PhotonSocketState.Connected;

                    this.peerBase.OnConnect();
                }
            }
            catch (SecurityException se)
            {
                if (this.ReportDebugOfLevel(DebugLevel.ERROR))
                {
                    this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ServerAddress + "' (" + ((ipAddress == null ) ? "": ipAddress.AddressFamily.ToString()) + ") failed: " + se.ToString());
                }

                this.HandleException(StatusCode.SecurityExceptionOnConnect);
                return;
            }
            catch (Exception se)
            {
                if (this.ReportDebugOfLevel(DebugLevel.ERROR))
                {
                    this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ServerAddress + "' (" + ((ipAddress == null) ? "" : ipAddress.AddressFamily.ToString()) + ") failed: " + se.ToString());
                }

                this.HandleException(StatusCode.ExceptionOnConnect);
                return;
            }

            Thread run = new Thread(new ThreadStart(ReceiveLoop));
            run.Name = "photon receive thread";
            run.IsBackground = true;
            run.Start();
        }

        /// <summary>Endless loop, run in Receive Thread.</summary>
        public void ReceiveLoop()
        {
            byte[] inBuffer = new byte[this.MTU];
            while (this.State == PhotonSocketState.Connected)
            {
                try
                {
                    int read = this.sock.Receive(inBuffer);
                    this.HandleReceivedDatagram(inBuffer, read, true);
                }
                catch (Exception e)
                {
                    if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
                    {
                        if (this.ReportDebugOfLevel(DebugLevel.ERROR))
                        {
                            this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ServerAddress + "' Exception: " + e);
                        }

                        this.HandleException(StatusCode.ExceptionOnReceive);
                    }
                }
            } //while Connected receive

            // on exit of the receive-loop: disconnect socket
            this.Disconnect();
        }
    } //class

}
#endif
