﻿using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Text.RegularExpressions;

[CustomEditor(typeof(T1MapMono))]
public class T1MapEditor : Editor
{
    public T1Map map;
    private string loadPath;
    public string LoadPath
    {
        get { return loadPath; }
        set
        {
            if (loadPath != value)
            {
                loadPath = value;
                EditorPrefs.SetString("T1.mapLoadPath", loadPath);
            }
        }
    }
    private string savePath;
    public string SavePath
    {
        get { return savePath; }
        set
        {
            if (savePath != value)
            {
                savePath = value;
                EditorPrefs.SetString("T1.mapSavePath", savePath);
            }
        }
    }
    private string mapName;
    public string MapName
    {
        get { return mapName; }
        set
        {
            if (mapName != value)
            {
                mapName = value;
                EditorPrefs.SetString("T1.mapName", mapName);
            }
        }
    }

    private SerializedProperty mapProp;
    private SerializedProperty worldProp;
    private SerializedProperty actors;
    private int tabIndex;
    private bool searchActorTypeIsOn;
    private bool searchActorTIDIsOn;
    private bool searchActorOriginIsOn;
    private int searchActorType;
    private int searchActorTID;
    private Vector3 searchActorOrigin;
    private int searchActorFoundIndex = -1;
    private bool meshSectorCeiling;
    private bool vertsOnly;
    private float meshSectorScale = 1.0f;
    private float meshSectorYaw;
    private GameObject meshGO;

    void OnEnable()
    {
        mapProp = serializedObject.FindProperty("map");
        worldProp = mapProp.FindPropertyRelative("worldInfo");
        actors = mapProp.FindPropertyRelative("actors");
        LoadPath = EditorPrefs.GetString("T1.mapLoadPath");
        SavePath = EditorPrefs.GetString("T1.mapSavePath");
        MapName = EditorPrefs.GetString("T1.mapName");
    }

    public void ShowCounts()
    {
        Debug.Log("Vertices: " + map.collisionVertices.Length);
        Debug.Log("SectorSets: " + map.collisionSectorSets.Length);
        Debug.Log("Sectors: " + map.collisionSectors.Length);
        Debug.Log("GridBounds Sections: " + map.gridBounds.sections.Length);
        int nStaticMeshes = 0;
        for (int i = 0; i < map.gridBounds.sections.Length; i++)
            nStaticMeshes += map.gridBounds.sections[i].staticMeshes.Length;
        Debug.Log("Static Meshes: " + nStaticMeshes);
        Debug.Log("Actors: " + map.actors.Length);
        Debug.Log("Visibility: " + map.visibility.Length + (map.visibility.Length > 0 ? (" staticmeshesLength: " + map.visibility[0].staticMeshes.Length) : ""));
    }

    public void CheckActorTIDS()
    {
        List<Vector2Int> tids = new List<Vector2Int>();
        for (int i = 0; i < map.actors.Length; i++)
        {
            if (map.actors[i].TID >= 0)
            {
                tids.Add(new Vector2Int(map.actors[i].TID, 1));
            }
        }
        for (int i = 0; i < map.collisionSectorSets.Length; i++)
        {
            if ((map.collisionSectorSets[i].flags & T1SectorSet.SectorSetFlags.EVENT) != 0)
            {
                int fi = tids.FindIndex(x => x.x == map.collisionSectorSets[i].arg4);
                if (fi >= 0)
                {
                    Vector2Int t = tids[fi];
                    t.y |= (1 << 1);
                    tids[fi] = t;
                }
                else
                {
                    tids.Add(new Vector2Int(map.collisionSectorSets[i].arg4, 2));
                }
            }
        }
        tids.Sort(delegate (Vector2Int x, Vector2Int y) { return x.x.CompareTo(y.x); });

        for (int i = 0; i < tids.Count; i++)
        {
            string s = "";
            if ((tids[i].y & (1 << 0)) != 0)
                s += " | Actor";
            if ((tids[i].y & (1 << 1)) != 0)
                s += " | Sector";
            Debug.Log(tids[i].x + s);
        }
    }

    public void CheckSecrets()
    {
        int secrets = 0;
        for (int i = 0; i < map.collisionSectorSets.Length; i++)
        {
            if ((map.collisionSectorSets[i].flags & T1SectorSet.SectorSetFlags.SECRET) != 0)
            {
                secrets++;
            }
        }
        Debug.Log("Secrets: " + secrets);
    }

    public void ListStaticMeshNames()
    {
        List<string> names = new List<string>();
        for (int i = 0; i < map.gridBounds.sections.Length; i++)
        {
            T1GridSection section = map.gridBounds.sections[i];
            for (int j = 0; j < section.staticMeshes.Length; j++)
            {
                if (!names.Contains(section.staticMeshes[j].modelPath))
                {
                    names.Add(section.staticMeshes[j].modelPath);
                }
            }
        }
        Debug.Log("" + names.Count + " different types of static meshes");
        for (int i = 0; i < names.Count; i++)
        {
            Debug.Log(names[i]);
        }
    }

    public override void OnInspectorGUI()
    {
        T1MapMono mb = target as T1MapMono;
        map = mb.map;

        serializedObject.Update();

        EditorGUILayout.Space();
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Load File"))
        {
            string path = EditorUtility.OpenFilePanel("Open T1 Map file", LoadPath, "map");
            if (!string.IsNullOrEmpty(path))
            {
                LoadPath = Path.GetDirectoryName(path);
                MapName = Path.GetFileNameWithoutExtension(path);
                map.Load(path);
            }
        }

        if (GUILayout.Button("Save File"))
        {
            string path = EditorUtility.SaveFilePanel("Save T1 Map file", SavePath, MapName, "map");
            if (!string.IsNullOrEmpty(path))
            {
                SavePath = Path.GetDirectoryName(path);
                MapName = Path.GetFileNameWithoutExtension(path);
                map.Save(path);
            }
        }
        GUILayout.EndHorizontal();

        MapName = EditorGUILayout.TextField("Filename", MapName);

        EditorGUILayout.Space();

        GUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("Reload"))
        {
            map.Load(LoadPath + "/" + mapName + ".map");
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        EditorGUI.BeginDisabledGroup(map.visibility.Length == 0);
        if (GUILayout.Button("Remove Vis"))
        {
            map.visibility = new T1Visibility[0];
        }
        EditorGUI.EndDisabledGroup();
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Remove Unused Verts"))
        {
            bool[] vertUsed = new bool[map.collisionVertices.Length];
            for (int i = 0; i < map.collisionSectors.Length; i++)
            {
                vertUsed[map.collisionSectors[i].indice1] = true;
                vertUsed[map.collisionSectors[i].indice2] = true;
                vertUsed[map.collisionSectors[i].indice3] = true;
            }

            Dictionary<int, int> vertLookup = new Dictionary<int, int>();
            int vertIndex = 0;
            for (int i = 0; i < vertUsed.Length; i++)
            {
                if (vertUsed[i])
                {
                    vertLookup.Add(i, vertIndex);
                    vertIndex++;
                }
            }

            //Remove unused verts
            int nRemoved = 0;
            for (int i = vertUsed.Length - 1; i >= 0; i--)
            {
                if (!vertUsed[i])
                {
					//Debug.Log("x: " + map.collisionVertices[i].x + " y: " + map.collisionVertices[i].y + " z: " + map.collisionVertices[i].z);
                    ArrayUtility.RemoveAt(ref map.collisionVertices, i);
                    nRemoved++;
                }
            }

            //update sector indices
            for (int i = 0; i < map.collisionSectors.Length; i++)
            {
                map.collisionSectors[i].indice1 = (short)vertLookup[map.collisionSectors[i].indice1];
                map.collisionSectors[i].indice2 = (short)vertLookup[map.collisionSectors[i].indice2];
                map.collisionSectors[i].indice3 = (short)vertLookup[map.collisionSectors[i].indice3];
            }

            Debug.Log("Removed " + nRemoved + " unused verts");
        }
        if (GUILayout.Button("StaticMesh Names"))
        {
            ListStaticMeshNames();
        }

        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Show Counts"))
        {
            ShowCounts();
        }
        if (GUILayout.Button("TIDs"))
        {
            CheckActorTIDS();
        }
        GUILayout.EndHorizontal();

        EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);

        tabIndex = GUILayout.SelectionGrid(tabIndex, new string[] { "Find Actor", "World", "Mesh to Sectors" }, 3);
        switch (tabIndex)
        {
            case 0: //find actor
            {
                EditorGUI.BeginChangeCheck();
                EditorGUIUtility.labelWidth = 60.0f;
                GUILayout.BeginHorizontal();
                searchActorTypeIsOn = EditorGUILayout.Toggle(searchActorTypeIsOn, GUILayout.Width(20));
                EditorGUI.BeginDisabledGroup(!searchActorTypeIsOn);
                searchActorType = EditorGUILayout.IntField("Type", searchActorType);
                EditorGUI.EndDisabledGroup();
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
                searchActorTIDIsOn = EditorGUILayout.Toggle(searchActorTIDIsOn, GUILayout.Width(20));
                EditorGUI.BeginDisabledGroup(!searchActorTIDIsOn);
                searchActorTID = EditorGUILayout.IntField("TID", searchActorTID);
                EditorGUI.EndDisabledGroup();
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
                searchActorOriginIsOn = EditorGUILayout.Toggle(searchActorOriginIsOn, GUILayout.Width(20));
                EditorGUI.BeginDisabledGroup(!searchActorOriginIsOn);
                searchActorOrigin = EditorGUILayout.Vector3Field("Origin", searchActorOrigin);
                EditorGUI.EndDisabledGroup();
                GUILayout.EndHorizontal();
                EditorGUIUtility.labelWidth = 0.0f;
                EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
                if (EditorGUI.EndChangeCheck())
                {
                    for (int i = 0; i < map.actors.Length; i++)
                    {
                        T1ActorInfo a = map.actors[i];
                        bool typeMatch = !searchActorTypeIsOn || (searchActorTypeIsOn && a.actorID == searchActorType);
                        bool tidMatch = !searchActorTIDIsOn || (searchActorTIDIsOn && a.TID == searchActorTID);
                        bool posMatch = !searchActorOriginIsOn || (searchActorOriginIsOn && Vector3.Distance(a.origin, searchActorOrigin) < 1.0f);

                        if (typeMatch && tidMatch && posMatch)
                        {
                            searchActorFoundIndex = i;
                            SerializedProperty spActor = actors.GetArrayElementAtIndex(searchActorFoundIndex);
                            spActor.isExpanded = true;
                            break;
                        }
                    }
                }

                if (searchActorFoundIndex >= 0 && searchActorFoundIndex < actors.arraySize)
                {
                    SerializedProperty spActor = actors.GetArrayElementAtIndex(searchActorFoundIndex);
                    bool pressDelete = GUILayout.Button("Delete this actor");
                    if (pressDelete)
                    {
                        if (EditorUtility.DisplayDialog("Confirm", "Are you sure you want to delete this actor?", "Yes", "No"))
                        {
                            //ArrayUtility.Clear(ref map.actors);
                            ArrayUtility.RemoveAt(ref map.actors, searchActorFoundIndex);
                            searchActorFoundIndex = -1;
                        }
                    }
                    else
                    {
                        EditorGUILayout.PropertyField(spActor, new GUIContent("Actor (Index " + searchActorFoundIndex + ")"), true);
                    }
                }
                else
                {
                    EditorGUILayout.LabelField("No Actor");
                }
                break;
            }
            case 1: //World
            {
                EditorGUILayout.PropertyField(worldProp, new GUIContent("World"), true);
                break;
            }
            case 2: //Mesh to Sectors
            {
                float t1scale = 1.0f; //2.855002f;
                meshGO = (GameObject)EditorGUILayout.ObjectField("GameObject", meshGO, typeof(GameObject), true);
                meshSectorCeiling = EditorGUILayout.Toggle("Set Ceiling", meshSectorCeiling);
                vertsOnly = EditorGUILayout.Toggle("Vertices Only", vertsOnly);
                meshSectorScale = EditorGUILayout.FloatField("Scale", meshSectorScale);
                meshSectorYaw = EditorGUILayout.FloatField("Yaw", meshSectorYaw);
                if (GUILayout.Button("Create Sectors"))
                {
                    Mesh mesh = meshGO.GetComponent<MeshFilter>().sharedMesh;
                    if (mesh != null)
                    {
                        float vertScale = meshSectorScale / t1scale;
                        int lastVertIndex = map.collisionVertices.Length;
                        int lastSectorSetIndex = map.collisionSectorSets.Length;
                        int lastSectorIndex = map.collisionSectors.Length;

                        if (!vertsOnly)
                        {
                            T1SectorSet sectorSet = new T1SectorSet();
                            sectorSet.fogZFar = 1536.0f;
                            sectorSet.fogStart = 1024.0f;
                            sectorSet.waterZFar = 1536.0f;
                            sectorSet.waterStart = 1024.0f;
                            sectorSet.waterColor = new Color32(0, 42, 190, 255);
                            sectorSet.blendLength = 1.0f;
                            ArrayUtility.Add(ref map.collisionSectorSets, sectorSet);

                            int[] meshTris = mesh.triangles;
                            for (int i = 0; i < meshTris.Length; i += 3)
                            {
                                T1Sector sector = new T1Sector();
                                sector.sectorSetIndex = (short)lastSectorSetIndex;
                                //flip the winding order here
                                sector.indice2 = (short)(lastVertIndex + meshTris[i + 0]);
                                sector.indice1 = (short)(lastVertIndex + meshTris[i + 1]);
                                sector.indice3 = (short)(lastVertIndex + meshTris[i + 2]);
                                sector.edgeLink1 = -1;
                                sector.edgeLink2 = -1;
                                sector.edgeLink3 = -1;
                                ArrayUtility.Add(ref map.collisionSectors, sector);
                            }
                        }

                        Vector3[] meshVerts = mesh.vertices;
                        for (int i = 0; i < meshVerts.Length; i++)
                        {
                            Vector3 vert = new Vector3(meshVerts[i].x, meshVerts[i].z, meshVerts[i].y) * vertScale;
                            ArrayUtility.Add(ref map.collisionVertices, vert);
                        }

                        if (!vertsOnly)
                        {
                            //set the sector edgelinks
                            for (int i = lastSectorIndex; i < map.collisionSectors.Length; i++)
                            {
                                int oi1 = map.collisionSectors[i].indice1;
                                int oi2 = map.collisionSectors[i].indice2;
                                int oi3 = map.collisionSectors[i].indice3;
                                for (int j = 0; j < map.collisionSectors.Length; j++)
                                {
                                    if (i == j)
                                        continue;
                                    int i1 = map.collisionSectors[j].indice1;
                                    int i2 = map.collisionSectors[j].indice2;
                                    int i3 = map.collisionSectors[j].indice3;
                                    //edgelink1 3-1
                                    if (((oi1 == i1 && oi3 == i2) || oi1 == i2 && oi3 == i1) || ((oi1 == i1 && oi3 == i3) || (oi1 == i3 && oi3 == i1)) || ((oi1 == i2 && oi3 == i3) || (oi1 == i3 && oi3 == i2)))
                                        map.collisionSectors[i].edgeLink1 = (short)j;
                                    //edgelink2 1-2
                                    else if (((oi1 == i1 && oi2 == i2) || oi1 == i2 && oi2 == i1) || ((oi1 == i1 && oi2 == i3) || (oi1 == i3 && oi2 == i1)) || ((oi1 == i2 && oi2 == i3) || (oi1 == i3 && oi2 == i2)))
                                        map.collisionSectors[i].edgeLink2 = (short)j;
                                    //edgelink3 2-3
                                    else if (((oi3 == i1 && oi2 == i2) || oi3 == i2 && oi2 == i1) || ((oi3 == i1 && oi2 == i3) || (oi3 == i3 && oi2 == i1)) || ((oi3 == i2 && oi2 == i3) || (oi3 == i3 && oi2 == i2)))
                                        map.collisionSectors[i].edgeLink3 = (short)j;
                                }
                            }
                        }

                        //set ceiling vert heights with ray casts
                        if (meshSectorCeiling)
                        {
                            for (int i = 0; i < map.collisionVertices.Length; i++)
                            {
                                RaycastHit rayHit;
                                Vector3 vert = new Vector3(map.collisionVertices[i].x, map.collisionVertices[i].z, map.collisionVertices[i].y) / vertScale;
                                if (Physics.Raycast(vert, Vector3.up, out rayHit, Mathf.Infinity))
                                {
                                    map.collisionVertices[i].w = rayHit.point.y * vertScale;
                                }
                            }
                        }

                        Vector3 center = new Vector3(0.0f, 0.0f, 0.0f);
                        Quaternion rotation = new Quaternion();
                        rotation.eulerAngles = new Vector3(0.0f, 0.0f, meshSectorYaw);
                        for (int i = 0; i < map.collisionVertices.Length; i++)
                        {
                            Vector3 vert = new Vector3(map.collisionVertices[i].x, map.collisionVertices[i].y, map.collisionVertices[i].z);
                            vert = rotation * (vert - center) + center;
                            map.collisionVertices[i].Set(vert.x, vert.y, vert.z, map.collisionVertices[i].w);
                        }
                    }
                }
                break;
            }
        }

        //EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);

        //EditorGUILayout.PropertyField(mapProp, new GUIContent("Map"), true);

        serializedObject.ApplyModifiedProperties();
    }
}
