using System.Collections.Generic;
using System.IO;
using System.Text;
using Games.Scene;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

public class ServerPathNodeGenerator : EditorWindow
{
    private MaterialPropertyBlock _materialProperty;
    private const float _defaultLinkLength = 6f;

    private float linkLengthSqr
    {
        get { return _linkLength > 0 ? _linkLength * _linkLength : float.PositiveInfinity; }
    }

    private const string _scenePathNodeDir = "/../../Public/ServerRun/Scene/";
    private const string _line2Text = "#MAX_ID=4999;MAX_RECORD=5000;TableType=Hash;";
    private const string _distanceKey = "ServerPathNodeIDist";
    private const string _sphereRootName = "ServerPathNode";
    private const float _navMeshOffsetMax = 1f;
    private const float _navMeshSampleDist = 2f;

    private Scene _currentScene;
    private List<MyTuple<Transform, Transform>> _links;

    private Vector2 _pointPos;
    private Vector2 _scrollViewPoint;
    private bool _pointError;
    private FloatSetting _linkLength;
    private Material _material;
    private Transform _root;

    [MenuItem("Scene/生成服务器寻路点")]
    public static void ShowEditorWindow()
    {
        GetWindow<ServerPathNodeGenerator>();
    }

    private void Awake()
    {
        SceneManager.sceneUnloaded += OnSceneUnloaded;
        SceneView.onSceneGUIDelegate += OnSceneGui;
        _linkLength = new FloatSetting(_distanceKey, _defaultLinkLength);
        _material = new Material(Shader.Find("Unlit/Color"));
        _materialProperty = new MaterialPropertyBlock();
        OnSceneChange();
    }

    private void OnDestroy()
    {
        if (_material != null)
            DestroyImmediate(_material);
        if (_root != null)
            DestroyImmediate(_root.gameObject);
        SceneManager.sceneUnloaded -= OnSceneUnloaded;
        SceneView.onSceneGUIDelegate -= OnSceneGui;
    }

    private void OnGUI()
    {
        // Add Points
        _pointPos = EditorGUILayout.Vector2Field("点位置:", _pointPos, GUILayout.Width(200f));
        var linkLength = EditorGUILayout.FloatField("最大连接距离", _linkLength);
        if (linkLength != _linkLength)
            _linkLength.Set(linkLength);

        // Add Point
        GUILayout.Space(20f);
        GUILayout.Label("添加点位");
        if (GUILayout.Button("添加 于指定位置", GUILayout.Width(100f)))
        {
            if (_root != null)
            {
                var point = _pointPos.InsertY();
                CreateSphereAt(_root.childCount, point);
            }
        }
        
        GUILayout.Space(20f);
        if (GUILayout.Button("整理路径点"))
        {
            if (_root != null)
            {
                var sphereList = GetSphereList();
                var posList = new Vector3[sphereList.Count];
                for (var i = 0; i < posList.Length; i++)
                {
                    posList[i] = sphereList[i].position;
                    DestroyImmediate(sphereList[i].gameObject);
                }
                for (var i = 0; i < posList.Length; i++)
                {
                    CreateSphereAt(i, posList[i]);
                }
            }
        }
        
        GUILayout.Space(20f);
        if (_pointError)
            EditorGUILayout.HelpBox("上次生成时,有路径点不在NavMesh上!", MessageType.Error);
        else
            GUILayout.Label("生成和保存Txt");
        if (GUILayout.Button("预览链接"))
        {
            _pointError = CheckAllPointPos();
            if (!_pointError)
                BuildLinks();
        }

        GUILayout.Space(5f);
        if (GUILayout.Button("保存为Txt"))
        {
            _pointError = CheckAllPointPos();
            if (!_pointError)
            {
                BuildLinks();
                WriteFileRecord();
            }
        }
    }

    private void OnSceneGui(SceneView sceneView)
    {
        Handles.color = Color.magenta;
        for (var i = 0; i < _links.Count; i++)
        {
            var source = _links[i].first;
            var target = _links[i].second;
            if (source != null && target != null)
                Handles.DrawLine(source.position, target.position);
        }
    }

    private void OnSceneUnloaded(Scene scene)
    {
        if (scene.buildIndex == _currentScene.buildIndex)
            OnSceneChange();
    }

    private void OnSceneChange()
    {
        _links = new List<MyTuple<Transform, Transform>>();
        _currentScene = SceneManager.GetActiveScene();
        ReadFileRecord();
    }

    private string GetFileName()
    {
        return Application.dataPath + _scenePathNodeDir + _currentScene.name + "RoadPath.txt";
    }

    private void ReadFileRecord()
    {
        var rootObj = GameObject.Find(_sphereRootName);
        if (rootObj != null)
            DestroyImmediate(rootObj);
        rootObj = new GameObject(_sphereRootName);
        _root = rootObj.transform;
        var fileName = GetFileName();
        if (File.Exists(fileName))
        {
            var lines = File.ReadAllLines(fileName);
            if (lines.Length > 4)
            {
                var pathNodeList = new List<PathNodeData>();
                var id = 0;
                for (var i = 4; i < lines.Length; i++)
                {
                    var lineSuccess = false;
                    var segments = lines[i].Split('\t');
                    if (segments.Length > 3)
                    {
                        int index;
                        int posX;
                        int posZ;
                        if (int.TryParse(segments[0], out index) &&
                            int.TryParse(segments[2], out posX) &&
                            int.TryParse(segments[3], out posZ))
                        {
                            var point = new Vector3(posX * 0.01f, 0f, posZ * 0.01f);
                            var sphere = CreateSphereAt(id, point);
                            MarkPointPosition(sphere);
                            var pathNode = new PathNodeData() { index = index, sphere = sphere, linkTo = new List<int>()};
                            pathNodeList.Add(pathNode);
                            for (var j = 4; j < segments.Length; j++)
                            {
                                int linkTo;
                                if (int.TryParse(segments[j], out linkTo) && linkTo > -1)
                                    pathNode.linkTo.Add(linkTo);
                                else
                                    break;
                            }

                            lineSuccess = true;
                        }
                        id++;
                    }
                    if (!lineSuccess)
                        Debug.LogError(string.Format("第{0}行数据读取失败,已经跳过!", i + 1));
                }
                // 重新构造链接
                for (var i = 0; i < pathNodeList.Count; i++)
                {
                    var source = pathNodeList[i];
                    for (var j = 0; j < source.linkTo.Count; j++)
                    {
                        var target = pathNodeList.Find(a => a.index == source.linkTo[j]);
                        if (target != null)
                        {
                            if (_links.Find(a => a.first == source.sphere && a.second == target.sphere) == null &&
                                _links.Find(a => a.first == target.sphere && a.second == source.sphere) == null)
                            {
                                _links.Add(new MyTuple<Transform, Transform>(source.sphere, target.sphere));
                            }
                        }
                    }
                }
            }
        }
    }

    private void WriteFileRecord()
    {
        var sphereList = GetSphereList();
        var maxNodeCount = 0;
        for (var i = 0; i < sphereList.Count; i++)
        {
            var sphere = sphereList[i];
            var nodeCount = 0;
            for (var j = 0; j < _links.Count; j++)
            {
                var link = _links[j];
                if (link.first != null && link.second != null &&
                    (link.first == sphere || link.second == sphere))
                    nodeCount++;
            }

            maxNodeCount = Mathf.Max(maxNodeCount, nodeCount);
        }

        var builder = new StringBuilder();
        // Line 0
        builder.Append("Id");
        builder.Append('\t');
        builder.Append("Desc");
        builder.Append('\t');
        builder.Append("PosX");
        builder.Append('\t');
        builder.Append("PosZ");
        for (var i = 0; i < maxNodeCount; i++)
        {
            builder.Append('\t');
            builder.Append("Node");
            builder.Append(i + 1);
        }

        // Line 1
        builder.Append('\r');
        builder.Append("INT");
        builder.Append('\t');
        builder.Append("STRING");
        builder.Append('\t');
        builder.Append("INT");
        builder.Append('\t');
        builder.Append("INT");
        for (var i = 0; i < maxNodeCount; i++)
        {
            builder.Append('\t');
            builder.Append("INT");
        }

        // Line 2
        builder.Append('\r');
        builder.Append(_line2Text);

        // Line 3
        builder.Append('\r');
        builder.Append("#点id");
        builder.Append('\t');
        builder.Append("程序不读");
        builder.Append('\t');
        builder.Append("点位置x,100倍");
        builder.Append('\t');
        builder.Append("点位置y,100倍");
        for (var i = 0; i < maxNodeCount; i++)
        {
            builder.Append('\t');
            builder.Append("可链接的节点");
            builder.Append(i + 1);
        }

        // Data Lines
        for (var i = 0; i < sphereList.Count; i++)
        {
            var source = sphereList[i];
            builder.Append('\r');
            builder.Append(i);
            builder.Append('\t');
            builder.Append("坐标");
            builder.Append(i);
            builder.Append('\t');
            builder.Append(Mathf.RoundToInt(sphereList[i].position.x * 100f));
            builder.Append('\t');
            builder.Append(Mathf.RoundToInt(sphereList[i].position.z * 100f));
            var nodePos = 0;
            for (var j = 0; j < sphereList.Count; j++)
            {
                var target = sphereList[j];
                if (_links.Find(a =>
                        a.first == source && a.second == target || a.first == target && a.second == source) != null)
                {
                    builder.Append('\t');
                    builder.Append(j);
                    nodePos++;
                }
            }

            for (var j = nodePos; j < maxNodeCount; j++)
            {
                builder.Append('\t');
                builder.Append(-1);
            }
        }
        // Extra Line at End
        builder.Append('\r');

        var fileName = GetFileName();
        var directory = Path.GetDirectoryName(fileName);
        if (string.IsNullOrEmpty(directory))
            Debug.LogError("文件路径错误 " + fileName);
        else
        {
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);
            var bytes = Encoding.Default.GetBytes(builder.ToString());
            File.WriteAllBytes(fileName, bytes);
        }
    }

    private bool CheckAllPointPos()
    {
        var result = false;
        var sphereList = GetSphereList();
        for (var i = 0; i < sphereList.Count; i++)
            if (!MarkPointPosition(sphereList[i]))
                result = true;
        return result;
    }

    private bool MarkPointPosition(Transform sphere)
    {
        var source = sphere.transform.position;
        source.x = Mathf.Floor(source.x * 100f) * 0.01f;
        source.z = Mathf.Floor(source.z * 100f) * 0.01f;
        source.y = 100f;
        var ray = new Ray(source, Vector3.down);
        RaycastHit rayHit;
        if (Physics.Raycast(ray, out rayHit, 200f, ActiveScene.terrainLayMask))
            source = rayHit.point;
        else
            source.y = 0f;
        NavMeshHit navHit;
        var result = NavMesh.SamplePosition(source, out navHit, _navMeshSampleDist, NavMesh.AllAreas);
        if (result)
        {
            if ((source.RemoveY() - navHit.position.RemoveY()).sqrMagnitude > _navMeshOffsetMax.ToSquare())
                result = false;
            else
                source = navHit.position;
        }
        sphere.position = source;
        var sphereRenderer = sphere.GetComponent<Renderer>();
        if (sphereRenderer != null)
        {
            sphereRenderer.GetPropertyBlock(_materialProperty);
            _materialProperty.SetColor("_Color", result ? Color.green : Color.red);
            sphereRenderer.SetPropertyBlock(_materialProperty);
        }

        return result;
    }

    private Transform CreateSphereAt(int index, Vector3 pos)
    {
        var sphereObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        sphereObj.name = "Point_" + index;
        var sphereCol = sphereObj.GetComponent<Collider>();
        if (sphereCol)
            DestroyImmediate(sphereCol);
        var sphere = sphereObj.transform;
        sphere.SetParent(_root);
        sphere.position = pos;
        MarkPointPosition(sphere);
        return sphere;
    }

    private List<Transform> GetSphereList()
    {
        var result = new List<Transform>();
        if (_root != null)
        {
            foreach (Transform child in _root)
                result.Add(child);
        }

        return result;
    }

    private void BuildLinks()
    {
        _links.Clear();
        var sphereList = GetSphereList();
        for (var i = 1; i < sphereList.Count; i++)
        {
            for (var j = 0; j < i; j++)
            {
                var source = sphereList[i].position;
                var target = sphereList[j].position;
                if ((source.RemoveY() - target.RemoveY()).sqrMagnitude < linkLengthSqr)
                {
                    NavMeshHit hit;
                    if (!NavMesh.Raycast(source, target, out hit, NavMesh.AllAreas))
                        _links.Add(new MyTuple<Transform, Transform>(sphereList[i], sphereList[j]));
                }
            }
        }
    }
    /// <summary>
    /// 初始化时,临时使用的数据
    /// </summary>
    private class PathNodeData
    {
        public int index;
        public List<int> linkTo;
        public Transform sphere;
    }
}