using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace AllocsFixes.NetConnections.Servers.Telnet
{
	public class TelnetConnection : IConnection
	{
		private readonly BlockingQueue<string> toClientQueue = new BlockingQueue<string> ();
		private readonly Telnet telnet;
		private bool authenticated = false;
		private readonly bool authEnabled;
		private readonly TcpClient client;
		private readonly EndPoint endpoint;
		private readonly int endpointAddressHash;
		private bool closed = false;

		public bool IsClosed { get { return closed; } }

		public bool IsAuthenticated { get { return !authEnabled || authenticated; } }

		public int EndPointHash { get { return endpointAddressHash; } }

		public TelnetConnection (Telnet owner, TcpClient client, bool authEnabled)
		{
			this.telnet = owner;
			this.authEnabled = authEnabled;
			this.client = client;
			this.endpoint = client.Client.RemoteEndPoint;

			if (endpoint is IPEndPoint) {
				endpointAddressHash = ((IPEndPoint)endpoint).Address.GetHashCode ();
			} else {
				endpointAddressHash = endpoint.GetHashCode ();
				Log.Out ("EndPoint is not an IPEndPoint but: " + endpoint.GetType ().ToString ());
			}

			Log.Out ("Telnet connection from: " + endpoint);

			ThreadManager.StartThread ("TelnetClientReceive_" + endpoint.ToString (), new ThreadManager.ThreadFunctionDelegate (ReceiveThread), System.Threading.ThreadPriority.BelowNormal);
			ThreadManager.StartThread ("TelnetClientSend_" + endpoint.ToString (), new ThreadManager.ThreadFunctionDelegate (SendThread), System.Threading.ThreadPriority.BelowNormal);

			if (authEnabled) {
				toClientQueue.Enqueue ("Please enter password:");
			} else {
				LoginMessage ();
			}
		}

		private void LoginMessage ()
		{
			toClientQueue.Enqueue ("*** Connected with 7DTD server.");
			toClientQueue.Enqueue ("*** Server version: " + GamePrefs.GetString(EnumGamePrefs.GameVersion));
			toClientQueue.Enqueue ("*** Dedicated server only build");
			toClientQueue.Enqueue ("*** Allocs server fixes loaded");
			toClientQueue.Enqueue (string.Empty);
			toClientQueue.Enqueue ("Server IP:   " + 
				((GamePrefs.GetString (EnumGamePrefs.ServerIP) != null && GamePrefs.GetString (EnumGamePrefs.ServerIP).Length != 0) ? GamePrefs.GetString (EnumGamePrefs.ServerIP) : "Any")
			);
			toClientQueue.Enqueue ("Server port: " + GamePrefs.GetInt (EnumGamePrefs.ServerPort));
			toClientQueue.Enqueue ("Max players: " + GamePrefs.GetInt (EnumGamePrefs.ServerMaxPlayerCount));
			toClientQueue.Enqueue ("Game mode:   " + GamePrefs.GetString (EnumGamePrefs.GameMode));
			toClientQueue.Enqueue ("World:       " + GamePrefs.GetString (EnumGamePrefs.GameWorld));
			toClientQueue.Enqueue ("Game name:   " + GamePrefs.GetString (EnumGamePrefs.GameName));
			toClientQueue.Enqueue ("Difficulty:  " + GamePrefs.GetInt (EnumGamePrefs.GameDifficulty));
			toClientQueue.Enqueue (string.Empty);
			toClientQueue.Enqueue ("Press 'help' to get a list of all commands. Press 'exit' to end session.");
			toClientQueue.Enqueue (string.Empty);
		}

		private void ReceiveThread (ThreadManager.ThreadInfo _tInfo)
		{
			try {
				StreamReader reader = new StreamReader (client.GetStream ());
				while (!closed) {
					string line = reader.ReadLine ();
					if (line != null && (line = line.Trim ()).Length > 0) {
						if (!IsAuthenticated) {
							if (line.Equals (GamePrefs.GetString (EnumGamePrefs.TelnetPassword))) {
								authenticated = true;
								toClientQueue.Enqueue ("Logon successful.");
								toClientQueue.Enqueue (string.Empty);
								toClientQueue.Enqueue (string.Empty);
								toClientQueue.Enqueue (string.Empty);
								LoginMessage ();
							} else {
								if (telnet.RegisterFailedLogin (this)) {
									toClientQueue.Enqueue ("Password incorrect, please enter password:");
								} else {
									toClientQueue.Enqueue ("Too many failed login attempts!");
									Thread.Sleep (100);
									Close (true);
									break;
								}
							}
						} else {
							if (line.ToLower ().Equals ("exit")) {
								break;
							}
							Log.Out ("Telnet executed \"" + line + "\" from: " + endpoint);
							ConsoleOutputSeparator.QueueNetCommand (line, this);
						}
					}
				}
			} catch (Exception) {
			}

			if (!closed)
				Close ();
		}

		private void SendThread (ThreadManager.ThreadInfo _tInfo)
		{
			try {
				while (!closed && client.GetStream ().CanWrite) {
					string line = toClientQueue.Dequeue ();
					if (!closed && client.GetStream ().CanWrite) {
						if (line == null) {
							client.GetStream ().WriteByte (0);
						} else {
							byte[] utfData = Encoding.UTF8.GetBytes (line);
							client.GetStream ().Write (utfData, 0, utfData.Length);
							client.GetStream ().WriteByte (13);
							client.GetStream ().WriteByte (10);
						}
					}
				}
			} catch (Exception) {
			}

			if (!closed)
				Close ();
		}

		public void Close (bool kickedForLogins = false)
		{
			if (!closed) {
				closed = true;
				toClientQueue.Close ();
				client.GetStream ().Close ();
				client.Close ();
				telnet.ConnectionClosed (this);

				if (kickedForLogins)
					Log.Out ("Telnet connection closed for too many login attempts: " + endpoint);
				else
					Log.Out ("Telnet connection closed: " + endpoint);
			}
		}

		public void SendLine (string line)
		{
			if (!closed && IsAuthenticated)
				toClientQueue.Enqueue (line);
			else
				toClientQueue.Enqueue (null);
		}

		public void SendLog (string text, string trace, UnityEngine.LogType type)
		{
			throw new System.NotImplementedException ();
		}

		public string GetDescription ()
		{
			return "Telnet from " + endpoint;
		}
	}
}
