using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityObject = UnityEngine.Object;

namespace Unity.VisualScripting
{
    public sealed class AssemblyResourceProvider : IResourceProvider
    {
        public const char DirectorySeparatorChar = '.';

        public Assembly assembly { get; }

        public string root { get; }

        private readonly string prefix;

        public AssemblyResourceProvider(Assembly assembly, string @namespace, string root)
        {
            Ensure.That(nameof(assembly)).IsNotNull(assembly);

            this.assembly = assembly;
            this.root = root;

            prefix = string.Empty;

            var hasNamespace = !string.IsNullOrEmpty(@namespace);
            var hasRoot = !string.IsNullOrEmpty(root);

            if (hasNamespace)
            {
                prefix += @namespace;
            }

            if (hasNamespace && hasRoot)
            {
                prefix += ".";
            }

            if (hasRoot)
            {
                prefix += root;
            }

            Analyze();
        }

        #region Filesystem

        public IEnumerable<string> GetAllFiles()
        {
            return assembly.GetManifestResourceNames();
        }

        public IEnumerable<string> GetFiles(string path)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            var normalizedDirectoryPath = NormalizeDirectoryPath(path);

            var directory = GetDirectory(normalizedDirectoryPath, true);

            foreach (var file in directory.files)
            {
                yield return $"{directory.path}{DirectorySeparatorChar}{file}";
            }
        }

        public IEnumerable<string> GetDirectories(string path)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            var normalizedDirectoryPath = NormalizeDirectoryPath(path);

            var directory = GetDirectory(normalizedDirectoryPath, true);

            foreach (var subDirectory in directory.subDirectories)
            {
                yield return subDirectory.Value.path;
            }
        }

        public string GetPersonalPath(string path, float width)
        {
            var name = Path.GetFileNameWithoutExtension(path).PartBefore('@');
            var extension = Path.GetExtension(path);
            var directory = Path.GetDirectoryName(path);

            return Path.Combine(directory, $"{name}@{width}x{extension}");
        }

        public string GetProfessionalPath(string path, float width)
        {
            var name = Path.GetFileNameWithoutExtension(path).PartBefore('@');
            var extension = Path.GetExtension(path);
            var directory = Path.GetDirectoryName(path);

            return Path.Combine(directory, $"{name}_Pro@{width}x{extension}");
        }

        public bool FileExists(string path)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            path = PreNormalizePath(path);

            var normalizedDirectoryPath = NormalizeDirectoryPath(Path.GetDirectoryName(path));
            var normalizedFileName = NormalizeFileName(Path.GetFileName(path));

            var directory = GetDirectory(normalizedDirectoryPath, false);

            return directory != null && directory.files.Contains(normalizedFileName);
        }

        public bool DirectoryExists(string path)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            path = PreNormalizePath(path);

            var normalizedDirectoryPath = NormalizeDirectoryPath(path);

            return GetDirectory(normalizedDirectoryPath, false) != null;
        }

        private string PreNormalizePath(string path)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            var undottedPath = "";

            var directory = Path.GetDirectoryName(path);

            if (!string.IsNullOrEmpty(directory))
            {
                undottedPath += directory.Replace('.', Path.DirectorySeparatorChar);
                undottedPath += Path.DirectorySeparatorChar;
            }

            undottedPath += Path.GetFileNameWithoutExtension(path).Replace('.', Path.DirectorySeparatorChar);
            undottedPath += Path.GetExtension(path);

            return undottedPath;
        }

        private string NormalizeDirectoryPath(string directoryPath)
        {
            Ensure.That(nameof(directoryPath)).IsNotNull(directoryPath);

            if (string.IsNullOrEmpty(directoryPath))
            {
                return prefix;
            }

            return prefix
                + "."
                + directoryPath.Replace(Path.DirectorySeparatorChar, DirectorySeparatorChar)
                    .Replace(Path.AltDirectorySeparatorChar, DirectorySeparatorChar)
                    .Replace(' ', '_');
        }

        private string NormalizeFileName(string fileName)
        {
            Ensure.That(nameof(fileName)).IsNotNull(fileName);

            return fileName;
        }

        public string DebugPath(string path)
        {
            return path;
        }

        #endregion


        #region Loading

        public T LoadAsset<T>(string path) where T : UnityObject
        {
            throw new NotSupportedException("Assets cannot be loaded from assembly resources.");
        }

        public Texture2D LoadTexture(string path, CreateTextureOptions options)
        {
            Ensure.That(nameof(path)).IsNotNull(path);

            Texture2D texture2D = Resources.Load<Texture2D>(path);

            return texture2D;
        }

        #endregion


        #region Internals

        private void Analyze()
        {
            foreach (var path in assembly.GetManifestResourceNames())
            {
                var directory = rootDirectory;

                var parts = path.Split(DirectorySeparatorChar);

                for (var i = 0; i < parts.Length; i++)
                {
                    var isFile = i == parts.Length - 2;
                    var isExtension = i == parts.Length - 1;
                    var isDirectory = !isFile && !isExtension;
                    var part = parts[i];

                    if (isDirectory)
                    {
                        Directory subDirectory;

                        if (!directory.subDirectories.TryGetValue(part, out subDirectory))
                        {
                            subDirectory = new Directory(directory, part);
                            directory.subDirectories.Add(part, subDirectory);
                        }

                        directory = subDirectory;
                    }
                    else if (isFile)
                    {
                        var extension = parts[i + 1];
                        directory.files.Add(part + '.' + extension);
                    }
                }
            }
        }

        private Directory GetDirectory(string normalizedDirectoryPath, bool throwOnFail)
        {
            Ensure.That(nameof(normalizedDirectoryPath)).IsNotNull(normalizedDirectoryPath);

            var parts = normalizedDirectoryPath.Split(DirectorySeparatorChar);

            var directory = rootDirectory;

            foreach (var part in parts)
            {
                Directory subDirectory;

                if (!directory.subDirectories.TryGetValue(part, out subDirectory))
                {
                    if (throwOnFail)
                    {
                        throw new FileNotFoundException("Assembly resource directory not found.", DebugPath(normalizedDirectoryPath));
                    }

                    return null;
                }

                directory = subDirectory;
            }

            return directory;
        }

        private readonly Directory rootDirectory = new Directory(null, null);

        private class Directory
        {
            public Directory parent { get; }

            public string name { get; }

            public string path { get; }

            public readonly Dictionary<string, Directory> subDirectories = new Dictionary<string, Directory>();

            public readonly List<string> files = new List<string>();

            public Directory(Directory parent, string name)
            {
                this.parent = parent;
                this.name = name;

                if (string.IsNullOrEmpty(parent?.path))
                {
                    path = name;
                }
                else
                {
                    path = $"{parent.path}{DirectorySeparatorChar}{name}";
                }
            }
        }

        #endregion
    }
}