using System;
using System.IO;
using System.Linq;
using System.Web;
using WebDAVSharp.Server.Adapters;
using WebDAVSharp.Server.Exceptions;
using WebDAVSharp.Server.Stores;

namespace WebDAVSharp.Server.MethodHandlers {
	/// <summary>
	/// This is the base class for <see cref="IWebDavMethodHandler" /> implementations.
	/// </summary>
	public abstract class WebDavMethodHandlerBase {
		private const int DepthInfinity = -1;

		/// <summary>
		/// Retrieves a store item through the specified
		/// <see cref="Uri" /> from the
		/// specified
		/// <see cref="WebDavServer" /> and
		/// <see cref="IWebDavStore" />.
		/// </summary>
		/// <param name="uri">The <see cref="Uri" /> to retrieve the store item for.</param>
		/// <param name="server">The <see cref="WebDavServer" /> that hosts the <paramref name="store" />.</param>
		/// <param name="store">The <see cref="IWebDavStore" /> from which to retrieve the store item.</param>
		/// <returns>
		/// The retrieved store item.
		/// </returns>
		/// <exception cref="System.ArgumentNullException"><para>
		///   <paramref name="uri" /> is <c>null</c>.</para>
		/// <para>
		///   <paramref name="server" /> is <c>null</c>.</para>
		/// <para>
		///   <paramref name="store" /> is <c>null</c>.</para></exception>
		/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavNotFoundException">If the item was not found.</exception>
		/// <exception cref="WebDavConflictException"><paramref name="uri" /> refers to a document in a collection, where the collection does not exist.</exception>
		/// <exception cref="WebDavNotFoundException"><paramref name="uri" /> refers to a document that does not exist.</exception>
		public static IWebDavStoreItem GetItem(WebDavServer server, IWebDavStore store, String fileName) {
			if (server == null)
				throw new ArgumentNullException("server");
			if (store == null)
				throw new ArgumentNullException("store");
			if (fileName == null)
				throw new ArgumentNullException("fileName");

			//Uri prefixUri = uri.GetPrefixUri(server);
			//String prefixUri = server.ServiceUrl;
			IWebDavStoreCollection collection = store.Root;

			IWebDavStoreItem item = null;
			//if (prefixUri.Segments.Length == uri.Segments.Length)
			if (fileName == "/")
				return collection;
			var dirNames = fileName.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);

			//for (int index = prefixUri.Segments.Length; index < uri.Segments.Length; index++) {
			for (int index = 0; index < dirNames.Length; index++) {
				var dirName = dirNames[index];
				//string segmentName = Uri.UnescapeDataString(uri.Segments[index]);
				//IWebDavStoreItem nextItem = collection.GetItemByName(segmentName.TrimEnd('/', '\\'));
				IWebDavStoreItem nextItem = collection.GetItemByName(dirName);
				if (nextItem == null)
					throw new WebDavNotFoundException(); //throw new WebDavConflictException();

				if (index == dirNames.Length - 1)
					item = nextItem;
				else {
					collection = nextItem as IWebDavStoreCollection;
					if (collection == null)
						throw new WebDavNotFoundException();
				}
			}

			if (item == null)
				throw new WebDavNotFoundException();

			return item;
		}

		/// <summary>
		/// Get the parent collection from the requested
		/// <see cref="Uri" />.
		/// <see cref="WebDavException" /> 409 Conflict possible.
		/// </summary>
		/// <param name="server">The <see cref="WebDavServer" /> through which the request came in from the client.</param>
		/// <param name="store">The <see cref="IWebDavStore" /> that the <see cref="WebDavServer" /> is hosting.</param>
		/// <param name="childUri">The <see cref="Uri" /> object containing the specific location of the child</param>
		/// <returns>
		/// The parrent collection as an <see cref="IWebDavStoreCollection" />
		/// </returns>
		/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException"></exception>
		/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavConflictException">
		/// </exception>
		/// <exception cref="WebDavUnauthorizedException">When the user is unauthorized and doesn't have access</exception>
		/// <exception cref="WebDavConflictException">When the parent collection doesn't exist</exception>
		public static IWebDavStoreCollection GetParentCollection(WebDavServer server, IWebDavStore store, String fileName) {
			String parentDirName = ExtractParentName(fileName);
			//Uri parentCollectionUri = childUri.GetParentUri();
			IWebDavStoreCollection collection;
			try {
				collection = GetItem(server, store, parentDirName) as IWebDavStoreCollection;
			}
			catch (UnauthorizedAccessException) {
				throw new WebDavUnauthorizedException();
			}
			catch (WebDavNotFoundException) {
				throw new WebDavConflictException();
			}
			if (collection == null)
				throw new WebDavConflictException();

			return collection;
		}

		public static string ExtractParentName(string fileName) {
			if (fileName == null)
				throw new ArgumentNullException("fileName");
			if (fileName == "/") {
				//TODO: better throw error?
				//throw new InvalidOperationException("Cannot get parent of root");
				return fileName;
			}

			//get path without the last part "dir1/dir2/<remove>/"
			return Path.GetDirectoryName(fileName.Trim('/', '\\')).Replace('\\', '/');
		}

		public string ExtractLocalPath(WebDavServer server, string absoluteUri) {
			var serviceLocalPath = new Uri(server.ServiceUrl).LocalPath.Trim('/');
			var startPos = absoluteUri.IndexOf(serviceLocalPath);
			absoluteUri = Uri.UnescapeDataString(absoluteUri);
			if (startPos > -1) {
				return '/' + absoluteUri.Substring(startPos + serviceLocalPath.Length).Trim('/', '\\');
			}else {
				return '/' + absoluteUri.Trim('/', '\\');
			}
		}

		/// <summary>
		/// Get the item in the collection from the requested
		/// <see cref="Uri" />.
		/// <see cref="WebDavException" /> 409 Conflict possible.
		/// </summary>
		/// <param name="collection">The parent collection as a <see cref="IWebDavStoreCollection" /></param>
		/// <param name="childUri">The <see cref="Uri" /> object containing the specific location of the child</param>
		/// <returns>
		/// The <see cref="IWebDavStoreItem" /> from the <see cref="IWebDavStoreCollection" />
		/// </returns>
		/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If user is not authorized to get access to the item</exception>
		/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavNotFoundException">If item not found.</exception>
		public static IWebDavStoreItem GetItemFromCollection(IWebDavStoreCollection collection, String fileName) {
			IWebDavStoreItem item;
			try {
				item = collection.GetItemByName(ExtractFileName(fileName));
			}
			catch (UnauthorizedAccessException) {
				throw new WebDavUnauthorizedException();
			}
			catch (WebDavNotFoundException) {
				throw new WebDavNotFoundException();
			}
			if (item == null)
				throw new WebDavNotFoundException();

			return item;
		}

		public static string ExtractFileName(string fileName) {
			//Uri.UnescapeDataString(childUri.Segments.Last().TrimEnd('/', '\\')));
			return Path.GetFileName(fileName);
		}

		/// <summary>
		/// Gets the Depth header : 0, 1 or infinity
		/// </summary>
		/// <param name="request">The <see cref="IHttpListenerContext" /> with the response included</param>
		/// <returns>
		/// The values 0, 1 or -1 (for infinity)
		/// </returns>
		public static int GetDepthHeader(HttpRequest request) {
			// get the value of the depth header as a string
			string depth = request.Headers["Depth"];

			// check if the string is valid or not infinity
			// if so, try to parse it to an int
			if (String.IsNullOrEmpty(depth) || depth.Equals("infinity"))
				return DepthInfinity;
			int value;
			if (!int.TryParse(depth, out value))
				return DepthInfinity;
			if (value == 0 || value == 1)
				return value;
			// else, return the infinity value
			return DepthInfinity;
		}

		/// <summary>
		/// Gets the Overwrite header : T or F
		/// </summary>
		/// <param name="request">The <see cref="IHttpListenerRequest"/> has the header included</param>
		/// <returns>The <see cref="bool"/> true if overwrite, false if no overwrite</returns>
		public static bool GetOverwriteHeader(HttpRequest request) {
			// get the value of the Overwrite header as a string
			string overwrite = request.Headers["Overwrite"];

			// check if the string is valid and if it equals T
			return overwrite != null && overwrite.Equals("T");
			// else, return false
		}

		/// <summary>
		/// Gets the Timeout header : Second-number
		/// </summary>
		/// <param name="request">The request with the request included</param>
		/// <returns>The value of the Timeout header as a string</returns>
		public static string GetTimeoutHeader(HttpRequest request) {
			// get the value of the timeout header as a string
			string timeout = request.Headers["Timeout"];

			// check if the string is valid or not infinity
			// if so, try to parse it to an int
			if (!String.IsNullOrEmpty(timeout) && !timeout.Equals("infinity") &&
					!timeout.Equals("Infinite, Second-4100000000"))
				return timeout;
			// else, return the timeout value as if it was requested to be 4 days
			return "Second-345600";
		}

		/// <summary>
		/// Gets the Destination header as an URI
		/// </summary>
		/// <param name="request">The <see cref="IHttpListenerRequest"/> has the header included</param>
		/// <returns>The <see cref="Uri"/> containing the destination</returns>
		public static Uri GetDestinationHeader(HttpRequest request) {
			// get the value of the Destination header as a string
			string destinationUri = request.Headers["Destination"];

			// check if the string is valid 
			if (!String.IsNullOrEmpty(destinationUri))
				return new Uri(destinationUri);
			// else, throw exception
			throw new WebDavConflictException();
		}
	}
}
