using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System;

namespace AsterNET.IO
{
	/// <summary>
	/// Socket connection to asterisk.
	/// </summary>
	public class SocketConnection
	{
		private TcpClient tcpClient;
		private NetworkStream networkStream;
		private StreamReader reader;
		private BinaryWriter writer;
		private Encoding encoding;
		private bool initial;

		#region Constructor - SocketConnection(string host, int port, int receiveTimeout) 
		/// <summary>
		/// Consructor
		/// </summary>
		/// <param name="host">client host</param>
		/// <param name="port">client port</param>
		/// <param name="encoding">encoding</param>
		public SocketConnection(string host, int port, Encoding encoding)
			:this(new TcpClient(host, port), encoding)
		{
		}

		/// <summary>
		/// Consructor
		/// </summary>
		/// <param name="host">client host</param>
		/// <param name="port">client port</param>
		/// <param name="encoding">encoding</param>
		/// <param name="receiveBufferSize">size of the receive buffer.</param>
		public SocketConnection(string host, int port,int receiveBufferSize, Encoding encoding)
			: this (new TcpClient(host, port) {ReceiveBufferSize = receiveBufferSize }, encoding)
		{
		}

		#endregion

		#region Constructor - SocketConnection(socket) 
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="tcpClient">TCP client from Listener</param>
		/// <param name="encoding">encoding</param>
		internal SocketConnection(TcpClient tcpClient, Encoding encoding)
		{
			initial = true;
			this.encoding = encoding;
			this.tcpClient = tcpClient;
			this.networkStream = this.tcpClient.GetStream();
			this.reader = new StreamReader(this.networkStream, encoding);
			this.writer = new BinaryWriter(this.networkStream, encoding);
		}
		#endregion

		public TcpClient TcpClient
		{
			get { return tcpClient; }
		}

		public NetworkStream NetworkStream
		{
			get { return networkStream; }
		}

		public Encoding Encoding
		{
			get { return encoding; }
		}

		public bool Initial
		{
			get { return initial; }
			set { initial = value; }
		}

		#region IsConnected 
		/// <summary>
		/// Returns the connection state of the socket.
		/// </summary>
		public bool IsConnected
		{
			get { return tcpClient.Connected; }
		}
		#endregion

		#region LocalAddress 
		public IPAddress LocalAddress
		{
			get
			{
				return ((IPEndPoint)(tcpClient.Client.LocalEndPoint)).Address;
			}
		}
		#endregion

		#region LocalPort 
		public int LocalPort
		{
			get
			{
				return ((IPEndPoint)(tcpClient.Client.LocalEndPoint)).Port;
			}
		}
		#endregion

		#region RemoteAddress 
		public IPAddress RemoteAddress
		{
			get
			{
				return ((IPEndPoint)(tcpClient.Client.RemoteEndPoint)).Address;
			}
		}
		#endregion

		#region RemotePort 
		public int RemotePort
		{
			get
			{
				return ((IPEndPoint)(tcpClient.Client.LocalEndPoint)).Port;
			}
		}
		#endregion

		#region ReadLine()
		/// <summary>
		/// Reads a line of text from the socket connection. The current thread is
		/// blocked until either the next line is received or an IOException
		/// encounters.
		/// </summary>
		/// <returns>the line of text received excluding any newline character</returns>
		/// <throws>  IOException if the connection has been closed. </throws>
		public string ReadLine()
		{
			string line = null;
			try
			{
				line = reader.ReadLine();
			}
			catch
			{
				line = null;
			}
			return line;
		}
		#endregion


		#region Write(string s)
		/// <summary>
		/// Sends a given String to the socket connection.
		/// </summary>
		/// <param name="s">the String to send.</param>
		/// <throws> IOException if the String cannot be sent, maybe because the </throws>
		/// <summary>connection has already been closed.</summary>
		public void Write(string s)
		{
			writer.Write(encoding.GetBytes(s));
			writer.Flush();
		}
		#endregion

		#region Write(string msg) 
		/// <summary>
		/// Sends a given String to the socket connection.
		/// </summary>
		/// <param name="msg">the String to send.</param>
		/// <throws> IOException if the String cannot be sent, maybe because the </throws>
		/// <summary>connection has already been closed.</summary>
		public void WriteEx(string msg)
		{
			byte[] data = encoding.GetBytes(msg);
			networkStream.BeginWrite(data, 0, data.Length, onWriteFinished, networkStream);
			networkStream.Flush();
		}

		private void onWriteFinished(IAsyncResult ar)
		{
			NetworkStream stream = (NetworkStream)ar.AsyncState;
			stream.EndWrite(ar);
		}
		#endregion

		#region Close
		/// <summary>
		/// Closes the socket connection including its input and output stream and
		/// frees all associated ressources.<br/>
		/// When calling close() any Thread currently blocked by a call to readLine()
		/// will be unblocked and receive an IOException.
		/// </summary>
		/// <throws>  IOException if the socket connection cannot be closed. </throws>
		public void Close()
		{
			try
			{
				tcpClient.Client.Shutdown(SocketShutdown.Both);
				tcpClient.Client.Close();
				tcpClient.Close();
			}
			catch { }
		}
		#endregion
	}
}