using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

namespace Mocapless
{
    /// <summary>Brings a finished FBX into the project, set up to play.</summary>
    public static class MocaplessImport
    {
        public const string Root = "Assets/Mocapless/Animations";

        public static string Slug(string text, int max = 48)
        {
            var s = Regex.Replace((text ?? "").ToLowerInvariant(), "[^a-z0-9]+", "-").Trim('-');
            if (s.Length > max) s = s.Substring(0, max).TrimEnd('-');
            return string.IsNullOrEmpty(s) ? "animation" : s;
        }

        public static string CharacterName(string filename) =>
            Slug(Path.GetFileNameWithoutExtension(filename ?? "character"), 40);

        /// <summary>Where the clip goes: Assets/Mocapless/&lt;character&gt;/&lt;sentence&gt;.fbx, never over another file.</summary>
        public static string PathFor(string characterFile, string prompt)
        {
            var folder = $"{Root}/{CharacterName(characterFile)}";
            var name = Slug(prompt);
            var path = $"{folder}/{name}.fbx";
            for (var n = 2; File.Exists(path); n++)
                path = $"{folder}/{name}-{n}.fbx";
            return path;
        }

        public class Result
        {
            public string FbxPath;
            public string ControllerPath;
            public bool Humanoid;
            public string Note;
        }

        /// <summary>
        /// Import the downloaded FBX: a Humanoid avatar for people (falls back to Generic
        /// if Unity cannot map it), Generic for animals; the clip named after the sentence,
        /// looped if asked; and an Animator Controller that plays it.
        /// </summary>
        public static Result Setup(string fbxPath, string family, string prompt, bool loop, bool makeController)
        {
            var result = new Result { FbxPath = fbxPath };
            AssetDatabase.ImportAsset(fbxPath, ImportAssetOptions.ForceUpdate);
            var importer = AssetImporter.GetAtPath(fbxPath) as ModelImporter;
            if (importer == null)
            {
                result.Note = "Unity did not read the file as a model.";
                return result;
            }

            var wantHuman = family == "humanoid";
            importer.importAnimation = true;
            importer.animationType = wantHuman ? ModelImporterAnimationType.Human : ModelImporterAnimationType.Generic;
            if (wantHuman) importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
            Name(importer, prompt, loop);
            importer.SaveAndReimport();

            if (wantHuman)
            {
                var avatar = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<Avatar>().FirstOrDefault();
                if (avatar == null || !avatar.isHuman || !avatar.isValid)
                {
                    importer.animationType = ModelImporterAnimationType.Generic;
                    Name(importer, prompt, loop);
                    importer.SaveAndReimport();
                    result.Note = "Unity could not map this skeleton to a Humanoid avatar, so it was imported as Generic.";
                }
                else result.Humanoid = true;
            }

            if (makeController)
            {
                var clip = Clip(fbxPath);
                if (clip != null)
                {
                    var controllerPath = Path.ChangeExtension(fbxPath, null) + ".controller";
                    AnimatorController.CreateAnimatorControllerAtPathWithClip(controllerPath, clip);
                    result.ControllerPath = controllerPath;
                }
            }
            AssetDatabase.SaveAssets();
            return result;
        }

        static void Name(ModelImporter importer, string prompt, bool loop)
        {
            var clips = importer.defaultClipAnimations;
            if (clips == null || clips.Length == 0) return;
            var label = prompt.Length > 60 ? prompt.Substring(0, 60) : prompt;
            for (var i = 0; i < clips.Length; i++)
            {
                clips[i].name = clips.Length == 1 ? label : $"{label} {i + 1}";
                clips[i].loopTime = loop;
            }
            importer.clipAnimations = clips;
        }

        public static AnimationClip Clip(string fbxPath) =>
            AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<AnimationClip>()
                .FirstOrDefault(c => !c.name.StartsWith("__preview__"));

        /// <summary>The animated model in the open scene, playing, and selected.</summary>
        public static GameObject AddToScene(Result result)
        {
            var model = AssetDatabase.LoadAssetAtPath<GameObject>(result.FbxPath);
            if (model == null) return null;
            var go = (GameObject)PrefabUtility.InstantiatePrefab(model);
            Undo.RegisterCreatedObjectUndo(go, "Add Mocapless animation");
            // not "??": a missing Unity component is a fake null that "??" does not see
            var animator = go.GetComponent<Animator>();
            if (animator == null) animator = go.AddComponent<Animator>();
            if (!string.IsNullOrEmpty(result.ControllerPath))
                animator.runtimeAnimatorController = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(result.ControllerPath);
            Selection.activeGameObject = go;
            return go;
        }
    }
}
