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

namespace AllocsFixes.NetConnections.Servers.Web
{
	public class Web : IServer {
		private readonly HttpListener _listener = new HttpListener ();
		private Dictionary<string, PathHandler> handlers = new Dictionary<string, PathHandler> ();
		private bool authEnabled = false;
		private string realm = "7dtd Admin Panel";
		public static int handlingCount = 0;
		public static int currentHandlers = 0;
		private string dataFolder;
		private bool mapEnabled = false;

		public Web () {
			try {
				int webPort = GamePrefs.GetInt (EnumGamePrefs.ControlPanelPort);
				if (webPort < 1 || webPort > 65533) {
					Log.Out ("Webserver not started (ControlPanelPort not within 1-65534)");
					return;
				}
				if (!Directory.Exists (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver")) {
					Log.Out ("Webserver not started (folder \"webserver\" not found in WebInterface mod folder)");
					return;
				}

				dataFolder = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver";

				if (!HttpListener.IsSupported) {
					Log.Out ("Webserver not started (needs Windows XP SP2, Server 2003 or later or Mono)");
					return;
				}
 
				handlers.Add (
						"/index.htm",
						new SimpleRedirectHandler ("/static/index.html"));
				handlers.Add (
						"/favicon.ico",
						new SimpleRedirectHandler ("/static/favicon.ico"));
				handlers.Add (
						"/static/",
						new StaticHandler (
								"/static/",
								dataFolder,
								new AllocsFixes.FileCache.DirectAccess (),
								true)
				); // TODO: Enable cache

				{
					if (AllocsFixes.Mods.ModLoaded ("AllocsMapRendering")) {
						Assembly renderAssembly = AllocsFixes.Mods.GetModAssembly ("AllocsMapRendering");
						foreach (Type t in renderAssembly.GetTypes ()) {
							foreach (MethodInfo mi in t.GetMethods ()) {
								if (mi.Name == "GetTileCache") {
									handlers.Add (
										"/map/",
										new StaticHandler (
											"/map/",
											StaticDirectories.GetSaveGameDir () + "/map",
											mi.Invoke (null, new object[0]) as AllocsFixes.FileCache.AbstractCache,
											false)
									);
									mapEnabled = true;
								}
							}
						}
					}
				}
				handlers.Add ("/api/", new ApiHandler ("/api/"));

				_listener.Prefixes.Add (String.Format ("http://*:{0}/", webPort + 2));
				authEnabled = File.Exists (dataFolder + "/protect");
				if (authEnabled) {
					_listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
				}
				_listener.Start ();
				_listener.Realm = realm;

				NetTelnetServer.RegisterServer (this);

				_listener.BeginGetContext (new AsyncCallback (HandleRequest), _listener);

				Log.Out ("Started Webserver on " + (webPort + 2) + " (authentication " + (authEnabled ? "enabled" : "disabled") + ")");
			} catch (Exception e) {
				Log.Out ("Error in Web.ctor: " + e);
			}
		}

		private void HandleRequest (IAsyncResult result) {
			if (_listener.IsListening) {
				Interlocked.Increment (ref handlingCount);
				Interlocked.Increment (ref currentHandlers);
				HttpListenerContext ctx = _listener.EndGetContext (result);
				_listener.BeginGetContext (new AsyncCallback (HandleRequest), _listener);
				try {
					ctx.Response.ProtocolVersion = new Version ("1.0");

					HttpListenerBasicIdentity user = Authorize (ctx);

					if (!authEnabled || (user.Name.ToLower ().Equals ("admin") && user.Password.Equals (GamePrefs.GetString (EnumGamePrefs.ControlPanelPassword)))) {
						if (ctx.Request.Url.AbsolutePath.Length < 2) {
							handlers ["/index.htm"].HandleRequest (ctx.Request, ctx.Response, user);
							return;
						} else {
							foreach (KeyValuePair<string, PathHandler> kvp in handlers) {
								if (ctx.Request.Url.AbsolutePath.StartsWith (kvp.Key)) {
									kvp.Value.HandleRequest (ctx.Request, ctx.Response, user);
									return;
								}
							}
						}

						Log.Out ("Error in Web.HandleRequest(): No handler found for path \"" + ctx.Request.Url.AbsolutePath + "\"");
						ctx.Response.StatusCode = (int)HttpStatusCode.NotFound;
					} else {
						ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
						ctx.Response.Headers ["WWW-Authenticate"] = "Basic realm=\"" + realm + "\"";
					}
				} catch (IOException e) {
					if (e.InnerException is SocketException) {
						Log.Out ("Error in Web.HandleRequest(): Remote host closed connection: " + e.InnerException.Message);
					} else {
						Log.Out ("Error (IO) in Web.HandleRequest(): " + e);
					}
				} catch (Exception e) {
					Log.Out ("Error in Web.HandleRequest(): " + e);
				} finally {
					if (ctx != null) {
						ctx.Response.OutputStream.Close ();
					}
					Interlocked.Decrement (ref currentHandlers);
				}
			}
		}

		private HttpListenerBasicIdentity Authorize (HttpListenerContext ctx) {
			try {
				return (HttpListenerBasicIdentity)ctx.User.Identity;
			} catch (NullReferenceException) {
				return null;
			}
		}

		public void Disconnect () {
			try {
				_listener.Stop ();
				_listener.Close ();
			} catch (Exception e) {
				Log.Out ("Error in Web.Disconnect: " + e);
			}
		}

		public void SendLine (string line) {
			try {
				//Log.Out ("NOT IMPLEMENTED: Web.WriteToClient");
			} catch (Exception e) {
				Log.Out ("Error in Web.WriteToClient: " + e);
			}
		}

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

		public static void SetResponseTextContent (HttpListenerResponse resp, string text) {
			byte[] buf = Encoding.UTF8.GetBytes (text);
			resp.ContentLength64 = buf.Length;
			resp.ContentType = "text/html";
			resp.ContentEncoding = Encoding.UTF8;
			resp.OutputStream.Write (buf, 0, buf.Length);
		}

	}
}
