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

using System.Collections;
using System.Collections.Generic;
using System.Text;
using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
using UnityEngine;

namespace MyBT {
    public class CodeInspector : ScriptableObject {
        #if UNITY_EDITOR

        #region type definitions
        [System.Serializable] public class DictionaryOfIntAndNodeList : MyBT.SerializableDictionary<int, NodeList> { }
        #endregion

        [SerializeField]
        private List<DictionaryOfIntAndNodeList> openNodeLines;
        
        private List<DictionaryOfIntAndNodeList> closeNodeLines;

        public SerializedProperty openNodeLinesAsSerializedProperty {
            get {
                SerializedObject sObj = new SerializedObject(this);
                return sObj.FindProperty("openNodeLines");
            }
        }

        [SerializeField]
        private TaskController taskController;

        public void Init(TaskController _taskController) {
            taskController = _taskController;
        }

        public bool hasTaskControllerDefined {
            get { return (taskController != null); }
        }

        private string nodeLineNumberListGenerationState {
            get {
                return (openNodeLines != null) + "&&" + (closeNodeLines != null);
            }
        }

        public int nodeLineCount {
            get {
                int sum = 0;
                if (openNodeLines != null) {
                    sum += openNodeLines.Count;
                }
                if (closeNodeLines != null) {
                    sum += closeNodeLines.Count;
                }
                return sum;
            }
        }

        [SerializeField]
        public FileHash __lineNumberListBtFileHashes = new FileHash();
        public FileHash lineNumberListBtFileHashes {
            get { return __lineNumberListBtFileHashes;  }
            private set { __lineNumberListBtFileHashes = value; }
        }
        public bool CheckLineNumberHashOutdated(bool allowNull = false) {
            if (taskController != null) { 
                return lineNumberListBtFileHashes.HashChanged(taskController.generatedCodeBtFileHashes, allowNull);
            }
            Debug.LogWarning("CheckLineNumberHashOutdated: taskController is undefined!");
            if (nodeLineNumberListError) { 
                return true;
            }
            return true;
        }
        public string GetLineNumberHashes () {
            return $"codeHash: {lineNumberListBtFileHashes} fileHash:{taskController.generatedCodeBtFileHashes} ";
        }
        public void ClearLineNumberHashes() {
            lineNumberListBtFileHashes.ClearHashCache();
        }

        private bool __nodeLineNumberListError = false;
        public bool nodeLineNumberListError {
            get { return __nodeLineNumberListError; }
            private set { __nodeLineNumberListError = value; }
        }

        public bool nodeLineNumberListGenerated {
            get {
                if ((openNodeLines != null) && (closeNodeLines != null)) {
                    int tokenCount = taskController.tokens != null ?
                            taskController.tokens.Count : 0;
                    bool generated = (tokenCount == openNodeLines.Count) && (tokenCount == closeNodeLines.Count);
                    return generated;
                }
                else {
                    return false;
                }
            }
        }

        private void UpdateLineNumberHash() {
            lineNumberListBtFileHashes.UpdateHashCache(taskController.generatedCodeBtFileHashes);
        }

        public void ResetNodeLinenumberList() {
            if (taskController != null) { 
                if (taskController.generatorLogging) Debug.Log("CodeInspector.ResetNodeLinenumberList");
            }
            nodeLineNumberListError = false;
            openNodeLines = null;
            closeNodeLines = null;

            ClearLineNumberHashes();
        }

        public new void SetDirty() {
            if (!Application.isPlaying) {
                if (taskController != null) { 
                    EditorSceneManager.MarkSceneDirty(taskController.gameObject.scene);
                    EditorUtility.SetDirty(taskController);
                    taskController.SetBehaviourTreeDirty();
                }
            }
        }

        public void RegenerateNodeLineNumberListIfRequired (bool forceUpdate = false) {
            if (CheckLineNumberHashOutdated() || forceUpdate) {
                if (taskController.generatorLogging) {
                    Debug.LogWarning($"TaskController.Update: regenerating behaviour tree {GetLineNumberHashes()} {forceUpdate}");
                }

                ResetNodeLinenumberList();
                GenerateNodeLineNumberList();
            }
        }

        public void GenerateNodeLineNumberList() {
            if (taskController) {
                if (!taskController.generationError && !taskController.bindingError && CheckLineNumberHashOutdated() && !nodeLineNumberListGenerated) {
                    if (taskController.generatorLogging)
                        Debug.Log($"CodeInspector.GenerateNodeLineNumberList: running " +
                        $"(!{taskController.generationError}&&!{taskController.bindingError}&&{CheckLineNumberHashOutdated()}&&!{nodeLineNumberListGenerated})");

                    openNodeLines = new List<DictionaryOfIntAndNodeList>();
                    closeNodeLines = new List<DictionaryOfIntAndNodeList>();
                    foreach (NodeList treeRoots in taskController.treeRootNodes) {
                        // Debug.Log("Handling "+treeRoots);
                        DictionaryOfIntAndNodeList openNodeLine = new DictionaryOfIntAndNodeList();
                        DictionaryOfIntAndNodeList closeNodeLine = new DictionaryOfIntAndNodeList();
                        foreach (Node treeNode in treeRoots) {
                            // Debug.Log("-  -  "+treeNode);
                            foreach (Node childNode in treeNode.getAllNodesRecursively(false)) {
                                // Debug.Log("-  -  -  "+childNode);
                                if (!openNodeLine.ContainsKey(childNode.lineNumber)) {
                                    // Debug.Log("-  -  -  new openNodeLine: "+childNode.lineNumber);
                                    openNodeLine[childNode.lineNumber] = new NodeList();
                                }
                                openNodeLine[childNode.lineNumber].Add(childNode);

                                if (!closeNodeLine.ContainsKey(childNode.lastLineNumber)) {
                                    // Debug.Log("-  -  -  new closeNodeLine: "+childNode.lineNumber);
                                    closeNodeLine[childNode.lastLineNumber] = new NodeList();
                                }
                                closeNodeLine[childNode.lastLineNumber].Add(childNode);
                            }
                        }
                        openNodeLines.Add(openNodeLine);
                        closeNodeLines.Add(closeNodeLine);
                    }

                    if (nodeLineNumberListGenerated) {
                        UpdateLineNumberHash();
                        // Debug.Log("CodeInspector.GenerateNodeLineNumberList: generated " + lineNumberListBtFileHashes);
                    }
                    else {
                        Debug.LogError("CodeInspector.GenerateNodeLineNumberList: failed" + lineNumberListBtFileHashes);
                    }
                }
                else {
                    if (taskController.generatorLogging) 
                        Debug.Log($"CodeInspector.GenerateNodeLineNumberList: not run (!{taskController.generationError} && {CheckLineNumberHashOutdated()} &&! {nodeLineNumberListGenerated} )");
                }
            }
        }

        public void DrawCodeInspector() {
            // string codeDisplayLog = "";
            EditorGUILayout.BeginVertical(EditorStyles.helpBox);

            StringBuilder errorMessageSB = new StringBuilder();
            if (taskController == null) {
                errorMessageSB.Append("TaskController undefined! ");
            }
            else {
                if (taskController.generationError) {
                    errorMessageSB.Append("generationError! ");
                }

                if (taskController.bindingError) {
                    errorMessageSB.Append("generationError! ");
                }

                if (!taskController.hasScript) {
                    errorMessageSB.Append("Missing Behaviour Tree Script! ");
                }
            }
            if (nodeLineNumberListError) {
                errorMessageSB.Append("nodeLineNumberListError! ");
            }
            if (errorMessageSB.Length > 0) {
                GUI.contentColor = Color.red;
                GUILayout.TextArea(errorMessageSB.ToString());
                GUI.contentColor = Color.black;
            }

            // create a style based on the default label style5
            GUIStyle myButtonStyle = new GUIStyle(GUI.skin.button);
            // do whatever you want with this style, e.g.:
            myButtonStyle.margin = new RectOffset(0, 0, -3, 0);
            myButtonStyle.border = new RectOffset(0, 0, 0, 0);
            myButtonStyle.overflow = new RectOffset(-4, -4, -4, -4);
            myButtonStyle.fixedHeight = 25;
            myButtonStyle.fixedWidth = 25;

            // GUISkin guiSkin = (GUISkin)AssetDatabase.LoadAssetAtPath("Assets/Editor Default Resources/MyBTSkin.guiskin", typeof(GUISkin));
            // if (guiSkin == null) {
            //     Debug.LogError($"GUISkin for BT not found MyBTSkin");
            // }
            // AssetDatabase.FindAssets("MyBTSkin") as GUISkin;
            // GUISkin guiSkin = MyBtResources.myBtGuiSkin;

            bool btSettingsChanged = false;

            //Debug.Log($"--- CODE INSPECTOR --- {taskController.gameObject.name} --- {Time.frameCount} -------------------------------------");
            if (taskController != null) {
                if (nodeLineNumberListGenerated && !nodeLineNumberListError) {
                    // for "the number of task scripts"
                    for (int i = 0; i < taskController.taskScripts.Count; i++) {
                        if (taskController.taskScripts[i] != null) {
                            EditorGUILayout.LabelField(("_ Script _ " + taskController.taskScripts[i].name + " ").PadRight(120, '_'));

                            // EditorGUILayout.LabelField(taskController.taskScripts[i].name);
                            TokenList tokens = taskController.tokens[i];
                            DictionaryOfIntAndNodeList openNodeLine = openNodeLines[i];
                            DictionaryOfIntAndNodeList closeNodeLine = closeNodeLines[i];

                            // get the line number of the last token
                            int numberOfLines = tokens[tokens.Count - 1].location.line;
                            // codeDisplayLog += $"- numberOfLines {numberOfLines}\n";
                            int tokenIndex = 0;
                            // int nodeDepthCached = 0;
                            // bool foldCache = false;
                            // int foldAbove = int.MaxValue;
                            Node lastNodeCache = null;
                            Node thisNode = null;
                            bool isFolded = false;
                            StringBuilder sbLineText = new StringBuilder(200);
                            string lineText;
                            StringBuilder sbLineNumber = new StringBuilder(4);
                            string lineNumberText;
                            NodeList logNodes = new NodeList();
                            bool isClosing; // isOpening
                            int nodeDepth = 0;

                            for (int currentLineNumber = 1; currentLineNumber < numberOfLines + 1; currentLineNumber++) {
                                // codeDisplayLog += $"iterate line {currentLineNumber}\n";

                                sbLineNumber.Clear();
                                lineNumberText = sbLineNumber.AppendFormat("{0:D4}", tokens[tokenIndex].location.line).ToString();
                                // lineNumberText = String.Format("{0:D4}", tokens[tokenIndex].location.line);

                                thisNode = null;
                                isClosing = false;
                                // isOpening = false;

                                if (closeNodeLine.ContainsKey(currentLineNumber)) {
                                    lastNodeCache = closeNodeLine[currentLineNumber][0];
                                    isClosing = true;
                                    nodeDepth -= 1;
                                }

                                // generate text of code to display by iterating trough the tokens
                                sbLineText.Clear();
                                if (nodeDepth >= 0)
                                    sbLineText.Append(' ', nodeDepth * 4);
                                while ((tokenIndex < tokens.Count) && (tokens[tokenIndex].location.line == currentLineNumber)) {
                                    sbLineText.Append(tokens[tokenIndex].location.code.Replace("\r", "").Replace("\n", "").Replace("\t", "").TrimStart(' ').TrimEnd(' '));
                                    tokenIndex += 1;
                                    // Debug.Log("tokenIndex "+tokenIndex + " on line "+ currentLineNumber + " " + s);
                                }
                                lineText = sbLineText.ToString(); // + $"{nodeDepth}";

                                // find depth of current Node
                                if (openNodeLine.ContainsKey(currentLineNumber)) {
                                    lastNodeCache = openNodeLine[currentLineNumber][0];
                                    thisNode = openNodeLine[currentLineNumber][0];

                                    if (thisNode != null) {
                                        if ((thisNode.GetType() == typeof(CompositeNode))
                                         || (thisNode.GetType() == typeof(TreeNode))
                                         || (thisNode.GetType() == typeof(DecoratorNode))
                                        ) {
                                            // isOpening = true;
                                            nodeDepth += 1;
                                        }
                                    }

                                    // thisnode is a weakreference, and is deleted externally on script change
                                    // https://docs.unity3d.com/Manual/script-Serialization.html
                                    else {
                                        // the object has been destroyed
                                        Debug.LogWarning($"DrawCodeInspector: detected error on line {currentLineNumber} -> clearing nodelinenumberlists");

                                        //requireResetNodeLinenumberList = true;
                                        nodeLineNumberListError = true;
                                    }
                                }

                                // if this line contains a node, we fold it when the parent is folded. because a composite node would be invisible if it hides itself.
                                isFolded = (lastNodeCache != null) ? lastNodeCache.parentFolded : false;

                                // this node is undefined, it is a comment, a closing bracket or a empty line. in this case use the lastNodecache.isFolded, as we are surely not a composite node
                                if (thisNode == null) {
                                    // dont fold closing brackets
                                    if (isClosing) {
                                        if (lastNodeCache != null) {
                                            if (lastNodeCache.parentNode != null) {
                                                isFolded = lastNodeCache.parentFolded;
                                            }
                                        }
                                    }
                                    else {
                                        if (lastNodeCache != null) {
                                            // fold comments in composites
                                            isFolded = lastNodeCache.isFolded || isFolded;
                                        }
                                    }
                                    if (nodeDepth == 0) {
                                        isFolded = false;
                                    }
                                }

                                if (!isFolded) {
                                    EditorGUILayout.BeginHorizontal();
                                    EditorGUILayout.LabelField(lineNumberText, GUILayout.Width(32));

                                    // dont show fold on ActionNode & RunTreeNode
                                    if ((thisNode != null) && (thisNode.GetType() != typeof(ActionNode)) && (thisNode.GetType() != typeof(RunTreeNode))) {
                                        bool newIsFolded = EditorGUILayout.Toggle(thisNode.isFolded, MyBtResources.popoutToggleGuiStyle, GUILayout.MaxWidth(16)); // label, 
                                        if (newIsFolded != thisNode.isFolded) {
                                            thisNode.isFolded = newIsFolded;
                                            btSettingsChanged |= true;
                                        }
                                    }
                                    else {
                                        EditorGUILayout.LabelField("", MyBtResources.emptyGuiStyle, GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                    }

                                    EditorGUILayout.LabelField(lineText, GUILayout.ExpandWidth(true), GUILayout.Height(MyBtResources.defaultGuiHeight));

                                    // logNodes = new NodeList();
                                    logNodes.Clear();
                                    // list of nodes that should show logging

                                    if (thisNode != null) {
                                        foreach (Node lineNode in openNodeLine[currentLineNumber]) {
                                            if ((lineNode != null) && (lineNode.nodeRuntimeDataList != null)) {
                                                //foreach (NodeRuntimeData runtimeData in lineNode.nodeRuntimeDataList) {
                                                //    if (runtimeData != null) {
                                                //        //GUI.color = MyBtResources.NodeStateColors[runtimeData.nodeState];
                                                //        //EditorGUILayout.LabelField("", MyBtResources.NodeStateGuiStyle[runtimeData.nodeState], GUILayout.Width(defaultGuiIconWidth), GUILayout.Height(defaultGuiHeight));

                                                //        GUI.color = MyBtResources.NodeResultColors[runtimeData.nodeResult];
                                                //        EditorGUILayout.LabelField($"R{lineNode.nodeRuntimeDataList.Count}", MyBtResources.NodeResultGuiStyle[runtimeData.nodeResult], GUILayout.Width(defaultGuiIconWidth), GUILayout.Height(defaultGuiHeight));
                                                //    }
                                                //}
                                                //Debug.Log(lineNode.NodeLogger("DEBUG", ""));
                                                if ((lineNode.lastResults != null) && (taskController.UiChangedAt(lineNode.lastResultsTime))) {
                                                    for (int j=0; j< lineNode.lastResults.Count; j++) {
                                                        foreach (NodeResult nrs in lineNode.lastResults[j]) {
                                                            GUI.color = MyBtResources.NodeResultColors[nrs];
                                                            EditorGUILayout.LabelField("", MyBtResources.NodeResultGuiStyle[nrs], GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                                        }
                                                        //for (int k = 0; k < lineNode.lastResults[j].Count; k++) {
                                                        //GUI.color = MyBtResources.NodeResultColors[lineNode.lastResults[j][k]];
                                                        //EditorGUILayout.LabelField("", MyBtResources.NodeResultGuiStyle[lineNode.lastResults[j][k]], GUILayout.Width(defaultGuiIconWidth), GUILayout.Height(defaultGuiHeight));
                                                        //}
                                                        //lineNode.lastResults[j] = NodeResult.Undefined;
                                                    }
                                                    //foreach (NodeResult lastResult in lineNode.lastResults) {
                                                    //}
                                                }
                                                else {
                                                    EditorGUILayout.LabelField("", MyBtResources.nodeUninitializedState, GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                                }

                                                GUI.color = lineNode.logStringDisplay ? Color.yellow : Color.white;
                                                bool newLogStringDisplay = EditorGUILayout.Toggle(lineNode.logStringDisplay, MyBtResources.logStringToggleGuiStyle, GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                                if (newLogStringDisplay != lineNode.logStringDisplay) {
                                                    lineNode.logStringDisplay = newLogStringDisplay;
                                                    btSettingsChanged |= true;
                                                }
                                                if (lineNode.logStringDisplay || lineNode.debugChangesActive) {
                                                    logNodes.Add(lineNode);
                                                }

                                                if (taskController.runtimeLogging) {
                                                    GUI.color = lineNode.debugChangesActive ? Color.cyan : Color.white;
                                                    bool newDebugChangesActive = EditorGUILayout.Toggle(lineNode.debugChangesActive, MyBtResources.alertToggleGuiStyle, GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                                    if (newDebugChangesActive != lineNode.debugChangesActive) {
                                                        lineNode.debugChangesActive = newDebugChangesActive;
                                                        btSettingsChanged |= true;
                                                    }

                                                    GUI.color = lineNode.debugInternalActive ? Color.red : Color.white;
                                                    bool newDebugInternalActive = EditorGUILayout.Toggle(lineNode.debugInternalActive, MyBtResources.debugToggleGuiStyle, GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight));
                                                    if (newDebugInternalActive != lineNode.debugInternalActive) {
                                                        lineNode.debugInternalActive = newDebugInternalActive;
                                                        btSettingsChanged |= true;
                                                    }
                                                }
                                            }
                                        }
                                        GUI.color = Color.white;
                                    }
                                    else {
                                        // to fix layout lines with a comment
                                        Rect iconRect2 = GUILayoutUtility.GetRect(GUIContent.none, new GUIStyle(), new GUILayoutOption[] { GUILayout.Width(MyBtResources.defaultGuiIconWidth), GUILayout.Height(MyBtResources.defaultGuiHeight) });
                                        iconRect2.height += 2;
                                        iconRect2.y += 1;
                                        iconRect2.x += 1;
                                    }

                                    EditorGUILayout.EndHorizontal();

                                    if (logNodes.Count > 0) {
                                        foreach (Node logNode in logNodes) {
                                            foreach (NodeRuntimeData runtimeData in logNode.nodeRuntimeDataList) {
                                                if (runtimeData != null) {
                                                    GUI.color = Color.yellow;

                                                    if (logNode.logStringDisplay) {
                                                        if (logNode is ActionNode) {
                                                            EditorGUILayout.BeginHorizontal();
                                                            string logString = (runtimeData.logString != null) ? runtimeData.logString.ToString().Replace("\n", "\\n") : "";
                                                            EditorGUILayout.LabelField("logs: " + logString);
                                                            EditorGUILayout.EndHorizontal();

                                                            EditorGUILayout.BeginHorizontal();
                                                            ActionNodeRuntimeData anrd = (runtimeData as ActionNodeRuntimeData);
                                                            if (anrd != null) {
                                                                string userDataString = (anrd.userData != null) ? anrd.userData.ToString().Replace("\n", "\\n") : "";
                                                                EditorGUILayout.LabelField("data: " + userDataString);
                                                            }
                                                            EditorGUILayout.EndHorizontal();
                                                        }
                                                    }

                                                    if (logNode.debugChangesActive) {
                                                        EditorGUILayout.BeginHorizontal();
                                                        string statusString = $"state={runtimeData.nodeState} result={runtimeData.nodeResult}";
                                                        if (logNode is CompositeNode) {
                                                            statusString += $" curIndex={runtimeData.currentIndex}";
                                                        }
                                                        EditorGUILayout.LabelField("" + statusString);
                                                        EditorGUILayout.EndHorizontal();
                                                    }

                                                    GUI.color = Color.white;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                else {
                    string logMessage = $"CodeInspector.DrawCodeInspector: not drawing ({nodeLineNumberListGenerated}) ({nodeLineNumberListGenerationState})";
                    GUI.contentColor = Color.red;
                    EditorGUILayout.LabelField(logMessage);
                    GUI.contentColor = Color.black;
                }

                if (btSettingsChanged) {
                    SetDirty();
                }


                // GUILayout.Label(codeDisplayLog);
            }

            EditorGUILayout.EndVertical();
        }
#endif
    }
}