using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using TweetLib.Browser.Interfaces;
using IOFile = System.IO.File;

namespace TweetLib.Core.Resources {
	public sealed class CachingResourceProvider<T> : IResourceProvider<T> {
		private readonly IResourceProvider<T> resourceProvider;
		private readonly Dictionary<string, ICachedResource> cache = new ();

		public CachingResourceProvider(IResourceProvider<T> resourceProvider) {
			this.resourceProvider = resourceProvider;
		}

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

		public T Status(HttpStatusCode code, string message) {
			return resourceProvider.Status(code, message);
		}

		public T File(byte[] contents, string extension) {
			return resourceProvider.File(contents, extension);
		}

		internal T CachedFile(string path) {
			string key = new Uri(path).LocalPath;

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

			ICachedResource resource;
			try {
				resource = new CachedFileResource(IOFile.ReadAllBytes(path), Path.GetExtension(path));
			} catch (FileNotFoundException) {
				resource = new CachedStatusResource(HttpStatusCode.NotFound, "File not found.");
			} catch (DirectoryNotFoundException) {
				resource = new CachedStatusResource(HttpStatusCode.NotFound, "Directory not found.");
			} catch (Exception e) {
				resource = new CachedStatusResource(HttpStatusCode.InternalServerError, e.Message);
			}

			cache[key] = resource;
			return resource.GetResource(resourceProvider);
		}

		private interface ICachedResource {
			T GetResource(IResourceProvider<T> resourceProvider);
		}

		private sealed class CachedFileResource : ICachedResource {
			private readonly byte[] contents;
			private readonly string extension;

			public CachedFileResource(byte[] contents, string extension) {
				this.contents = contents;
				this.extension = extension;
			}

			T ICachedResource.GetResource(IResourceProvider<T> resourceProvider) {
				return resourceProvider.File(contents, extension);
			}
		}

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

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

			public T GetResource(IResourceProvider<T> resourceProvider) {
				return resourceProvider.Status(code, message);
			}
		}
	}
}