#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;

using log4net.Layout;
using log4net.Core;
using log4net.Util;

namespace log4net.Appender 
{
	/// <summary>
	/// Sends logging events as connectionless UDP datagrams to a remote host or a 
	/// multicast group using an <see cref="UdpClient" />.
	/// </summary>
	/// <remarks>
	/// <para>
	/// UDP guarantees neither that messages arrive, nor that they arrive in the correct order.
	/// </para>
	/// <para>
	/// To view the logging results, a custom application can be developed that listens for logging 
	/// events.
	/// </para>
	/// <para>
	/// When decoding events send via this appender remember to use the same encoding
	/// to decode the events as was used to send the events. See the <see cref="Encoding"/>
	/// property to specify the encoding to use.
	/// </para>
	/// </remarks>
	/// <example>
	/// This example shows how to log receive logging events that are sent 
	/// on IP address 244.0.0.1 and port 8080 to the console. The event is 
	/// encoded in the packet as a unicode string and it is decoded as such. 
	/// <code lang="C#">
	/// IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
	/// UdpClient udpClient;
	/// byte[] buffer;
	/// string loggingEvent;
	/// 
	/// try 
	/// {
	///     udpClient = new UdpClient(8080);
	///     
	///     while(true) 
	///     {
	///         buffer = udpClient.Receive(ref remoteEndPoint);
	///         loggingEvent = System.Text.Encoding.Unicode.GetString(buffer);
	///         Console.WriteLine(loggingEvent);
	///     }
	/// } 
	/// catch(Exception e) 
	/// {
	///     Console.WriteLine(e.ToString());
	/// }
	/// </code>
	/// <code lang="Visual Basic">
	/// Dim remoteEndPoint as IPEndPoint
	/// Dim udpClient as UdpClient
	/// Dim buffer as Byte()
	/// Dim loggingEvent as String
	/// 
	/// Try 
	///     remoteEndPoint = new IPEndPoint(IPAddress.Any, 0)
	///     udpClient = new UdpClient(8080)
	///
	///     While True
	///         buffer = udpClient.Receive(ByRef remoteEndPoint)
	///         loggingEvent = System.Text.Encoding.Unicode.GetString(buffer)
	///         Console.WriteLine(loggingEvent)
	///     Wend
	/// Catch e As Exception
	///     Console.WriteLine(e.ToString())
	/// End Try
	/// </code>
	/// <para>
	/// An example configuration section to log information using this appender to the 
	/// IP 224.0.0.1 on port 8080:
	/// </para>
	/// <code lang="XML" escaped="true">
	/// <appender name="UdpAppender" type="log4net.Appender.UdpAppender">
	///     <remoteAddress value="224.0.0.1" />
	///     <remotePort value="8080" />
	///     <layout type="log4net.Layout.PatternLayout" value="%-5level %logger [%ndc] - %message%newline" />
	/// </appender>
	/// </code>
	/// </example>
	/// <author>Gert Driesen</author>
	/// <author>Nicko Cadell</author>
	public class UdpAppender : AppenderSkeleton
	{
		#region Public Instance Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="UdpAppender" /> class.
		/// </summary>
		/// <remarks>
		/// The default constructor initializes all fields to their default values.
		/// </remarks>
		public UdpAppender() 
		{
		}

		#endregion Public Instance Constructors

		#region Public Instance Properties

		/// <summary>
		/// Gets or sets the IP address of the remote host or multicast group to which
		/// the underlying <see cref="UdpClient" /> should sent the logging event.
		/// </summary>
		/// <value>
		/// The IP address of the remote host or multicast group to which the logging event 
		/// will be sent.
		/// </value>
		/// <remarks>
		/// <para>
		/// Multicast addresses are identified by IP class <b>D</b> addresses (in the range 224.0.0.0 to
		/// 239.255.255.255).  Multicast packets can pass across different networks through routers, so
		/// it is possible to use multicasts in an Internet scenario as long as your network provider 
		/// supports multicasting.
		/// </para>
		/// <para>
		/// Hosts that want to receive particular multicast messages must register their interest by joining
		/// the multicast group.  Multicast messages are not sent to networks where no host has joined
		/// the multicast group.  Class <b>D</b> IP addresses are used for multicast groups, to differentiate
		/// them from normal host addresses, allowing nodes to easily detect if a message is of interest.
		/// </para>
		/// <para>
		/// Static multicast addresses that are needed globally are assigned by IANA.  A few examples are listed in the table below:
		/// </para>
		/// <para>
		/// <list type="table">
		///     <listheader>
		///         <term>IP Address</term>
		///         <description>Description</description>
		///     </listheader>
		///     <item>
		///         <term>224.0.0.1</term>
		///         <description>
		///             <para>
		///             Sends a message to all system on the subnet.
		///             </para>
		///         </description>
		///     </item>
		///     <item>
		///         <term>224.0.0.2</term>
		///         <description>
		///             <para>
		///             Sends a message to all routers on the subnet.
		///             </para>
		///         </description>
		///     </item>
		///     <item>
		///         <term>224.0.0.12</term>
		///         <description>
		///             <para>
		///             The DHCP server answers messages on the IP address 224.0.0.12, but only on a subnet.
		///             </para>
		///         </description>
		///     </item>
		/// </list>
		/// </para>
		/// <para>
		/// A complete list of actually reserved multicast addresses and their owners in the ranges
		/// defined by RFC 3171 can be found at the <A href="http://www.iana.org/assignments/multicast-addresses">IANA web site</A>. 
		/// </para>
		/// <para>
		/// The address range 239.0.0.0 to 239.255.255.255 is reserved for administrative scope-relative 
		/// addresses.  These addresses can be reused with other local groups.  Routers are typically 
		/// configured with filters to prevent multicast traffic in this range from flowing outside
		/// of the local network.
		/// </para>
		/// </remarks>
		public IPAddress RemoteAddress
		{
			get { return m_remoteAddress; }
			set { m_remoteAddress = value; }
		}

		/// <summary>
		/// Gets or sets the TCP port number of the remote host or multicast group to which 
		/// the underlying <see cref="UdpClient" /> should sent the logging event.
		/// </summary>
		/// <value>
		/// An integer value in the range <see cref="IPEndPoint.MinPort" /> to <see cref="IPEndPoint.MaxPort" /> 
		/// indicating the TCP port number of the remote host or multicast group to which the logging event 
		/// will be sent.
		/// </value>
		/// <remarks>
		/// The underlying <see cref="UdpClient" /> will send messages to this TCP port number
		/// on the remote host or multicast group.
		/// </remarks>
		/// <exception cref="ArgumentOutOfRangeException">The value specified is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
		public int RemotePort
		{
			get { return m_remotePort; }
			set 
			{
				if (value < IPEndPoint.MinPort || value > IPEndPoint.MaxPort) 
				{
					throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)value,
						"The value specified is less than " + 
						IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) + 
						" or greater than " + 
						IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
				} 
				else 
				{
					m_remotePort = value;
				}
			}
		}

		/// <summary>
		/// Gets or sets the TCP port number from which the underlying <see cref="UdpClient" /> will communicate.
		/// </summary>
		/// <value>
		/// An integer value in the range <see cref="IPEndPoint.MinPort" /> to <see cref="IPEndPoint.MaxPort" /> 
		/// indicating the TCP port number from which the underlying <see cref="UdpClient" /> will communicate.
		/// </value>
		/// <remarks>
		/// <para>
		/// The underlying <see cref="UdpClient" /> will bind to this port for sending messages.
		/// </para>
		/// <para>
		/// Setting the value to 0 (the default) will cause the udp client not to bind to
		/// a local port.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentOutOfRangeException">The value specified is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
		public int LocalPort
		{
			get { return m_localPort; }
			set 
			{
				if (value != 0 && (value < IPEndPoint.MinPort || value > IPEndPoint.MaxPort))
				{
					throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)value,
						"The value specified is less than " + 
						IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) + 
						" or greater than " + 
						IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
				} 
				else 
				{
					m_localPort = value;
				}
			}
		}

		/// <summary>
		/// Gets or sets <see cref="Encoding"/> used to write the packets.
		/// </summary>
		/// <value>
		/// The <see cref="Encoding"/> used to write the packets.
		/// </value>
		/// <remarks>
		/// <para>
		/// The <see cref="Encoding"/> used to write the packets.
		/// </para>
		/// </remarks>
		public Encoding Encoding
		{
			get { return m_encoding; }
			set { m_encoding = value; }
		}

		#endregion Public Instance Properties

		#region Protected Instance Properties

		/// <summary>
		/// Gets or sets the underlying <see cref="UdpClient" />.
		/// </summary>
		/// <value>
		/// The underlying <see cref="UdpClient" />.
		/// </value>
		/// <remarks>
		/// <see cref="UdpAppender" /> creates a <see cref="UdpClient" /> to send logging events 
		/// over a network.  Classes deriving from <see cref="UdpAppender" /> can use this
		/// property to get or set this <see cref="UdpClient" />.  Use the underlying <see cref="UdpClient" />
		/// returned from <see cref="Client" /> if you require access beyond that which 
		/// <see cref="UdpAppender" /> provides.
		/// </remarks>
		protected UdpClient Client
		{
			get { return this.m_client; }
			set { this.m_client = value; }
		}

		/// <summary>
		/// Gets or sets the cached remote endpoint to which the logging events should be sent.
		/// </summary>
		/// <value>
		/// The cached remote endpoint to which the logging events will be sent.
		/// </value>
		/// <remarks>
		/// The <see cref="ActivateOptions" /> method will initialize the remote endpoint 
		/// with the values of the <see cref="RemoteAddress" /> and <see cref="RemotePort"/>
		/// properties.
		/// </remarks>
		protected IPEndPoint RemoteEndPoint
		{
			get { return this.m_remoteEndPoint; }
			set { this.m_remoteEndPoint = value; }
		}

		#endregion Protected Instance Properties

		#region Implementation of IOptionHandler

		/// <summary>
		/// Initialize the appender based on the options set.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This is part of the <see cref="IOptionHandler"/> delayed object
		/// activation scheme. The <see cref="ActivateOptions"/> method must 
		/// be called on this object after the configuration properties have
		/// been set. Until <see cref="ActivateOptions"/> is called this
		/// object is in an undefined state and must not be used. 
		/// </para>
		/// <para>
		/// If any of the configuration properties are modified then 
		/// <see cref="ActivateOptions"/> must be called again.
		/// </para>
		/// <para>
		/// The appender will be ignored if no <see cref="RemoteAddress" /> was specified or 
		/// an invalid remote or local TCP port number was specified.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">The required property <see cref="RemoteAddress" /> was not specified.</exception>
		/// <exception cref="ArgumentOutOfRangeException">The TCP port number assigned to <see cref="LocalPort" /> or <see cref="RemotePort" /> is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
		public override void ActivateOptions()
		{
			base.ActivateOptions();

			if (this.RemoteAddress == null) 
			{
				throw new ArgumentNullException("The required property 'Address' was not specified.");
			} 
			else if (this.RemotePort < IPEndPoint.MinPort || this.RemotePort > IPEndPoint.MaxPort) 
			{
				throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("this.RemotePort", (object)this.RemotePort,
					"The RemotePort is less than " + 
					IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) + 
					" or greater than " + 
					IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
			} 
			else if (this.LocalPort != 0 && (this.LocalPort < IPEndPoint.MinPort || this.LocalPort > IPEndPoint.MaxPort))
			{
				throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("this.LocalPort", (object)this.LocalPort,
					"The LocalPort is less than " + 
					IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) + 
					" or greater than " + 
					IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + ".");
			} 
			else 
			{
				this.RemoteEndPoint = new IPEndPoint(this.RemoteAddress, this.RemotePort);
				this.InitializeClientConnection();
			}
		}

		#endregion

		#region Override implementation of AppenderSkeleton

		/// <summary>
		/// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> method.
		/// </summary>
		/// <param name="loggingEvent">The event to log.</param>
		/// <remarks>
		/// <para>
		/// Sends the event using an UDP datagram.
		/// </para>
		/// <para>
		/// Exceptions are passed to the <see cref="AppenderSkeleton.ErrorHandler"/>.
		/// </para>
		/// </remarks>
		protected override void Append(LoggingEvent loggingEvent) 
		{
			try 
			{
				Byte [] buffer = m_encoding.GetBytes(RenderLoggingEvent(loggingEvent).ToCharArray());
#if NETSTANDARD1_3
				Client.SendAsync(buffer, buffer.Length, RemoteEndPoint).Wait();
#else
				this.Client.Send(buffer, buffer.Length, this.RemoteEndPoint);
#endif
			} 
			catch (Exception ex) 
			{
				ErrorHandler.Error(
					"Unable to send logging event to remote host " + 
					this.RemoteAddress.ToString() + 
					" on port " + 
					this.RemotePort + ".", 
					ex, 
					ErrorCode.WriteFailure);
			}
		}

		/// <summary>
		/// This appender requires a <see cref="Layout"/> to be set.
		/// </summary>
		/// <value><c>true</c></value>
		/// <remarks>
		/// <para>
		/// This appender requires a <see cref="Layout"/> to be set.
		/// </para>
		/// </remarks>
		override protected bool RequiresLayout
		{
			get { return true; }
		}

		/// <summary>
		/// Closes the UDP connection and releases all resources associated with 
		/// this <see cref="UdpAppender" /> instance.
		/// </summary>
		/// <remarks>
		/// <para>
		/// Disables the underlying <see cref="UdpClient" /> and releases all managed 
		/// and unmanaged resources associated with the <see cref="UdpAppender" />.
		/// </para>
		/// </remarks>
		override protected void OnClose() 
		{
			base.OnClose();

			if (this.Client != null) 
			{
				this.Client.Close();
				this.Client = null;
			}
		}

		#endregion Override implementation of AppenderSkeleton

		#region Protected Instance Methods

		/// <summary>
		/// Initializes the underlying  <see cref="UdpClient" /> connection.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The underlying <see cref="UdpClient"/> is initialized and binds to the 
		/// port number from which you intend to communicate.
		/// </para>
		/// <para>
		/// Exceptions are passed to the <see cref="AppenderSkeleton.ErrorHandler"/>.
		/// </para>
		/// </remarks>
		protected virtual void InitializeClientConnection() 
		{
			try 
			{
				if (this.LocalPort == 0)
				{
#if NETCF || NET_1_0 || SSCLI_1_0 || CLI_1_0
					this.Client = new UdpClient();
#else
					this.Client = new UdpClient(RemoteAddress.AddressFamily);
#endif
				}
				else
				{
#if NETCF || NET_1_0 || SSCLI_1_0 || CLI_1_0
					this.Client = new UdpClient(this.LocalPort);
#else
					this.Client = new UdpClient(this.LocalPort, RemoteAddress.AddressFamily);
#endif
				}
			} 
			catch (Exception ex) 
			{
				ErrorHandler.Error(
					"Could not initialize the UdpClient connection on port " + 
					this.LocalPort.ToString(NumberFormatInfo.InvariantInfo) + ".", 
					ex, 
					ErrorCode.GenericFailure);

				this.Client = null;
			}
		}

		#endregion Protected Instance Methods

		#region Private Instance Fields

		/// <summary>
		/// The IP address of the remote host or multicast group to which 
		/// the logging event will be sent.
		/// </summary>
		private IPAddress m_remoteAddress;
		
		/// <summary>
		/// The TCP port number of the remote host or multicast group to 
		/// which the logging event will be sent.
		/// </summary>
		private int m_remotePort;

		/// <summary>
		/// The cached remote endpoint to which the logging events will be sent.
		/// </summary>
		private IPEndPoint m_remoteEndPoint;

		/// <summary>
		/// The TCP port number from which the <see cref="UdpClient" /> will communicate.
		/// </summary>
		private int m_localPort;

		/// <summary>
		/// The <see cref="UdpClient" /> instance that will be used for sending the 
		/// logging events.
		/// </summary>
		private UdpClient m_client;

		/// <summary>
		/// The encoding to use for the packet.
		/// </summary>
#if NETSTANDARD1_3
		private Encoding m_encoding = Encoding.Unicode;
#else
		private Encoding m_encoding = Encoding.Default;
#endif

		#endregion Private Instance Fields
	}
}