//======= Copyright (c) Valve Corporation, All rights reserved. ===============

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using System.Linq;
using Valve.Newtonsoft.Json;
using System.IO;

namespace Valve.VR
{
    [System.Serializable]
    public class SteamVR_Input_ActionFile
    {
        public List<SteamVR_Input_ActionFile_Action> actions = new List<SteamVR_Input_ActionFile_Action>();
        public List<SteamVR_Input_ActionFile_ActionSet> action_sets = new List<SteamVR_Input_ActionFile_ActionSet>();
        public List<SteamVR_Input_ActionFile_DefaultBinding> default_bindings = new List<SteamVR_Input_ActionFile_DefaultBinding>();
        public List<Dictionary<string, string>> localization = new List<Dictionary<string, string>>();

        [JsonIgnore]
        public string filePath;

        [JsonIgnore]
        public List<SteamVR_Input_ActionFile_LocalizationItem> localizationHelperList = new List<SteamVR_Input_ActionFile_LocalizationItem>();

        public void InitializeHelperLists()
        {
            foreach (var actionset in action_sets)
            {
                actionset.actionsInList = new List<SteamVR_Input_ActionFile_Action>(actions.Where(action => action.path.StartsWith(actionset.name) && SteamVR_Input_ActionFile_ActionTypes.listIn.Contains(action.type)));
                actionset.actionsOutList = new List<SteamVR_Input_ActionFile_Action>(actions.Where(action => action.path.StartsWith(actionset.name) && SteamVR_Input_ActionFile_ActionTypes.listOut.Contains(action.type)));
                actionset.actionsList = new List<SteamVR_Input_ActionFile_Action>(actions.Where(action => action.path.StartsWith(actionset.name)));
            }

            foreach (var item in localization)
            {
                localizationHelperList.Add(new SteamVR_Input_ActionFile_LocalizationItem(item));
            }
        }

        public void SaveHelperLists()
        {
            //fix actions list
            foreach (var actionset in action_sets)
            {
                actionset.actionsList.Clear();
                actionset.actionsList.AddRange(actionset.actionsInList);
                actionset.actionsList.AddRange(actionset.actionsOutList);
            }

            actions.Clear();

            foreach (var actionset in action_sets)
            {
                actions.AddRange(actionset.actionsInList);
                actions.AddRange(actionset.actionsOutList);
            }

            localization.Clear();
            foreach (var item in localizationHelperList)
            {
                Dictionary<string, string> localizationItem = new Dictionary<string, string>();
                localizationItem.Add(SteamVR_Input_ActionFile_LocalizationItem.languageTagKeyName, item.language);

                foreach (var itemItem in item.items)
                {
                    localizationItem.Add(itemItem.Key, itemItem.Value);
                }

                localization.Add(localizationItem);
            }
        }

        public static string GetShortName(string name)
        {
            string fullName = name;
            int lastSlash = fullName.LastIndexOf('/');
            if (lastSlash != -1)
            {
                if (lastSlash == fullName.Length - 1)
                {
                    fullName = fullName.Remove(lastSlash);
                    lastSlash = fullName.LastIndexOf('/');
                    if (lastSlash == -1)
                    {
                        return GetCodeFriendlyName(fullName);
                    }
                }
                return GetCodeFriendlyName(fullName.Substring(lastSlash + 1));
            }

            return GetCodeFriendlyName(fullName);
        }

        public static string GetCodeFriendlyName(string name)
        {
            name = name.Replace('/', '_').Replace(' ', '_');

            if (char.IsLetter(name[0]) == false)
                name = "_" + name;

            for (int charIndex = 0; charIndex < name.Length; charIndex++)
            {
                if (char.IsLetterOrDigit(name[charIndex]) == false && name[charIndex] != '_')
                {
                    name = name.Remove(charIndex, 1);
                    name = name.Insert(charIndex, "_");
                }
            }

            return name;
        }

        public string[] GetFilesToCopy(bool throwErrors = false)
        {
            List<string> files = new List<string>();

            FileInfo actionFileInfo = new FileInfo(this.filePath);
            string path = actionFileInfo.Directory.FullName;

            files.Add(this.filePath);

            foreach (var binding in default_bindings)
            {
                string bindingPath = Path.Combine(path, binding.binding_url);

                if (File.Exists(bindingPath))
                    files.Add(bindingPath);
                else
                {
                    if (throwErrors)
                    {
                        Debug.LogError("<b>[SteamVR]</b> Could not bind binding file specified by the actions.json manifest: " + bindingPath);
                    }
                }
            }

            return files.ToArray();
        }

        public void CopyFilesToPath(string toPath, bool overwrite)
        {
            string[] files = SteamVR_Input.actionFile.GetFilesToCopy();

            foreach (string file in files)
            {
                FileInfo bindingInfo = new FileInfo(file);
                string newFilePath = Path.Combine(toPath, bindingInfo.Name);

                bool exists = false;
                if (File.Exists(newFilePath))
                    exists = true;

                if (exists)
                {
                    if (overwrite)
                    {
                        FileInfo existingFile = new FileInfo(newFilePath);
                        existingFile.IsReadOnly = false;
                        existingFile.Delete();

                        File.Copy(file, newFilePath);

                        RemoveAppKey(newFilePath);

                        Debug.Log("<b>[SteamVR]</b> Copied (overwrote) SteamVR Input file at path: " + newFilePath);
                    }
                    else
                    {
                        Debug.Log("<b>[SteamVR]</b> Skipped writing existing file at path: " + newFilePath);
                    }
                }
                else
                {
                    File.Copy(file, newFilePath);

                    RemoveAppKey(newFilePath);

                    Debug.Log("<b>[SteamVR]</b> Copied SteamVR Input file to folder: " + newFilePath);
                }

            }
        }


        private const string findString_appKeyStart = "\"app_key\"";
        private const string findString_appKeyEnd = "\",";
        private static void RemoveAppKey(string newFilePath)
        {
            if (File.Exists(newFilePath))
            {
                string jsonText = System.IO.File.ReadAllText(newFilePath);

                string findString = "\"app_key\"";
                int stringStart = jsonText.IndexOf(findString);

                if (stringStart == -1)
                    return; //no app key

                int stringEnd = jsonText.IndexOf("\",", stringStart);

                if (stringEnd == -1)
                    return; //no end?

                stringEnd += findString_appKeyEnd.Length;

                int stringLength = stringEnd - stringStart;

                string newJsonText = jsonText.Remove(stringStart, stringLength);

                FileInfo file = new FileInfo(newFilePath);
                file.IsReadOnly = false;

                File.WriteAllText(newFilePath, newJsonText);
            }
        }
        public static SteamVR_Input_ActionFile Open(string path)
        {
            if (File.Exists(path))
            {
                string jsonText = File.ReadAllText(path);

                SteamVR_Input_ActionFile actionFile = Valve.Newtonsoft.Json.JsonConvert.DeserializeObject<SteamVR_Input_ActionFile>(jsonText);
                actionFile.filePath = path;
                actionFile.InitializeHelperLists();

                return actionFile;
            }

            return null;
        }


        public void Save(string path)
        {
            FileInfo existingActionsFile = new FileInfo(path);
            if (existingActionsFile.Exists)
            {
                existingActionsFile.IsReadOnly = false;
            }

            //SanitizeActionFile(); //todo: shouldn't we be doing this?

            string json = JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

            File.WriteAllText(path, json);
        }
    }

    public enum SteamVR_Input_ActionFile_DefaultBinding_ControllerTypes
    {
        vive, //hmd
        vive_pro, //hmd
        vive_controller,
        generic,
        holographic_controller,
        oculus_touch,
        gamepad,
        knuckles,
        index_hmd, //hmd
        vive_cosmos_controller,
        rift, //hmd
        vive_tracker_camera,
        vive_tracker,
    }

    [System.Serializable]
    public class SteamVR_Input_ActionFile_DefaultBinding
    {
        public string controller_type;
        public string binding_url;

        public SteamVR_Input_ActionFile_DefaultBinding GetCopy()
        {
            SteamVR_Input_ActionFile_DefaultBinding newDefaultBinding = new SteamVR_Input_ActionFile_DefaultBinding();
            newDefaultBinding.controller_type = this.controller_type;
            newDefaultBinding.binding_url = this.binding_url;
            return newDefaultBinding;
        }
    }

    [System.Serializable]
    public class SteamVR_Input_ActionFile_ActionSet
    {
        [JsonIgnore]
        private const string actionSetInstancePrefix = "instance_";

        public string name;
        public string usage;

        [JsonIgnore]
        public string codeFriendlyName
        {
            get
            {
                return SteamVR_Input_ActionFile.GetCodeFriendlyName(name);
            }
        }

        [JsonIgnore]
        public string shortName
        {
            get
            {
                int lastIndex = name.LastIndexOf('/');
                if (lastIndex == name.Length - 1)
                    return string.Empty;

                return SteamVR_Input_ActionFile.GetShortName(name);
            }
        }

        public void SetNewShortName(string newShortName)
        {
            name = GetPathFromName(newShortName);
        }

        public static string CreateNewName()
        {
            return GetPathFromName("NewSet");
        }

        private const string nameTemplate = "/actions/{0}";
        public static string GetPathFromName(string name)
        {
            return string.Format(nameTemplate, name);
        }

        public static SteamVR_Input_ActionFile_ActionSet CreateNew()
        {
            return new SteamVR_Input_ActionFile_ActionSet() { name = CreateNewName() };
        }

        public SteamVR_Input_ActionFile_ActionSet GetCopy()
        {
            SteamVR_Input_ActionFile_ActionSet newSet = new SteamVR_Input_ActionFile_ActionSet();
            newSet.name = this.name;
            newSet.usage = this.usage;
            return newSet;
        }

        public override bool Equals(object obj)
        {
            if (obj is SteamVR_Input_ActionFile_ActionSet)
            {
                SteamVR_Input_ActionFile_ActionSet set = (SteamVR_Input_ActionFile_ActionSet)obj;
                if (set == this)
                    return true;

                if (set.name == this.name)
                    return true;

                return false;
            }

            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        [JsonIgnore]
        public List<SteamVR_Input_ActionFile_Action> actionsInList = new List<SteamVR_Input_ActionFile_Action>();

        [JsonIgnore]
        public List<SteamVR_Input_ActionFile_Action> actionsOutList = new List<SteamVR_Input_ActionFile_Action>();

        [JsonIgnore]
        public List<SteamVR_Input_ActionFile_Action> actionsList = new List<SteamVR_Input_ActionFile_Action>();
    }

    public enum SteamVR_Input_ActionFile_Action_Requirements
    {
        optional,
        suggested,
        mandatory,
    }

    [System.Serializable]
    public class SteamVR_Input_ActionFile_Action
    {
        [JsonIgnore]
        private static string[] _requirementValues;
        [JsonIgnore]
        public static string[] requirementValues
        {
            get
            {
                if (_requirementValues == null)
                    _requirementValues = System.Enum.GetNames(typeof(SteamVR_Input_ActionFile_Action_Requirements));

                return _requirementValues;
            }
        }

        public string name;
        public string type;
        public string scope;
        public string skeleton;
        public string requirement;

        public SteamVR_Input_ActionFile_Action GetCopy()
        {
            SteamVR_Input_ActionFile_Action newAction = new SteamVR_Input_ActionFile_Action();
            newAction.name = this.name;
            newAction.type = this.type;
            newAction.scope = this.scope;
            newAction.skeleton = this.skeleton;
            newAction.requirement = this.requirement;
            return newAction;
        }

        [JsonIgnore]
        public SteamVR_Input_ActionFile_Action_Requirements requirementEnum
        {
            get
            {
                for (int index = 0; index < requirementValues.Length; index++)
                {
                    if (string.Equals(requirementValues[index], requirement, System.StringComparison.CurrentCultureIgnoreCase))
                    {
                        return (SteamVR_Input_ActionFile_Action_Requirements)index;
                    }
                }

                return SteamVR_Input_ActionFile_Action_Requirements.suggested;
            }
            set
            {
                requirement = value.ToString();
            }
        }

        [JsonIgnore]
        public string codeFriendlyName
        {
            get
            {
                return SteamVR_Input_ActionFile.GetCodeFriendlyName(name);
            }
        }

        [JsonIgnore]
        public string shortName
        {
            get
            {
                return SteamVR_Input_ActionFile.GetShortName(name);
            }
        }

        [JsonIgnore]
        public string path
        {
            get
            {
                int lastIndex = name.LastIndexOf('/');
                if (lastIndex != -1 && lastIndex + 1 < name.Length)
                {
                    return name.Substring(0, lastIndex + 1);
                }

                return name;
            }
        }

        private const string nameTemplate = "/actions/{0}/{1}/{2}";
        public static string CreateNewName(string actionSet, string direction)
        {
            return string.Format(nameTemplate, actionSet, direction, "NewAction");
        }
        public static string CreateNewName(string actionSet, SteamVR_ActionDirections direction, string actionName)
        {
            return string.Format(nameTemplate, actionSet, direction.ToString().ToLower(), actionName);
        }

        public static SteamVR_Input_ActionFile_Action CreateNew(string actionSet, SteamVR_ActionDirections direction, string actionType)
        {
            return new SteamVR_Input_ActionFile_Action() { name = CreateNewName(actionSet, direction.ToString().ToLower()), type = actionType };
        }

        [JsonIgnore]
        public SteamVR_ActionDirections direction
        {
            get
            {
                if (type.ToLower() == SteamVR_Input_ActionFile_ActionTypes.vibration)
                    return SteamVR_ActionDirections.Out;

                return SteamVR_ActionDirections.In;
            }
        }

        protected const string prefix = "/actions/";

        [JsonIgnore]
        public string actionSet
        {
            get
            {
                int setEnd = name.IndexOf('/', prefix.Length);
                if (setEnd == -1)
                    return string.Empty;

                return name.Substring(0, setEnd);
            }
        }

        public void SetNewActionSet(string newSetName)
        {
            name = string.Format(nameTemplate, newSetName, direction.ToString().ToLower(), shortName);
        }

        public override string ToString()
        {
            return shortName;
        }

        public override bool Equals(object obj)
        {
            if (obj is SteamVR_Input_ActionFile_Action)
            {
                SteamVR_Input_ActionFile_Action action = (SteamVR_Input_ActionFile_Action)obj;
                if (this == obj)
                    return true;

                if (this.name == action.name && this.type == action.type && this.skeleton == action.skeleton && this.requirement == action.requirement)
                    return true;

                return false;
            }

            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    public class SteamVR_Input_ActionFile_LocalizationItem
    {
        public const string languageTagKeyName = "language_tag";

        public string language;
        public Dictionary<string, string> items = new Dictionary<string, string>();

        public SteamVR_Input_ActionFile_LocalizationItem(string newLanguage)
        {
            language = newLanguage;
        }

        public SteamVR_Input_ActionFile_LocalizationItem(Dictionary<string, string> dictionary)
        {
            if (dictionary == null)
                return;

            if (dictionary.ContainsKey(languageTagKeyName))
                language = (string)dictionary[languageTagKeyName];
            else
                Debug.Log("<b>[SteamVR]</b> Input: Error in actions file, no language_tag in localization array item.");

            foreach (KeyValuePair<string, string> item in dictionary)
            {
                if (item.Key != languageTagKeyName)
                    items.Add(item.Key, (string)item.Value);
            }
        }
    }

    public class SteamVR_Input_ManifestFile
    {
        public string source;
        public List<SteamVR_Input_ManifestFile_Application> applications;
    }

    public class SteamVR_Input_ManifestFile_Application
    {
        public string app_key;
        public string launch_type;
        public string url;
        public string binary_path_windows;
        public string binary_path_linux;
        public string binary_path_osx;
        public string action_manifest_path;
        //public List<SteamVR_Input_ManifestFile_Application_Binding> bindings = new List<SteamVR_Input_ManifestFile_Application_Binding>();
        public string image_path;
        public Dictionary<string, SteamVR_Input_ManifestFile_ApplicationString> strings = new Dictionary<string, SteamVR_Input_ManifestFile_ApplicationString>();
    }

    public class SteamVR_Input_Unity_AssemblyFile_Definition
    {
        public string name = "SteamVR_Actions";
        public string[] references = new string[] { "SteamVR" };
        public string[] optionalUnityReferences = new string[0];
        public string[] includePlatforms = new string[0];
        public string[] excludePlatforms = new string[] { "Android" };
        public bool allowUnsafeCode = false;
        public bool overrideReferences = false;
        public string[] precompiledReferences = new string[0];
        public bool autoReferenced = false;
        public string[] defineConstraints = new string[0];
    }

    public class SteamVR_Input_ManifestFile_ApplicationString
    {
        public string name;
    }

    public class SteamVR_Input_ManifestFile_Application_Binding
    {
        public string controller_type;
        public string binding_url;
    }

    public class SteamVR_Input_ManifestFile_Application_Binding_ControllerTypes
    {
        public static string oculus_touch = "oculus_touch";
        public static string vive_controller = "vive_controller";
        public static string knuckles = "knuckles";
        public static string holographic_controller = "holographic_controller";
        public static string vive = "vive";
        public static string vive_pro = "vive_pro";
        public static string holographic_hmd = "holographic_hmd";
        public static string rift = "rift";
        public static string vive_tracker_camera = "vive_tracker_camera";
        public static string vive_cosmos = "vive_cosmos";
        public static string vive_cosmos_controller = "vive_cosmos_controller";
        public static string index_hmd = "index_hmd";
    }

    static public class SteamVR_Input_ActionFile_ActionTypes
    {
        public static string boolean = "boolean";
        public static string vector1 = "vector1";
        public static string vector2 = "vector2";
        public static string vector3 = "vector3";
        public static string vibration = "vibration";
        public static string pose = "pose";
        public static string skeleton = "skeleton";

        public static string skeletonLeftPath = "\\skeleton\\hand\\left";
        public static string skeletonRightPath = "\\skeleton\\hand\\right";

        public static string[] listAll = new string[] { boolean, vector1, vector2, vector3, vibration, pose, skeleton };
        public static string[] listIn = new string[] { boolean, vector1, vector2, vector3, pose, skeleton };
        public static string[] listOut = new string[] { vibration };
        public static string[] listSkeletons = new string[] { skeletonLeftPath, skeletonRightPath };
    }

    static public class SteamVR_Input_ActionFile_ActionSet_Usages
    {
        public static string leftright = "leftright";
        public static string single = "single";
        public static string hidden = "hidden";

        public static string leftrightDescription = "per hand";
        public static string singleDescription = "mirrored";
        public static string hiddenDescription = "hidden";

        public static string[] listValues = new string[] { leftright, single, hidden };
        public static string[] listDescriptions = new string[] { leftrightDescription, singleDescription, hiddenDescription };
    }
}