using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using ServiceStack.DataAnnotations;
using ServiceStack.VirtualPath;

namespace ServiceStack.IO
{
    public class ResourceVirtualFiles 
        : AbstractVirtualPathProviderBase
    {
        protected ResourceVirtualDirectory RootDir;
        public Assembly BackingAssembly { get; }
        public string RootNamespace { get; }

        public override IVirtualDirectory RootDirectory => RootDir;
        public override string VirtualPathSeparator => "/";
        public override string RealPathSeparator => ".";
        
        public DateTime LastModified { get; set; } 

        public ResourceVirtualFiles(Type baseTypeInAssembly)
            : this(baseTypeInAssembly.Assembly, GetNamespace(baseTypeInAssembly)) { }

        public ResourceVirtualFiles(Assembly backingAssembly, string rootNamespace=null)
        {
            this.BackingAssembly = backingAssembly ?? throw new ArgumentNullException(nameof(backingAssembly));
            this.RootNamespace = rootNamespace ?? backingAssembly.GetName().Name;
            this.LastModified = GetAssemblyLastModified(BackingAssembly);

            Initialize();
        }
        
        private static DateTime GetAssemblyLastModified(Assembly asm)
        {
            try
            {
                return new FileInfo(asm.Location).LastWriteTime;
            }
            catch (Exception) { /* ignored */ }
            return DateTime.UtcNow;
        }
        
        //https://docs.microsoft.com/en-us/dotnet/api/system.resources.tools.stronglytypedresourcebuilder.verifyresourcename?redirectedfrom=MSDN&view=netframework-4.8#remarks
        static readonly char [] NamespaceSpecialChars = { ' ', '\u00A0', ',', ';', '|', '~', '@', '#', '%', '^', '&', 
            '*', '+', '-', /*'/', '\\',*/ '<', '>', '?', '[', ']', '(', ')', '{', 
            '}', '\"', '\'', '!'};

        private static string CleanChars(string name)
        {
            var newChars = new char[name.Length];
            var nameChars = name.AsSpan();
            for (var i = 0; i < nameChars.Length ;i++) 
            {
                newChars[i] = nameChars[i];
                foreach (var c in NamespaceSpecialChars)
                {
                    if (nameChars[i] == c)
                    {
                        newChars[i] = '_';
                        break;
                    }
                }
            }
            return new string (newChars);
        }
        
        public static HashSet<string> PartialFileNames { get; set; } = new HashSet<string>
        {
            "min.js",
            "min.css",
        };

        public string CleanPath(string filePath)
        {
            var sanitizedPath = base.SanitizePath(filePath);
            if (sanitizedPath == null)
                return null;
            var lastDirPos = sanitizedPath.LastIndexOf('/');
            if (lastDirPos >= 0)
            {
                var dirPath = sanitizedPath.Substring(0, lastDirPos);
                var fileName = sanitizedPath.Substring(lastDirPos + 1);
                if (PartialFileNames.Contains(fileName))
                {
                    var partialName = dirPath.LastRightPart('/');
                    dirPath = dirPath.LastLeftPart('/');
                    fileName = partialName + '.' + fileName;
                }
                
                var cleanDir = CleanChars(dirPath); //only dirs are replaced 
                var cleanPath = cleanDir + '/' + fileName;
                return cleanPath;
            }
            return sanitizedPath;
        }

        public override IVirtualFile GetFile(string virtualPath)
        {
            var virtualFile = RootDirectory.GetFile(CleanPath(virtualPath));
            virtualFile?.Refresh();
            return virtualFile;
        }

        private static string GetNamespace(Type type)
        {
            var attr = type.FirstAttribute<SchemaAttribute>();
            return attr != null ? attr.Name : type.Namespace;
        }

        protected sealed override void Initialize()
        {
            var asm = BackingAssembly;
            RootDir = new ResourceVirtualDirectory(this, null, asm, LastModified, RootNamespace);
        }

        public override string CombineVirtualPath(string basePath, string relativePath)
        {
            return string.Concat(basePath, VirtualPathSeparator, relativePath);
        }
    }
}