#if USE_ARCWEAVE
using System;
using System.Collections.Generic;
using UnityEngine;
using Language.Lua;
namespace PixelCrushers.DialogueSystem.ArcweaveSupport
{
    /// 
    /// Implements Arcscript built-in functions for the Dialogue System's Lua environment.
    /// Notes:
    /// - Uses LuaInterpreter.
    /// - Does not implement reset() or resetAll().
    /// 
    [AddComponentMenu("")] // Use wrapper.
    public class ArcweaveLua : Saver
    {
        [Tooltip("Typically leave unticked so temporary Dialogue Managers don't unregister your functions.")]
        public bool unregisterOnDisable = false;
        [Tooltip("Support multiplayer Lua functions. Currently: visits() incorporates Variable['ActorIndex'].")]
        public bool multiplayer = false;
        #region Unity Entrypoints
        public override void OnEnable()
        {
            base.OnEnable();
            Lua.RegisterFunction(nameof(abs), null, SymbolExtensions.GetMethodInfo(() => abs(0)));
            Lua.RegisterFunction(nameof(sqr), null, SymbolExtensions.GetMethodInfo(() => sqr(0)));
            Lua.RegisterFunction(nameof(sqrt), null, SymbolExtensions.GetMethodInfo(() => sqrt(0)));
            Lua.RegisterFunction(nameof(random), null, SymbolExtensions.GetMethodInfo(() => random()));
            Lua.RegisterFunction(nameof(visits), this, SymbolExtensions.GetMethodInfo(() => visits(string.Empty)));
            Lua.environment.Register(nameof(roll), roll);
            Lua.environment.Register(nameof(show), show);
        }
        public override void OnDisable()
        {
            base.OnDisable();
            if (unregisterOnDisable)
            {
                Lua.UnregisterFunction(nameof(abs));
                Lua.UnregisterFunction(nameof(sqr));
                Lua.UnregisterFunction(nameof(sqrt));
                Lua.UnregisterFunction(nameof(random));
                Lua.UnregisterFunction(nameof(roll));
                Lua.UnregisterFunction(nameof(show));
            }
        }
        #endregion
        #region Arcweave Functions
        public static double abs(double n)
        {
            return Mathf.Abs((float)n);
        }
        public static double sqr(double n)
        {
            return (n * n);
        }
        public static double sqrt(double n)
        {
            return Mathf.Sqrt((float)n);
        }
        public static double random()
        {
            return UnityEngine.Random.value; // Note: Returns 1 inclusive, but Arcscript random() is 1 exclusive.
        }
        public static LuaValue roll(LuaValue[] values)
        {
            int m = (int)(values[0] as LuaNumber).Number;
            int n = (values.Length > 1 && values[1] is LuaNumber) ? (int)(values[1] as LuaNumber).Number : 1;
            double result = 0;
            for (int i = 0; i < (int)n; i++)
            {
                result += UnityEngine.Random.Range(1, (int)m + 1);
            }
            return new LuaNumber(result);
        }
        public static LuaValue show(LuaValue[] values)
        {
            var s = string.Empty;
            foreach (var value in values)
            {
                s += value.ToString();
            }
            return new LuaString(s);
        }
        #endregion
        #region visits()
        [Serializable]
        public class SaveData
        {
            public List guids = new List();
            public List visits = new List();
            public string lastTextGuid;
            public SaveData() { }
            public SaveData(Dictionary visitsDict, string lastTextGuid)
            {
                foreach (var kvp in visitsDict)
                {
                    guids.Add(kvp.Key);
                    visits.Add(kvp.Value);
                }
                this.lastTextGuid = lastTextGuid;
            }
        }
        [Serializable]
        public class MultiplayerSaveData
        {
            public List actorIndices = new List();
            public List saveData = new List();
        }
        protected Dictionary> visitsDicts = new Dictionary>();
        protected Dictionary lastTextGuids = new Dictionary();
        protected virtual string GetActorIndex()
        {
            return multiplayer ? DialogueLua.GetVariable("ActorIndex").asString : "Player"; 
        }
        protected virtual string GetLastTextGuid(string actorIndex)
        {
            if (!lastTextGuids.ContainsKey(actorIndex))
            {
                lastTextGuids.Add(actorIndex, string.Empty);
            }
            return lastTextGuids[actorIndex];
        }
        protected virtual Dictionary GetVisitsDict(string actorIndex)
        {
            if (!visitsDicts.ContainsKey(actorIndex))
            {
                visitsDicts.Add(actorIndex, new Dictionary());
            }
            return visitsDicts[actorIndex];
        }
        protected virtual void OnConversationLine(Subtitle subtitle)
        {
            if (string.IsNullOrEmpty(subtitle.formattedText.text)) return;
            var lastTextGuid = Field.LookupValue(subtitle.dialogueEntry.fields, "Guid");
            if (string.IsNullOrEmpty(lastTextGuid)) return;
            var actorIndex = GetActorIndex();
            lastTextGuids[actorIndex] = lastTextGuid;
            var visitsDict = GetVisitsDict(actorIndex);
            if (visitsDict.ContainsKey(lastTextGuid))
            {
                visitsDict[lastTextGuid]++;
            }
            else
            {
                visitsDict.Add(lastTextGuid, 1);
            }
        }
        public double visits(string id)
        {
            int count;
            var actorIndex = GetActorIndex();
            var guid = !string.IsNullOrEmpty(id) ? id : GetLastTextGuid(actorIndex);
            var visitsDict = GetVisitsDict(actorIndex);
            return visitsDict.TryGetValue(guid, out count) ? count : 0;
        }
        public override string RecordData()
        {
            if (!multiplayer)
            {
                var actorIndex = GetActorIndex();
                var data = new SaveData(GetVisitsDict(actorIndex), GetLastTextGuid(actorIndex));
                return SaveSystem.Serialize(data);
            }
            else
            {
                var multiplayerData = new MultiplayerSaveData();
                foreach (var kvp in visitsDicts)
                {
                    var actorIndex = kvp.Key;
                    var visitsDict = kvp.Value;
                    var data = new SaveData(visitsDict, GetLastTextGuid(actorIndex));
                    multiplayerData.actorIndices.Add(actorIndex);
                    multiplayerData.saveData.Add(data);
                }
                return SaveSystem.Serialize(multiplayerData);
            }
        }
        public override void ApplyData(string s)
        {
            if (string.IsNullOrEmpty(s)) return;
            if (!multiplayer)
            {
                var data = SaveSystem.Deserialize(s);
                if (data == null) return;
                var actorIndex = GetActorIndex();
                var visitsDict = GetVisitsDict(actorIndex);
                visitsDict.Clear();
                for (int i = 0; i < Mathf.Min(data.guids.Count, data.visits.Count); i++)
                {
                    visitsDict.Add(data.guids[i], data.visits[i]);
                }
                lastTextGuids[actorIndex] = data.lastTextGuid;
            }
            else
            {
                var multiplayerData = SaveSystem.Deserialize(s);
                if (multiplayerData == null) return;
                for (int i = 0; i < Mathf.Min(multiplayerData.actorIndices.Count, multiplayerData.saveData.Count); i++)
                {
                    var actorIndex = multiplayerData.actorIndices[i];
                    var data = multiplayerData.saveData[i];
                    var visitsDict = GetVisitsDict(actorIndex);
                    visitsDict.Clear();
                    for (int j = 0; j < Mathf.Min(data.guids.Count, data.visits.Count); j++)
                    {
                        visitsDict.Add(data.guids[j], data.visits[j]);
                    }
                    lastTextGuids[actorIndex] = data.lastTextGuid;
                }
            }
        }
        #endregion
    }
}
#endif