using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using CefSharp;
using TweetLib.Core.Browser;

namespace TweetDuck.Resources {
	internal sealed class ResourceProvider : IResourceProvider<IResourceHandler> {
		private readonly Dictionary<string, ICachedResource> cache = new Dictionary<string, ICachedResource>();

		public IResourceHandler Status(HttpStatusCode code, string message) {
			return CreateStatusHandler(code, message);
		}

		public IResourceHandler File(string path) {
			string key = new Uri(path).LocalPath;

			if (cache.TryGetValue(key, out var cachedResource)) {
				return cachedResource.GetResource();
			}

			cachedResource = FileWithCaching(path);
			cache[key] = cachedResource;
			return cachedResource.GetResource();
		}

		private ICachedResource FileWithCaching(string path) {
			try {
				return new CachedFile(System.IO.File.ReadAllBytes(path), Path.GetExtension(path));
			} catch (FileNotFoundException) {
				return new CachedStatus(HttpStatusCode.NotFound, "File not found.");
			} catch (DirectoryNotFoundException) {
				return new CachedStatus(HttpStatusCode.NotFound, "Directory not found.");
			} catch (Exception e) {
				return new CachedStatus(HttpStatusCode.InternalServerError, e.Message);
			}
		}

		public void ClearCache() {
			cache.Clear();
		}

		private static ResourceHandler CreateHandler(byte[] bytes) {
			var handler = ResourceHandler.FromStream(new MemoryStream(bytes), autoDisposeStream: true);
			handler.Headers.Set("Access-Control-Allow-Origin", "*");
			return handler;
		}

		private static IResourceHandler CreateFileContentsHandler(byte[] bytes, string extension) {
			if (bytes.Length == 0) {
				return CreateStatusHandler(HttpStatusCode.NoContent, "File is empty."); // FromByteArray crashes CEF internals with no contents
			}
			else {
				var handler = CreateHandler(bytes);
				handler.MimeType = Cef.GetMimeType(extension);
				return handler;
			}
		}

		private static IResourceHandler CreateStatusHandler(HttpStatusCode code, string message) {
			var handler = CreateHandler(Encoding.UTF8.GetBytes(message));
			handler.StatusCode = (int) code;
			return handler;
		}

		private interface ICachedResource {
			IResourceHandler GetResource();
		}

		private sealed class CachedFile : ICachedResource {
			private readonly byte[] bytes;
			private readonly string extension;

			public CachedFile(byte[] bytes, string extension) {
				this.bytes = bytes;
				this.extension = extension;
			}

			public IResourceHandler GetResource() {
				return CreateFileContentsHandler(bytes, extension);
			}
		}

		private sealed class CachedStatus : ICachedResource {
			private readonly HttpStatusCode code;
			private readonly string message;

			public CachedStatus(HttpStatusCode code, string message) {
				this.code = code;
				this.message = message;
			}

			public IResourceHandler GetResource() {
				return CreateStatusHandler(code, message);
			}
		}
	}
}