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

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

namespace MyBT {
    [System.Serializable]
    public class BehaviourTreeGenerator : ScriptableObject {
        // void Init() {

        // }

        #region generated stuff
        // the contents of the script
        // [HideInInspector, SerializeField]
        // public List<string> codes = null;

        // list of the tasks
        [SerializeField]
        public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods = null;

        // the tokens are generated by the TokenEnvironment
        [SerializeField]
        public List<TokenList> tokens = null;

        // the treenodes are generated by the NodeGenerator
        [SerializeField]
        public List<NodeList> treeRootNodes = null;

        [SerializeField]
        public TreeNode rootNode = null;
        #endregion

        #region settings
        // [SerializeField]
        // public List<UnityEngine.TextAsset> taskScripts = new List<UnityEngine.TextAsset>();

        // currently causes errors, thus it's disabled in the editor
        [HideInInspector]
        public bool readUnityComponents = false;

        [SerializeField]
        public bool generatorLogging = false;

        public bool internalAutomaticRegenerate = true;
        #endregion

        #region runtime variables
        public bool generationError = false;
        public bool bindingError = false;
        #endregion

        #region file change detection
        // cache of the file hashes as the currently are
        [SerializeField]
        public FileHash textAssetHashes = new FileHash();
        // stores the file hashes at the moment of generation
        [SerializeField]
        public FileHash generatedCodeBtFileHashes = new FileHash();
        // stores the file hashes at the moment of binding
        [SerializeField]
        public FileHash boundCodeBtFileHashes = new FileHash();
        #endregion

        public bool HasScripts (TaskController _taskController) {
            return (_taskController.taskScripts != null) && (_taskController.taskScripts.Count > 0);
        }

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


        public void FullGenerate (TaskController taskController, bool force) {
            GenerateTokensAndNodes(taskController, force);
            ScanAndBindMethods(taskController, force);
            SetTaskControllerAndGenerateCache(taskController);
        }

        public void FullReset() {
            ClearBinding();
            ResetNodesAndTokens();
        }

        public void ClearBindingsAndScanAndBindMethods (TaskController taskController, bool force) {
            ClearBinding();
            ScanAndBindMethods(taskController, force);
        }

        public void BtScriptChangeDetected(TaskController _taskController, bool forceUpdate = false) {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected");

            // update current script hashes, so we compare the generated nodes against the actual scripts  
            textAssetHashes.UpdateHashCacheFromTextAsset(_taskController.taskScripts);
            if (CheckGenerationOutdated() || forceUpdate) {
                if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected: clear nodes and tokens");
                ResetNodesAndTokens();
            }
            if (CheckBindingOutdated(_taskController) || forceUpdate) {
                if (generatorLogging) Debug.Log("BehaviourTreeGenerator.BtScriptChangeDetected: clear binding");
                ClearBinding();
            }
            // regenerate
            GenerateTokensAndNodes(_taskController);
            ScanAndBindMethods(_taskController, forceUpdate);

            _taskController.InspectorRegenerate?.Invoke(true);
        }

        public void SetTaskControllerAndGenerateCache (TaskController _taskController) {
            if (treeRootNodes != null) { 
                foreach (NodeList rn in treeRootNodes) {
                    foreach (Node crn in rn) { 
                        foreach (Node cn in crn.getAllNodesRecursively(false)) {
                            cn.SetTaskController(_taskController);
                            cn.GenerateNodeDataCaches();
                        }
                    }
                }
            }
            else {
                Debug.LogError($"BehaviourtreeGenerator.SetTaskController: treeRootNodes {treeRootNodes}");
            }
        }

        public void Restart() {
            if (rootNode != null) {
                if (rootNode.nodeRuntimeDataList.Count > 0) {
                    rootNode.nodeRuntimeDataList[0].Destroy();
                }
            }
        }


        public void RegenerateBehaviourTreeIfRequired(TaskController _taskController, bool forceUpdate = false) {
            if (CheckGenerationOutdated() || CheckBindingOutdated(_taskController) || forceUpdate) {
                if (generatorLogging) {
                    Debug.LogWarning($"TaskController.Update: regenerating behaviour tree {CheckGenerationOutdated()} {CheckBindingOutdated(_taskController)}");
                }

                FullReset();
            }
            else {
                Debug.Log($"TaskController.Update: status: generated:{generatedCodeBtFileHashes} bound:{boundCodeBtFileHashes} text:{textAssetHashes}");
            }

            if (CheckGenerationOutdated() || CheckBindingOutdated(_taskController) || forceUpdate) { // || !behaviourTreeGenerator.isBound // this makes it way slower...
                if (generatorLogging) {
                    Debug.LogWarning($"TaskController.TickUpdate: updating bindings");
                }

                FullGenerate(_taskController, false);
            }
            else {
                Debug.Log($"TaskController.Update: status: generated:{generatedCodeBtFileHashes} bound:{boundCodeBtFileHashes} text:{textAssetHashes}");
            }
        }


        // public bool tokensGenerated {
        //     get {
        //         return Tokenizer.IsAllGenerated(tokens);
        //     }
        // }

        // public bool nodesGenerated {
        //     get {
        //         return NodeGenerator.IsAllGenerated(treeRootNodes, rootNode);
        //     }
        // }

        // public bool isGenerated {
        //     get {
        //         return (hasScript && tokensGenerated && nodesGenerated);
        //     }
        // }

        // public string generationState {
        //     get {
        //         return (hasScript + "&&" + tokensGenerated + "&&" + nodesGenerated);
        //     }
        // }

#region Tokenizer & NodeGenerator
        public bool CheckGenerationOutdated(bool allowNull = false) {
            // if (debugLog) Debug.Log("CheckGenerationOutdated:\tGenerated Hash:\t" + generatedCodeBtFileHashes + " \n"+
            //                                             "\t\t\tText Asset Hash:\t" + textAssetHashes);
            return generatedCodeBtFileHashes.HashChanged(textAssetHashes, allowNull);
        }

        public string GetGenerationHashes () {
            return $"text:{textAssetHashes} generated:{generatedCodeBtFileHashes}";
        }

        public void UpdateGenerationHashes() {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.UpdateGenerationHashes");
            generatedCodeBtFileHashes.UpdateHashCache(textAssetHashes);
        }

        public void ClearGenerationHashes() {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearGenerationHashes");
            generatedCodeBtFileHashes.ClearHashCache();
        }

        public void GenerateTokensAndNodes(TaskController _taskController, bool force=false) {
            if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.Generate ({CheckGenerationOutdated()} && !{generationError} && {internalAutomaticRegenerate} )");

            if (HasScripts(_taskController) && CheckGenerationOutdated() && !generationError && (internalAutomaticRegenerate || force)) {
                // generate the tokens
                if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.Generate: --- Execute Tokenizer --- ({HasScripts(_taskController)} && ({GetGenerationHashes()}) !{generationError} && ({internalAutomaticRegenerate} || {force}))");

                bool tokenizerSuccess = Tokenizer.Tokenize(out tokens, _taskController.taskScripts);
                generationError |= !tokenizerSuccess;

                if (!tokenizerSuccess) {
                    Debug.LogError($"BehaviourTreeGenerator.Generate: Tokenizer not generated ");
                }

                bool nodeGenerationSuccess = NodeGenerator.Generate(out treeRootNodes, out rootNode, tokens);
                generationError |= !nodeGenerationSuccess;

                if (!nodeGenerationSuccess) {
                    Debug.LogError("BehaviourTreeGenerator.Generate: failed");
                }

                // assign taskController
                SetTaskControllerAndGenerateCache(_taskController);

                // update hash to save last successful generation source
                if (generationError) {
                    Debug.LogError($"BehaviourTreeGenerator.Generate: NodeGenerator not generated ");
                    ClearGenerationHashes();
                }
                else {
                    UpdateGenerationHashes();
                }
            }
        }

        public int nodeCount {
            get { return NodeGenerator.GeneratedSize(treeRootNodes); }
        }

        /// <summary>
        /// Reset the whole Task / Node / Action Tree structure for regeneration from the source behaviour tree.
        /// </summary>
        public void ResetNodesAndTokens() {
            if (generatorLogging) Debug.LogWarning("BehaviourTreeGenerator.ResetNodesAndTokens");

            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.Reset: --- Reset NodeGenerator ---");
            NodeGenerator.DestroyAll(ref treeRootNodes, ref rootNode);

            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.Reset: --- Reset Tokenizer ---");
            Tokenizer.DestroyAll(ref tokens);

            generationError = false;
            ClearGenerationHashes();
        }
#endregion

        // public bool methodsScanned {
        //     get {
        //         return MethodScanner.IsGenerated(methods);
        //     }
        // }

        // public bool methodsBound {
        //     get {
        //         return MethodBinder.IsAllBound(treeRootNodes);
        //     }
        // }

        // public bool isBound {
        //     get {
        //         return (methodsScanned && methodsBound);
        //     }
        // }

#region Binding 
        public bool CheckBindingOutdated(TaskController _taskController, bool allowNull = false) {
            textAssetHashes.UpdateHashCacheFromTextAsset(_taskController.taskScripts);
            return boundCodeBtFileHashes.HashChanged(textAssetHashes, allowNull);
        }

        public void UpdateBindingHashes() {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.UpdateBindingHashes");
            boundCodeBtFileHashes.UpdateHashCache(textAssetHashes);
        }

        public void ClearBindingHashes() {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBindingHashes");
            boundCodeBtFileHashes.ClearHashCache();
        }

        public void VerifyBinding (TaskController _taskController) {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.VerifyBinding");
            if (!MethodBinder.VerifyBinding(treeRootNodes)) {
                ClearBindingHashes();
                RegenerateBehaviourTreeIfRequired(_taskController);
            }
        }

        public void ScanAndBindMethods(TaskController _taskController, bool force = false) {
            if (CheckBindingOutdated(_taskController) && !bindingError && internalAutomaticRegenerate || force) {
                if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods:  ({CheckBindingOutdated(_taskController)} && !{bindingError} && {internalAutomaticRegenerate} || {force}) ");

                // read the contents of the script, remove tabs and spaces
                bool componentCheckSuccess = MethodScanner.CheckComponents(_taskController.gameObject, readUnityComponents, ref methods);
                bindingError |= !componentCheckSuccess;
                if (!componentCheckSuccess) {
                    if (generatorLogging) Debug.LogError("BehaviourTreeGenerator.ScanAndBindMethods: MethodScanner failed");
                }

                bool methodBinderSuccess = MethodBinder.Bind(ref treeRootNodes, rootNode, methods);
                bindingError |= !methodBinderSuccess;
                if (!methodBinderSuccess) {
                    if (generatorLogging) Debug.LogError("BehaviourTreeGenerator.ScanAndBindMethods: MethodBinder failed");
                }

                SetTaskControllerAndGenerateCache(_taskController);

                if (!bindingError) {
                    if (generatorLogging) Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods: --- UpdateBindingHashes ---");
                    UpdateBindingHashes();
                }
                else {
                    Debug.Log($"BehaviourTreeGenerator.ScanAndBindMethods: ScanAndBindMethods Failed");
                    ClearBindingHashes();
                }
            }
            else {
                if (generatorLogging) Debug.LogWarning($"BehaviourTreeGenerator.Bind: not running ( {CheckBindingOutdated(_taskController)} && !{generationError} && {internalAutomaticRegenerate} )");
            }
        }

        public void ClearBinding() {
            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBinding: --- Reset MethodBinder ---");
            MethodBinder.ResetAll(ref treeRootNodes); // causes error in building

            if (generatorLogging) Debug.Log("BehaviourTreeGenerator.ClearBinding: --- Reset MethodScanner ---");
            MethodScanner.Reset(ref methods);

            bindingError = false;

            ClearBindingHashes();
        }
#endregion
    }
}