/* Copyright (c) 2010 Michael Lidgren

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.Net;

namespace Lidgren.Network
{
	/// <summary>
	/// Partly immutable after NetPeer has been initialized
	/// </summary>
	public sealed class NetPeerConfiguration
	{
		private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer";

		private bool m_isLocked;
		internal bool m_acceptIncomingConnections;
		internal string m_appIdentifier;
		internal IPAddress m_localAddress;
		internal int m_port;
		internal int m_receiveBufferSize, m_sendBufferSize;
		internal int m_defaultOutgoingMessageCapacity;
		internal int m_maximumTransmissionUnit;
		internal bool m_useMessageCoalescing;
		internal int m_maximumConnections;
		internal NetIncomingMessageType m_disabledTypes;
		internal int m_throttleBytesPerSecond;
		internal int m_throttlePeakBytes;
		internal int m_maxRecycledBytesKept;

		// handshake, timeout and keepalive
		internal float m_handshakeAttemptDelay;
		internal int m_handshakeMaxAttempts;
		internal float m_connectionTimeout;
		internal float m_pingFrequency;

		// reliability
		internal float m_maxAckDelayTime;

		// bad network simulation
		internal float m_loss;
		internal float m_duplicates;
		internal float m_minimumOneWayLatency;
		internal float m_randomOneWayLatency;

		public NetPeerConfiguration(string appIdentifier)
		{
			if (string.IsNullOrEmpty(appIdentifier))
				throw new NetException("App identifier must be at least one character long");
			m_appIdentifier = appIdentifier.ToString(System.Globalization.CultureInfo.InvariantCulture);

			// defaults
			m_isLocked = false;
			m_acceptIncomingConnections = true;
			m_localAddress = IPAddress.Any;
			m_port = 0;
			m_receiveBufferSize = 131071;
			m_sendBufferSize = 131071;
			m_connectionTimeout = 25;
			m_maximumConnections = 16;
			m_defaultOutgoingMessageCapacity = 8;
			m_pingFrequency = 6.0f;
			m_throttleBytesPerSecond = 1024 * 256;
			m_throttlePeakBytes = 8192;
			m_maxAckDelayTime = 0.01f;
			m_handshakeAttemptDelay = 1.0f;
			m_handshakeMaxAttempts = 7;
			m_maxRecycledBytesKept = 128 * 1024;
			m_useMessageCoalescing = true;

			m_loss = 0.0f;
			m_minimumOneWayLatency = 0.0f;
			m_randomOneWayLatency = 0.0f;
			m_duplicates = 0.0f;

			// default disabled types
			m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage;

			// Maximum transmission unit
			// Ethernet can take 1500 bytes of payload, so lets stay below that.
			// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
			// 20 bytes IP header
			//  8 bytes UDP header
			//  5 bytes lidgren header for one message
			//  1 byte just to be on the safe side
			// Totals 1440 minus 34 = 1406 bytes free for payload
			m_maximumTransmissionUnit = 1406;
		}

		public NetPeerConfiguration Clone()
		{
			NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
			retval.m_isLocked = false;
			return retval;
		}

		internal void VerifyAndLock()
		{
			if (m_throttleBytesPerSecond != 0 && m_throttleBytesPerSecond < m_maximumTransmissionUnit)
				m_throttleBytesPerSecond = m_maximumTransmissionUnit;

			m_isLocked = true;
		}

#if DEBUG
		/// <summary>
		/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
		/// </summary>
		public float SimulatedLoss
		{
			get { return m_loss; }
			set { m_loss = value; }
		}

		/// <summary>
		/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
		/// </summary>
		public float SimulatedMinimumLatency
		{
			get { return m_minimumOneWayLatency; }
			set { m_minimumOneWayLatency = value; }
		}

		/// <summary>
		/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
		/// </summary>
		public float SimulatedRandomLatency
		{
			get { return m_randomOneWayLatency; }
			set { m_randomOneWayLatency = value; }
		}

		/// <summary>
		/// Gets the average simulated one way latency in seconds
		/// </summary>
		public float SimulatedAverageLatency
		{
			get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
		}

		/// <summary>
		/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
		/// </summary>
		public float SimulatedDuplicatesChance
		{
			get { return m_duplicates; }
			set { m_duplicates = value; }
		}

#endif

		/// <summary>
		/// Gets or sets the identifier of this application; the library can only connect to matching app identifier peers
		/// </summary>
		public string AppIdentifier
		{
			get { return m_appIdentifier; }
		}

		/// <summary>
		/// Enables receiving of the specified type of message
		/// </summary>
		public void EnableMessageType(NetIncomingMessageType type)
		{
			m_disabledTypes &= (~type);
		}

		/// <summary>
		/// Disables receiving of the specified type of message
		/// </summary>
		public void DisableMessageType(NetIncomingMessageType type)
		{
			m_disabledTypes |= type;
		}

		/// <summary>
		/// Enables or disables receiving of the specified type of message
		/// </summary>
		public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled)
		{
			if (enabled)
				m_disabledTypes &= (~type);
			else
				m_disabledTypes |= type;
		}

		/// <summary>
		/// Gets if receiving of the specified type of message is enabled
		/// </summary>
		public bool IsMessageTypeEnabled(NetIncomingMessageType type)
		{
			return !((m_disabledTypes & type) == type);
		}

		/// <summary>
		/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers
		/// </summary>
		public int MaximumTransmissionUnit
		{
			get { return m_maximumTransmissionUnit; }
			set
			{
				if (value < 1 || value >= 4096)
					throw new NetException("MaximumTransmissionUnit must be between 1 and 4095 bytes");
				m_maximumTransmissionUnit = value;
			}
		}

		/// <summary>
		/// Gets or sets if message coalescing (sending multiple messages in a single packet) should be used. Normally this should be true.
		/// </summary>
		public bool UseMessageCoalescing
		{
			get { return m_useMessageCoalescing; }
			set { m_useMessageCoalescing = value; }
		}

		/// <summary>
		/// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public int MaximumConnections
		{
			get { return m_maximumConnections; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_maximumConnections = value;
			}
		}

		/// <summary>
		/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
		/// </summary>
		public bool AcceptIncomingConnections
		{
			get { return m_acceptIncomingConnections; }
			set { m_acceptIncomingConnections = value; }
		}

		/// <summary>
		/// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument
		/// </summary>
		public int DefaultOutgoingMessageCapacity
		{
			get { return m_defaultOutgoingMessageCapacity; }
			set { m_defaultOutgoingMessageCapacity = value; }
		}

		/// <summary>
		/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public IPAddress LocalAddress
		{
			get { return m_localAddress; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_localAddress = value;
			}
		}

		/// <summary>
		/// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public int Port
		{
			get { return m_port; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_port = value;
			}
		}

		/// <summary>
		/// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public int ReceiveBufferSize
		{
			get { return m_receiveBufferSize; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_receiveBufferSize = value;
			}
		}

		/// <summary>
		/// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public int SendBufferSize
		{
			get { return m_sendBufferSize; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_sendBufferSize = value;
			}
		}

		/// <summary>
		/// Gets or sets the number of seconds of non-response before disconnecting because of time out. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public float ConnectionTimeout
		{
			get { return m_connectionTimeout; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_connectionTimeout = value;
			}
		}

		/// <summary>
		/// Gets or sets the number of seconds between latency calculation (rtt) pings
		/// </summary>
		public float PingFrequency
		{
			get { return m_pingFrequency; }
			set { m_pingFrequency = value; }
		}

		/// <summary>
		/// Gets or sets the number of allowed bytes to be sent per second per connection; 0 means unlimited (throttling disabled)
		/// </summary>
		public int ThrottleBytesPerSecond
		{
			get { return m_throttleBytesPerSecond; }
			set
			{
				if (m_throttleBytesPerSecond != 0 && m_throttleBytesPerSecond < m_maximumTransmissionUnit)
					throw new NetException("ThrottleBytesPerSecond can not be lower than MaximumTransmissionUnit");
				m_throttleBytesPerSecond = value;
			}
		}

		/// <summary>
		/// Gets or sets the peak number of bytes sent before throttling kicks in, if enabled
		/// </summary>
		public int ThrottlePeakBytes
		{
			get { return m_throttlePeakBytes; }
			set
			{
				if (m_throttlePeakBytes < m_maximumTransmissionUnit)
					throw new NetException("ThrottlePeakBytes can not be lower than MaximumTransmissionUnit");
				m_throttlePeakBytes = value;
			}
		}

		/// <summary>
		/// Gets or sets the number between handshake attempts in seconds
		/// </summary>
		public float HandshakeAttemptDelay
		{
			get { return m_handshakeAttemptDelay; }
			set { m_handshakeAttemptDelay = value; }
		}

		/// <summary>
		/// Gets or sets the maximum number of handshake attempts before declaring failure to shake hands
		/// </summary>
		public int HandshakeMaxAttempts
		{
			get { return m_handshakeMaxAttempts; }
			set { m_handshakeMaxAttempts = value; }
		}

		/// <summary>
		/// Gets or sets the maximum number of bytes kept in the recycle pool. Cannot be changed once NetPeer is initialized.
		/// </summary>
		public int MaxRecycledBytesKept
		{
			get { return m_maxRecycledBytesKept; }
			set
			{
				if (m_isLocked)
					throw new NetException(c_isLockedMessage);
				m_maxRecycledBytesKept = value;
			}
		}
	}
}
