﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.Experimental.GraphView;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using static UnityEngine.Analytics.IAnalytic;

namespace E.Story
{
    /// <summary>
    /// 故事编辑器窗口
    /// </summary>
    public class StoryEditorWindow : EditorWindow
    {
        #region 自定义设置属性

        /// <summary>
        /// 是否允许节点或分组标题重复
        /// </summary>
        public static bool AllowDuplicateTitles { get; } = true;

        /// <summary>
        /// 是否允许节点或分组标题内含有空格
        /// </summary>
        public static bool AllowWhitespacesInTitles { get; } = true;

        /// <summary>
        /// 是否允许节点或分组标题内含有特殊字符
        /// </summary>
        public static bool AllowSpecialCharactersInTitles { get; } = false;

        #endregion


        #region 只读字段

        // 预设名称
        private readonly string defaultFileName = "新故事";
        private readonly string keyLastStoryName = "最近打开故事";

        // uss文件路径
        private readonly string variablesPath = "Assets/E Tool/E Story/Editor/Style Sheets/Variables.uss";
        private readonly string toolbarStylePath = "Assets/E Tool/E Story/Editor/Style Sheets/ToolbarStyle.uss";
        private readonly string graphViewStylePath = "Assets/E Tool/E Story/Editor/Style Sheets/GraphViewStyle.uss";

        // 文件夹路径
        private readonly string exampleFolderPath = "Assets/E Tool/E Story/Example";
        private readonly string storyDatasFolderPath = "Assets/E Tool/E Story/Example/Story Datas";
        private readonly string storyDatasFolderName = "Story Datas";

        #endregion


        #region UI元素

        private StoryGraphView graphView;
        private Toolbar toolbar;
        private VisualElement panLeft;
        private VisualElement panCenter;
        private VisualElement panRight;
        private TextField tfdFileName;
        private Button btnSave;
        private Button btnOpen;
        private Button btnNew;
        private Button btnClear;
        private Button btnVars;
        private Button btnNavMap;

        #endregion


        #region 临时变量

        private string fileName;
        private StoryDataSO storyData;

        #endregion


        #region 静态方法

        /// <summary>
        /// 打开窗口
        /// </summary>
        [MenuItem("E Tool/打开 E Story 故事编辑器 %&s")]
        // 在静态方法上添加MenuItem属性，可使Unity菜单栏显示相应按钮，点击按钮可调用此方法
        // 字符串末尾的%&s表示可用快捷键，其中%表示Ctrl键，&表示Alt键，s表示S键，另外还有#可以表示Shift键
        public static void Open()
        {
            // 获取窗口
            StoryEditorWindow wnd = GetWindow<StoryEditorWindow>();
            // 修改窗口标题
            wnd.titleContent = new GUIContent("E Story 故事编辑器");
        }

        /// <summary>
        /// 当双击资产文件时，打开故事编辑器
        /// </summary>
        [OnOpenAsset()]
        public static bool OnDoubleClick(int instanceID)
        {
            // 获取窗口实例并打开
            StoryEditorWindow wnd = (StoryEditorWindow)GetWindow(typeof(StoryEditorWindow));
            if (wnd == null)
            {
                Open();
            }
            wnd.RemoveNotification();

            // 获取资产路径
            string fullPath = AssetDatabase.GetAssetPath(instanceID);

            // 检测是否目标资产类型
            StoryDataSO storyData = IOUtility.LoadAsset<StoryDataSO>(fullPath);
            if (storyData == null)
            {
                return false;
            }

            string str = "确认打开新故事并覆盖当前视图内容吗？未保存的数据将无法恢复。";
            if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
            {
                wnd.storyData = storyData;
                wnd.RecordCurrentStory();

                wnd.graphView.ClearGraph();
                wnd.LoadDatas(storyData);

                // 提示消息
                string message = $"故事已打开";
                wnd.ShowNotification(new GUIContent(message));
            }

            return true;
        }

        #endregion


        #region Unity 方法

        /// <summary>
        /// 绘制GUI
        /// </summary>
        private void CreateGUI()
        {
            minSize = new(600, 400);

            // 添加组件
            AddToolbar();
            AddGraphView();

            // 引用样式
            AddStyles();

            // 打开上个故事
            OpenLastStory();
        }

        #endregion


        #region 添加窗口对象

        /// <summary>
        /// 添加节点视图
        /// </summary>
        private void AddGraphView()
        {
            // 实例化视图类
            graphView = new StoryGraphView(this);
            // 将视图放入UI根元素
            rootVisualElement.Add(graphView);
        }

        /// <summary>
        /// 添加工具栏
        /// </summary>
        private void AddToolbar()
        {
            // 创建工具栏
            toolbar = new();

            // 创建分组
            panLeft = new VisualElement();
            panCenter = new VisualElement();
            panRight = new VisualElement();

            // 创建UI元素
            tfdFileName = ElementUtility.CreateTextField(defaultFileName, "当前故事", callback =>
            {
                fileName = callback.newValue.RemoveSpecialCharacters();
            });
            btnSave = ElementUtility.CreateButton("保存", () => SaveStory());
            btnOpen = ElementUtility.CreateButton("打开", () => OpenStory());
            btnNew = ElementUtility.CreateButton("新建", () => NewStory());
            btnClear = ElementUtility.CreateButton("清空", () => ClearGraphAndCreateDefaultDatas());
            btnVars = ElementUtility.CreateButton("变量面板", () => ToggleVarPanel());
            btnNavMap = ElementUtility.CreateButton("导航面板", () => ToggleMiniMap());

            // 将目标放入容器
            panLeft.Add(btnOpen);
            panLeft.Add(btnNew);

            panCenter.Add(tfdFileName);
            panCenter.Add(btnSave);
            panCenter.Add(btnClear);

            panRight.Add(btnVars);
            panRight.Add(btnNavMap);

            toolbar.Add(panLeft);
            toolbar.Add(panCenter);
            toolbar.Add(panRight);

            rootVisualElement.Add(toolbar);
        }

        /// <summary>
        /// 添加样式文件
        /// </summary>
        private void AddStyles()
        {
            rootVisualElement.AddStyleSheets(variablesPath);
            toolbar.AddStyleSheets(toolbarStylePath);
            graphView.AddStyleSheets(graphViewStylePath);
        }

        #endregion


        #region 按钮事件

        /// <summary>
        /// 保存故事
        /// </summary>
        private void SaveStory()
        {
            // 检测文件名是否为空
            if (string.IsNullOrEmpty(fileName))
            {
                string str = "故事名称不能为空。";
                EditorUtility.DisplayDialog("警告", str, "明白");
                return;
            }

            // 检测是否有重复标题
            if (graphView.DuplicateTitlesAmount > 0)
            {
                string str = "节点或分组的标题不能重复，请检查视图中标红的对象。";
                EditorUtility.DisplayDialog("警告", str, "明白");
                return;
            }

            // 创建存档文件夹
            IOUtility.CreateFolder(exampleFolderPath, storyDatasFolderName);

            // 创建图形文件
            storyData = IOUtility.CreateAsset<StoryDataSO>(storyDatasFolderPath, $"{fileName}");
            storyData.Init(fileName);

            // 保存数据
            SaveDatas();

            // 记录打开信息
            EditorPrefs.SetString(keyLastStoryName, fileName);

            // 提示消息
            string message = $"故事已保存";
            ShowNotification(new GUIContent(message));
        }

        /// <summary>
        /// 打开故事
        /// </summary>
        private void OpenStory()
        {
            // 获取文件路径
            string filePath = EditorUtility.OpenFilePanel("打开故事", storyDatasFolderPath, "asset");

            // 检测是否为空
            if (string.IsNullOrEmpty(filePath))
            {
                return;
            }

            // 绝对路径转为相对路径
            filePath = FileUtil.GetProjectRelativePath(filePath);

            // 载入文件
            StoryDataSO story = IOUtility.LoadAsset<StoryDataSO>(filePath);

            // 检测是否为空
            if (story == null)
            {
                string temp = "故事不存在：\n\n" +
                    $"{filePath}\n\n" +
                    "请确保你选择了正确的文件。";
                EditorUtility.DisplayDialog("警告", temp, "明白");

                return;
            }

            string str = "确认打开新故事并覆盖当前视图内容吗？未保存的数据将无法恢复。";
            if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
            {
                storyData = story;
                RecordCurrentStory();

                graphView.ClearGraph();
                LoadDatas(storyData);

                // 提示消息
                string message = $"故事已打开";
                ShowNotification(new GUIContent(message));
            }
        }

        /// <summary>
        /// 打开上次编辑的故事
        /// </summary>
        private void OpenLastStory()
        {
            // 获取文件路径
            string storyName = EditorPrefs.GetString(keyLastStoryName);

            // 检测是否为空
            if (string.IsNullOrEmpty(storyName))
            {
                return;
            }

            // 载入文件
            StoryDataSO story = IOUtility.LoadAsset<StoryDataSO>(storyDatasFolderPath, storyName);

            // 检测是否为空
            string message;
            if (story == null)
            {
                message = $"未找到上次编辑的故事";
                ShowNotification(new GUIContent(message));
                return;
            }

            storyData = story;
            graphView.ClearGraph();
            LoadDatas(storyData);

            // 提示消息
            message = $"已打开上次编辑的故事";
            ShowNotification(new GUIContent(message));
        }

        /// <summary>
        /// 新建故事
        /// </summary>
        private void NewStory()
        {
            string str = "确认创建新故事吗？未保存的数据将无法恢复。";
            if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
            {
                graphView.ClearGraph();
                graphView.AddDefaultNodes();
                graphView.AddDefaultVarData();
                graphView.UpdateStoryInfo();
                UpdateFileName(defaultFileName);

                string message = $"故事已创建";
                ShowNotification(new GUIContent(message));
            }
        }

        /// <summary>
        /// 清空视图并添加默认数据
        /// </summary>
        private void ClearGraphAndCreateDefaultDatas()
        {
            string str = "确认清空当前视图内容吗？未保存的数据将无法恢复。";
            if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
            {
                graphView.ClearGraph();
                graphView.AddDefaultNodes();
                graphView.AddDefaultVarData();
                graphView.UpdateStoryInfo();

                string message = $"视图已清空";
                ShowNotification(new GUIContent(message));
            }
        }

        /// <summary>
        /// 切换小地图
        /// </summary>
        private void ToggleMiniMap()
        {
            graphView.ToggleMiniMap();
            // 切换按钮样式
            btnNavMap.ToggleInClassList("button__selected");
        }

        /// <summary>
        /// 切换变量面板
        /// </summary>
        private void ToggleVarPanel()
        {
            graphView.ToggleVarPanel();
            // 切换按钮样式
            btnVars.ToggleInClassList("button__selected");
        }

        #endregion


        #region 实用方法

        /// <summary>
        /// 保存数据
        /// </summary>
        private void SaveDatas()
        {
            SaveGroupDatas(graphView.Groups);
            SaveNodeDatas(graphView.Nodes);
            SaveVarDatas(graphView.Vars);
            SaveNoteDatas(graphView.Notes);

            // 写入硬盘
            IOUtility.SaveAsset(storyData);
        }

        /// <summary>
        /// 保存分组数据
        /// </summary>
        /// <param name="groups"></param>
        private void SaveGroupDatas(List<BaseGroup> groups)
        {
            // 遍历分组列表
            foreach (BaseGroup group in groups)
            {
                // 创建分组数据
                GroupData groupData = group.GetGroupData();
                // 加入列表
                storyData.GroupDatas.Add(groupData);
            }
        }

        /// <summary>
        /// 保存节点数据
        /// </summary>
        /// <param name="nodes">节点列表</param>
        private void SaveNodeDatas(List<BaseNode> nodes)
        {
            // 遍历节点列表
            foreach (BaseNode node in nodes)
            {
                // 创建节点数据
                NodeData nodeData = node.GetNodeData();
                // 加入列表
                storyData.NodeDatas.Add(nodeData);
            }
        }

        /// <summary>
        /// 保存变量数据
        /// </summary>
        /// <param name="vars">变量</param>
        private void SaveVarDatas(List<VarData> vars)
        {
            storyData.VarDatas = DataUtility.CloneVarDatas(vars);
        }

        /// <summary>
        /// 保存便签数据
        /// </summary>
        /// <param name="notes"></param>
        private void SaveNoteDatas(List<BaseNote> notes)
        {
            // 遍历列表
            foreach (BaseNote note in notes)
            {
                // 创建节点数据
                NoteData noteData = note.GetNoteData();
                // 加入列表
                storyData.NoteDatas.Add(noteData);
            }
        }

        /// <summary>
        /// 载入数据
        /// </summary>
        /// <param name="storyData">故事数据</param>
        private void LoadDatas(StoryDataSO storyData)
        {
            UpdateFileName(storyData.FileName);
            LoadVarDatas(storyData.VarDatas);
            Dictionary<string, BaseGroup> loadedGroups = LoadGroupDatas(storyData.GroupDatas);
            Dictionary<string, BaseNode> loadedNodes = LoadNodeDatas(storyData.NodeDatas, loadedGroups);
            LoadNodesConnections(loadedNodes);
            LoadNoteDatas(storyData.NoteDatas);
        }

        /// <summary>
        /// 载入分组数据
        /// </summary>
        /// <param name="groupDatas">分组数据列表</param>
        /// <returns>已载入分组字典</returns>
        private Dictionary<string, BaseGroup> LoadGroupDatas(List<GroupData> groupDatas)
        {
            Dictionary<string, BaseGroup> loadedGroups = new();

            // 遍历分组视图列表
            foreach (GroupData groupGraph in groupDatas)
            {
                BaseGroup group = graphView.CreateGroup(groupGraph.Title, groupGraph.Position);
                group.ID = groupGraph.GUID;

                loadedGroups.Add(group.ID, group);
            }

            graphView.UpdateGroupCountInfo();

            return loadedGroups;
        }

        /// <summary>
        /// 载入节点数据
        /// </summary>
        /// <param name="nodeDatas">节点数据列表</param>
        /// <param name="loadedGroups">已载入的分组</param>
        /// <returns>已载入节点列表</returns>
        private Dictionary<string, BaseNode> LoadNodeDatas(List<NodeData> nodeDatas, Dictionary<string, BaseGroup> loadedGroups)
        {
            Dictionary<string, BaseNode> loadedNodes = new();

            // 遍历节点视图列表
            foreach (NodeData nodeData in nodeDatas)
            {
                // 创建节点
                BaseNode node = graphView.CreateNode(nodeData.Title, nodeData.Type, nodeData.Position, null, false);
                node.GUID = nodeData.GUID;
                node.Note = nodeData.Note;
                node.ChoiceDatas = DataUtility.CloneChoiceDatas(nodeData.ChoiceDatas);

                // 检测节点类型并获取特定变量值
                if (node.Type == NodeType.Dialogue)
                {
                    DialogueNode dNode = node as DialogueNode;
                    dNode.RoleName = nodeData.RoleName;
                    dNode.Portrait = nodeData.Portrait;
                    dNode.SentenceDatas = DataUtility.CloneSentenceDatas(nodeData.SentenceDatas);
                }
                else if (node.Type == NodeType.BGI)
                {
                    BGINode bNode = node as BGINode;
                    bNode.BGI = nodeData.BGI;
                }
                else if (node.Type == NodeType.EditVar)
                {
                    EditVarNode evNode = node as EditVarNode;
                    evNode.EditVarDatas = DataUtility.CloneEditVarDatas(nodeData.EditVarDatas);
                }

                // 绘制节点
                node.Draw();

                // 获取所属分组分组
                BaseGroup group = null;
                if (!string.IsNullOrEmpty(nodeData.GroupID))
                {
                    group = loadedGroups[nodeData.GroupID];
                    group.AddElement(node);
                }

                // 记录该节点已被加载
                loadedNodes.Add(node.GUID, node);
            }

            graphView.UpdateNodeCountInfo();

            return loadedNodes;
        }

        /// <summary>
        /// 载入节点连线
        /// </summary>
        /// <param name="loadedNodes">已载入的节点</param>
        private void LoadNodesConnections(Dictionary<string, BaseNode> loadedNodes)
        {
            // 遍历所有已载入的节点
            foreach (KeyValuePair<string, BaseNode> loadedNode in loadedNodes)
            {
                // 遍历节点的所有输出端口
                foreach (Port outputPort in loadedNode.Value.outputContainer.Children())
                {
                    ChoiceData choiceData = (ChoiceData)outputPort.userData;

                    if (string.IsNullOrEmpty(choiceData.NextNodeID))
                    {
                        continue;
                    }

                    // 获取下个节点的输入端口
                    Port nextNodeInputPort = loadedNodes[choiceData.NextNodeID].Input;

                    // 创建连线
                    graphView.CreateEdge(outputPort, nextNodeInputPort);
                }

                // 刷新端口样式
                loadedNode.Value.RefreshPorts();
            }
        }

        /// <summary>
        /// 载入变量数据
        /// </summary>
        private void LoadVarDatas(List<VarData> oldVarDatas)
        {
            List<VarData> newVarDatas = DataUtility.CloneVarDatas(oldVarDatas);
            graphView.VarPanel.SetVarDatas(newVarDatas);
        }

        /// <summary>
        /// 载入便签数据
        /// </summary>
        /// <param name="stickyNoteDatas"></param>
        private void LoadNoteDatas(List<NoteData> stickyNoteDatas)
        {
            // 遍历便签数据列表
            foreach (NoteData stickyNote in stickyNoteDatas)
            {
                graphView.CreateNote(stickyNote.Title, stickyNote.Content, stickyNote.Layout);
            }

            graphView.UpdateNoteCountInfo();
        }

        /// <summary>
        /// 更新文件名
        /// </summary>
        /// <param name="newFileName"></param>
        private void UpdateFileName(string newFileName)
        {
            tfdFileName.value = newFileName;
        }

        /// <summary>
        /// 记录当前的故事
        /// </summary>
        private void RecordCurrentStory()
        {
            EditorPrefs.SetString(keyLastStoryName, storyData.FileName);
        }

        #endregion
    }
}