//============= Copyright (c) Ludic GmbH, All rights reserved. ==============
//
// Purpose: Part of the My Behaviour Tree Code
//
//=============================================================================

using UnityEngine;
using System;
using System.Linq;
//using System.Runtime.Serialization.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Xml.Serialization;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace MyBT {
    [System.Serializable] public class DictionaryOfStringAndMethodImplementation : SerializableDictionary<string, MethodImplementation> {}
    [System.Serializable] public class DictionaryOfStringAndDictionaryOfStringAndMethodImplementation : SerializableDictionary<string, DictionaryOfStringAndMethodImplementation> {}

    public enum TickUpdateMode {
        // run in update / lateUpdate
        Update,
        LateUpdate,
        // run in an interval for 1 step
        DelayedUpdate,
        DelayedLateUpdate,
        // run for a maximum time
        MaximumTimeUpdate,
        MaximumTimeLateUpdate,
        // run a full cycle
        FullrunUpdate,
        FullrunLateUpdate,
        // run manually
        Manual
    }

    [ExecuteInEditMode, System.Serializable]
    public partial class TaskController : MonoBehaviour {
        #region unity inspector updater (only in editor)
        // handling changes of the bt -> requires redrawing of the editor ui
        [NonSerialized]
        public Action<bool> InspectorRedraw;
        [NonSerialized]
        public Action<bool> InspectorRegenerate;

        // called from lateUpdate
        [System.Diagnostics.Conditional("UNITY_EDITOR")]
        void CallInspectorRedraw() {
            // makes unity editor very slow!!!
            //UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
            lastTickTime = currentUiTime;
            if (InspectorRedraw != null) InspectorRedraw.Invoke(false);
        }

        // called from lateUpdate
        [System.Diagnostics.Conditional("UNITY_EDITOR")]
        void CallInspectorRegenerate() {
            if (InspectorRegenerate != null) InspectorRegenerate.Invoke(false);
        }

        // when was the tick executed last time
        public float lastTickTime = 0;
        // when was the behaviour tree changed last time
        private float lastUiChangedTime = 0;

        // has the behaviour tree changed in this frame?
        // it is called from TaskControllerInspector
        public bool UiChangedThisFrame () {
            return (lastTickTime == lastUiChangedTime);
        }

        public bool UiChangedAt (float time) {
            return lastTickTime == time;
        }

        // called to set behaviour tree changed
        public void SetUiUpdate() {
            // time at beginning of frame
            lastUiChangedTime = TaskController.currentUiTime;
        }

        public static float currentUiTime {
            get { return Time.time; }
        }
        #endregion

        #region settings
        // tick settings
        public float tickDelay = 0f;
        public float maximumRuntime = 0.1f;
        public TickUpdateMode tickMode = TickUpdateMode.Update;
        // generation settings
        public bool runtimeAutomaticRegenerate = true;
        public bool resetDataOnCodeChange = false;
        // logging
        public bool runtimeLogging = false;
        public bool overrideLogStringDisplay = false;
        public bool overrideDebugInternalActive = false;
        public bool overrideDebugChangesActive = false;
        // timer for tick delay calculation
        private float tickDelayTimer = 0f;

        NodeResult rootResult = NodeResult.Succeeded;

#if false
        public IEnumerator<NodeResult> _tickEnumerator;
        public IEnumerator<NodeResult> tickEnumerator {
            get {
                return _tickEnumerator;
            }
            set {
                if (runtimeLogging)
                    Debug.LogWarning($"setting tickEnumerator to '{value}'");
                _tickEnumerator = value;
            }
        }
#else
        public IEnumerator<NodeResult> tickEnumerator;
#endif

        private bool tickSuccess = false;
        #endregion

        #region duplicate watcher
        // #if UNITY_EDITOR
        // Catch duplication of this GameObject
        [SerializeField]
        private int instanceID = 0;
        /// <summary>
        /// Detect when the GameObject has been duplicated
        /// if so make sure then dont share the same instance of the behaviour tree generator
        /// </summary>
        void AwakeDuplicateCatcher() {
            if (instanceID != GetInstanceID()) {
                if (generatorLogging) Debug.Log($"Detected Duplicate! ( {instanceID} != {GetInstanceID()} )");
                // Do whatever you need to set up the duplicate

                // copy old contents
                // List<UnityEngine.TextAsset> oldTaskScripts = new List<UnityEngine.TextAsset>();
                // for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) {
                //     taskScripts.Add(oldTaskScripts[i]);
                // }

                // clear current data
                __behaviourTreeGenerator = null;
                // assign scripts
                // read scripts
                // for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) {
                //     taskScripts.Add(oldTaskScripts[i]);
                // }

                // works, but regenerates everything, breaks when unity builds the project
                // BtScriptChangeDetected(true);
                // FullReset();

                instanceID = GetInstanceID();

                #if UNITY_EDITOR
                EditorUtility.SetDirty(this);
                #endif
            }
        }
        // #endif

        private void Awake() {
            AwakeDuplicateCatcher();
            if (Application.isPlaying) {
                VerifyBindings();
                RegenerateBehaviourTreeIfRequired(false);
            }
        }

        public void OnDestroy () {
            #if !UNITY_EDITOR
            // calling this when building causes it to fail
            ClearBinding();
            #endif
        }
        #endregion

        // public bool destroyNextFrame = false;
        // public bool breakNextFrame = false;

        #region tick functions
        public void Update() {
            // if (breakNextFrame) {
            //     Debug.Break();
            // }
            // if (destroyNextFrame) {
            //     OnDestroy();
            //     breakNextFrame = true;
            // }

            // Debug.Log($"Update {Time.frameCount} {Time.time}");
            if (Application.isPlaying) {
                if (tickMode == TickUpdateMode.Update) {
					TickUpdateStep();
                }
                else if (tickMode == TickUpdateMode.DelayedUpdate) {
                    tickDelayTimer -= Time.deltaTime;
                    if (tickDelayTimer < 0) {
                        tickDelayTimer += tickDelay;
						TickUpdateStep();
                    }
                }
                else if (tickMode == TickUpdateMode.MaximumTimeUpdate) {
                    float startTime = Time.realtimeSinceStartup;
                    while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) {
                        TickUpdateStep();
                    }
                }
                else if (tickMode == TickUpdateMode.FullrunUpdate) {
                    TickUpdateFull();
                }
            }
        }

        public void LateUpdate () {
            if (Application.isPlaying) {
                if (tickMode == TickUpdateMode.LateUpdate) {
					TickUpdateStep();
                }
                else if (tickMode == TickUpdateMode.DelayedLateUpdate) {
                    tickDelayTimer -= Time.deltaTime;
                    if (tickDelayTimer < 0) {
                        tickDelayTimer += tickDelay;
						TickUpdateStep();
                    }
                }
                else if (tickMode == TickUpdateMode.MaximumTimeLateUpdate) {
                    float startTime = Time.realtimeSinceStartup;
                    while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) {
                        TickUpdateStep();
                    }
                }
                else if (tickMode == TickUpdateMode.FullrunLateUpdate) {
                    TickUpdateFull();
                }
            }
        }

        public void TickUpdate () {
			TickUpdateStep();
		}

        public void TickUpdateStep () {
            if (restartBehaviourTreeNextFrame) {
                RestartNow();
            }

			if (hasRootNode) {
                // if null or looped trough all items
                if ((!tickSuccess) || (tickEnumerator == null)) {
                    // restart tick
                    tickEnumerator = rootNode.Tick(null, 0).GetEnumerator();
                    if (runtimeLogging)
                        Debug.Log($"Assigned new Iterator {(tickEnumerator != null)}");
                }

                // run as usual
                tickSuccess = tickEnumerator.MoveNext();
                rootResult = tickEnumerator.Current;
                if (runtimeLogging)
                    Debug.Log($"--- Ticked --- s: {tickSuccess} r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------");
            }
            else {
                Debug.LogError("cannot tick, missing root node! disabling this gameObject !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
                gameObject.SetActive(false);
            }

            CallInspectorRedraw();
        }

        public void TickUpdateFull() {
            if (restartBehaviourTreeNextFrame) {
                RestartNow();
            }
            
            tickDelayTimer = tickDelay;
            //behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this);

            if (hasRootNode) {
                // reset the bt
                foreach (var result in rootNode.Tick(null, 0)) {
                    rootResult = result;
                }
                rootResult = NodeResult.Continue;
            }
            if (runtimeLogging)
                Debug.Log($"--- Restart --- r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------");

            CallInspectorRedraw();
        }
        #endregion
    }





    public partial class TaskController {
        #region behaviourtree generator & exposing of methods and variables
        [SerializeField]
        private BehaviourTreeGenerator __behaviourTreeGenerator;
        [HideInInspector, SerializeField]
        private BehaviourTreeGenerator behaviourTreeGenerator {
            get {
                if (__behaviourTreeGenerator == null) { __behaviourTreeGenerator = BehaviourTreeGenerator.CreateInstance<BehaviourTreeGenerator>(); }
                if (__behaviourTreeGenerator == null) { Debug.LogError($"behaviourTreeGenerator is null... how? {behaviourTreeGenerator}"); }
                return __behaviourTreeGenerator;
            }
        }
        // expose parts of the behaviourTreeGenerator
        #endregion

        #region functions and variables from behaviourTreeGenerator
        // configuration
        [SerializeField]
        public List<TextAsset> taskScripts;

        public bool generatorLogging {
            get { return behaviourTreeGenerator.generatorLogging; }
            set { behaviourTreeGenerator.generatorLogging = value; }
        }
        public bool internalAutomaticRegenerate {
            get { return behaviourTreeGenerator.internalAutomaticRegenerate; }
            set { behaviourTreeGenerator.internalAutomaticRegenerate = value; }
        }

        public TreeNode rootNode {
            get { return behaviourTreeGenerator.rootNode; }
        }
        public bool hasRootNode {
            get { return (behaviourTreeGenerator.rootNode != null); }
        }

        public void FullGenerate (bool force) {
            behaviourTreeGenerator.FullGenerate(this, force);
        }

        public void FullReset () {
            behaviourTreeGenerator.FullReset();
        }

        private bool restartBehaviourTreeNextFrame = false;
        public void Restart(bool forceImmediate=false) {
            restartBehaviourTreeNextFrame = true;
            if (forceImmediate) {
                RestartNow();
            }
        }
        private void RestartNow () {
            behaviourTreeGenerator.Restart();
            restartBehaviourTreeNextFrame = false;
        }

        public void VerifyBindings() {
            behaviourTreeGenerator.VerifyBinding(this);
        }

        public void RegenerateBehaviourTreeIfRequired (bool forceUpdate) {
            behaviourTreeGenerator.RegenerateBehaviourTreeIfRequired(this, forceUpdate);
        }

        /// <summary>
        /// the methods called have changed (or might have...)
        /// we dont yet filter which scripts are changed
        /// </summary>
        public void ClearBindingsAndScanAndBindMethods(bool forceUpdate = false) {
            // forcefully clear and regenerate
            behaviourTreeGenerator.ClearBindingsAndScanAndBindMethods(this, forceUpdate);
        }

        /// <summary>
        /// is called by DetectBtChanges
        /// </summary>
        /// <param name="forceUpdate"></param>
        public void BtScriptChangeDetected(bool forceUpdate = false) {
            behaviourTreeGenerator.BtScriptChangeDetected(this, forceUpdate);
        }
        public void ResetNodesAndTokens() {
            behaviourTreeGenerator.ResetNodesAndTokens();
        }
        public void GenerateTokensAndNodes(bool forceUpdate = false) {
            Debug.Log("GenerateTokensAndNodes");
            behaviourTreeGenerator.GenerateTokensAndNodes(this, forceUpdate);
        }
        public void ClearBinding() {
            behaviourTreeGenerator.ClearBinding();
        }
        public void ScanAndBindMethods(bool forceUpdate = false) {
            behaviourTreeGenerator.ScanAndBindMethods(this, forceUpdate);
        }
        public void SetTaskController() {
            behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this);
        }
        public void SetBehaviourTreeDirty() {
        #if UNITY_EDITOR
            EditorUtility.SetDirty(behaviourTreeGenerator);
        #endif
        }
        public bool CheckBindingOutdated(bool allowNull = true) {
            return behaviourTreeGenerator.CheckBindingOutdated(this, allowNull);
        }
        public bool CheckGenerationOutdated(bool allowNull = true) {
            return behaviourTreeGenerator.CheckGenerationOutdated(allowNull);
        }

        // status
        // hasScript && tokensGenerated && nodesGenerated
        // public bool isGenerated {
        //     get { return behaviourTreeGenerator.isGenerated; }
        // }
        // public string generationState {
        //     get { return behaviourTreeGenerator.generationState; }
        // }
        public bool hasScript {
            get { return behaviourTreeGenerator.HasScripts(this); }
        }
        // public bool tokensGenerated {
        //     get { return behaviourTreeGenerator.tokensGenerated; }
        // }
        // public bool nodesGenerated {
        //     get { return behaviourTreeGenerator.nodesGenerated; }
        // }


        public FileHash textAssetHashes {
            get { return behaviourTreeGenerator.textAssetHashes; }
        }
        public FileHash generatedCodeBtFileHashes {
            get { return behaviourTreeGenerator.generatedCodeBtFileHashes; }
        }
        public FileHash boundCodeBtFileHashes {
            get { return behaviourTreeGenerator.boundCodeBtFileHashes; }
        }

        public List<TokenList> tokens {
            get { return behaviourTreeGenerator.tokens; }
        }

        public List<NodeList> treeRootNodes {
            get { return behaviourTreeGenerator.treeRootNodes; }
        }
    #if UNITY_EDITOR
        public SerializedProperty treeRootNodesAsSerializedProperty {
            get {
                SerializedObject sObj = new SerializedObject(behaviourTreeGenerator);
                return sObj.FindProperty("treeRootNodes");
            }
        }
    #endif
        public int nodeCount {
            get { return behaviourTreeGenerator.nodeCount; }
        }

        public bool generationError {
            get { return behaviourTreeGenerator.generationError; }
        }

        public bool bindingError {
            get { return behaviourTreeGenerator.bindingError; }
        }

        // public bool methodsScanned {
        //     get { return behaviourTreeGenerator.methodsScanned; }
        // }
        // public bool methodsBound {
        //     get { return behaviourTreeGenerator.methodsBound; }
        // }
        public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods {
            get { return behaviourTreeGenerator.methods; }
        }
        #endregion
    }
}