using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace FileManager
{
	/// <summary>Get valid filename. Advanced features incl. parameterized template</summary>
	public class FileNamingTemplate : NamingTemplate
	{
		/// <param name="template">Proposed file name with optional html-styled template tags.</param>
		public FileNamingTemplate(string template) : base(template) { }

		/// <summary>Generate a valid path for this file or directory</summary>
		public LongPath GetFilePath(ReplacementCharacters replacements, bool returnFirstExisting = false)
		{
			string fileName = 
				Template.EndsWith(Path.DirectorySeparatorChar) || Template.EndsWith(Path.AltDirectorySeparatorChar) ?
					FileUtility.RemoveLastCharacter(Template) : 
					Template;

			List<string> pathParts = new();

			var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value, replacements));

			while (!string.IsNullOrEmpty(fileName))
			{
				var file = Path.GetFileName(fileName);

				if (Path.IsPathRooted(Template) && file == string.Empty)
				{
					pathParts.Add(fileName);
					break;
				}
				else
				{
					pathParts.Add(file);
					fileName = Path.GetDirectoryName(fileName);
				}
			}

			pathParts.Reverse();
			var fileNamePart = pathParts[^1];
			pathParts.Remove(fileNamePart);

			LongPath directory = Path.Join(pathParts.Select(p => replaceFileName(p, paramReplacements, LongPath.MaxFilenameLength)).ToArray());

			//If file already exists, GetValidFilename will append " (n)" to the filename.
			//This could cause the filename length to exceed MaxFilenameLength, so reduce
			//allowable filename length by 5 chars, allowing for up to 99 duplicates.
			return FileUtility.GetValidFilename(Path.Join(directory, replaceFileName(fileNamePart, paramReplacements, LongPath.MaxFilenameLength - 5)), replacements, returnFirstExisting);
		}

		private static string replaceFileName(string filename, Dictionary<string,string> paramReplacements, int maxFilenameLength)
		{
			List<StringBuilder> filenameParts = new();
			//Build the filename in parts, replacing replacement parameters with
			//their values, and storing the parts in a list.
			while (!string.IsNullOrEmpty(filename))
			{
				int openIndex = filename.IndexOf('<');
				int closeIndex = filename.IndexOf('>');

				if (openIndex == 0 && closeIndex > 0)
				{
					var key = filename[..(closeIndex + 1)];

					if (paramReplacements.ContainsKey(key))
						filenameParts.Add(new StringBuilder(paramReplacements[key]));
					else
						filenameParts.Add(new StringBuilder(key));

					filename = filename[(closeIndex + 1)..];
				}
				else if (openIndex > 0 && closeIndex > openIndex)
				{
					var other = filename[..openIndex];
					filenameParts.Add(new StringBuilder(other));
					filename = filename[openIndex..];
				}
				else
				{
					filenameParts.Add(new StringBuilder(filename));
					filename = string.Empty;
				}
			}

			//Remove 1 character from the end of the longest filename part until
			//the total filename is less than max filename length
			while (filenameParts.Sum(p => p.Length) > maxFilenameLength)
			{
				int maxLength = filenameParts.Max(p => p.Length);
				var maxEntry = filenameParts.First(p => p.Length == maxLength);

				maxEntry.Remove(maxLength - 1, 1);
			}
			return string.Join("", filenameParts);
		}

		private static string formatValue(object value, ReplacementCharacters replacements)
		{
			if (value is null)
				return "";

			// Other illegal characters will be taken care of later. Must take care of slashes now so params can't introduce new folders.
			// Esp important for file templates.
			return replacements.ReplaceInvalidFilenameChars(value.ToString());
		}
	}
}
