// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR;
using VIVE.OpenXR;
using VIVE.OpenXR.PlaneDetection;
using VIVE.OpenXR.Toolkits.PlaneDetection;

public class PlaneDetectionTestHandle : MonoBehaviour
{
	[SerializeField, Tooltip("Used on mesh generated by extent")]
	private Material extentMat;
	[SerializeField, Tooltip("Used on mesh generated by vertices")]
	private Material planeMat;

	public Button detectPlaneButton;
	public Button clearObjectsButton;

	public TextMeshProUGUI statusOrigin;

    public XRInputSubsystem xrInputSubsystem;

	public Transform root;

    void GetXRInputSubsystem()
    {
        List<XRInputSubsystem> xrSubsystemList = new List<XRInputSubsystem>();
        SubsystemManager.GetInstances(xrSubsystemList);
        foreach (var xrSubsystem in xrSubsystemList)
        {
            if (xrSubsystem.running)
            {
                xrInputSubsystem = xrSubsystem;
                break;
            }
        }
    }


    IEnumerator Start()
    {
        if (!PlaneDetectionManager.IsSupported()) yield break;

		detectPlaneButton.onClick.AddListener(OnDetectPlane);
		clearObjectsButton.onClick.AddListener(OnClearObjects);

		// AutoRun after two second
		Invoke("OnDetectPlane", 2);

        float t = Time.unscaledTime;
        while (xrInputSubsystem == null)
        {
            yield return null;
            GetXRInputSubsystem();
            if (Time.unscaledTime - t > 5)
            {
                Debug.LogError("Get XRInputSubsystem timeout");
                statusOrigin.text = "Get XRInputSubsystem timeout";
                yield break;
            }
        }
    }

    float time = 0;

	public IEnumerator GetAllPlanes()
	{
		if (!PlaneDetectionManager.IsSupported())
		{
			detectPlaneButton.interactable = true;
			yield break;
		}

		var pd = PlaneDetectionManager.CreatePlaneDetector();
		if (pd == null)
		{
			detectPlaneButton.interactable = true;
			yield break;
		}

		pd.BeginPlaneDetection();
		yield return null;

		// Check State
		var state = pd.GetPlaneDetectionState();
		bool isDone = false;
		time = 0;
		while (isDone)
		{
			switch (state)
			{
				case VivePlaneDetection.XrPlaneDetectionStateEXT.DONE_EXT:
					Debug.Log("GetAllPlanes() state: " + state);
					isDone = true;
					break;
				case VivePlaneDetection.XrPlaneDetectionStateEXT.PENDING_EXT:
					if (time + 0.5f > Time.unscaledTime)
					{
						time = Time.unscaledTime;
						Debug.Log("GetAllPlanes() state: " + state);
					}
					yield return null;
					continue;
				case VivePlaneDetection.XrPlaneDetectionStateEXT.NONE_EXT:
				case VivePlaneDetection.XrPlaneDetectionStateEXT.FATAL_EXT:
				case VivePlaneDetection.XrPlaneDetectionStateEXT.ERROR_EXT:
					Debug.Log("GetAllPlanes() state: " + state);
                    PlaneDetectionManager.DestroyPlaneDetector(pd);
					detectPlaneButton.interactable = true;
					yield break;
			}
			yield return null;
			state = pd.GetPlaneDetectionState();
		}

		List<PlaneDetectorLocation> locations;
		if (pd.GetPlaneDetections(out locations) != XrResult.XR_SUCCESS)
		{
			detectPlaneButton.interactable = true;
			yield break;
		}

		foreach (var location in locations)
		{
			Debug.Log("GetAllPlanes() location.planeId: " + location.planeId);
			Debug.Log("GetAllPlanes() location.locationFlags: " + location.locationFlags);
			Debug.Log("GetAllPlanes() location.pose: " + location.pose);
			Debug.Log("GetAllPlanes() location.pose.rotation.eulerAngles: " + location.pose.rotation.eulerAngles);
			Debug.Log("GetAllPlanes() location.scale: " + location.size);
			Debug.Log("GetAllPlanes() location.orientation: " + location.orientation);
			Debug.Log("GetAllPlanes() location.semanticType: " + location.semanticType);
			Debug.Log("GetAllPlanes() location.polygonBufferCount: " + location.polygonBufferCount);
			var plane = pd.GetPlane(location.planeId);
			if (plane == null)
				continue;

			var obj = CreatePlane(location, plane, extentMat, planeMat);
			if (root != null)
				obj.transform.SetParent(root, false);
			existPlanes.Add(obj);
			Debug.Log("GetAllPlanes() plane.scale: " + plane.scale);
			Debug.Log("GetAllPlanes() plane.center: " + plane.center);
		}

        PlaneDetectionManager.DestroyPlaneDetector(pd);
		detectPlaneButton.interactable = true;
	}

	List<GameObject> existPlanes = new List<GameObject>();

	static GameObject CreatePlane(PlaneDetectorLocation location, VIVE.OpenXR.Toolkits.PlaneDetection.Plane plane, Material extentMat, Material planeMat)
	{
		GameObject objRoot = new GameObject("Plane" + location.planeId);
		objRoot.transform.localPosition = location.pose.position;
		objRoot.transform.localRotation = location.pose.rotation;
		GameObject obj = new GameObject("BySize");
		obj.transform.SetParent(objRoot.transform, false);
		obj.AddComponent<MeshFilter>().mesh = CreatePlane(location.size);
		obj.AddComponent<MeshRenderer>().material = extentMat;

		obj = new GameObject("ByPlane");
		obj.transform.SetParent(objRoot.transform, false);
		obj.AddComponent<MeshFilter>().mesh = CreatePlane(plane);
		obj.AddComponent<MeshRenderer>().material = planeMat;
		return objRoot;
	}

	static Mesh CreatePlane(VIVE.OpenXR.Toolkits.PlaneDetection.Plane plane)
	{
		Mesh mesh = new Mesh();
		mesh.name = "PlaneMesh";
		mesh.vertices = plane.verticesGenerated;
		mesh.triangles = plane.indicesGenerated;
		mesh.uv = plane.uvsGenerated;
		mesh.RecalculateNormals();
		mesh.RecalculateTangents();
		mesh.RecalculateBounds();

		return mesh;
	}

	static Mesh CreatePlane(Vector3 size)
	{
		Mesh mesh = new Mesh();
		mesh.name = "PlaneMesh";
		var vertices = new Vector3[4];
		float hx = size.x / 2;
		float hy = size.y / 2;
		//  3---2
		//  | \ |   // This plane is facing +Z.
		//  1---0
		vertices[0] = new Vector3(-hx, -hy, 0);
		vertices[1] = new Vector3( hx, -hy, 0);
		vertices[2] = new Vector3(-hx,  hy, 0);
		vertices[3] = new Vector3( hx,  hy, 0);
		mesh.vertices = vertices;
		mesh.triangles = new int[] { 0, 3, 2, 0, 1, 3 };
		mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1), };
		mesh.RecalculateNormals();
		mesh.RecalculateTangents();
		mesh.RecalculateBounds();

		return mesh;
	}

	public bool CheckSupport()
	{
		if (!PlaneDetectionManager.IsSupported())
		{
			throw new NotSupportedException("PlaneDetection is not supported.");
		}
		return true;
	}

    public void OnDetectPlane()
	{
		Debug.Log("OnDetectPlane()");
		OnClearObjects();
		CheckSupport();
        detectPlaneButton.interactable = false;
		StartCoroutine(GetAllPlanes());
	}

	public void OnClearObjects()
	{
		Debug.Log("OnClearObjects()");
        CheckSupport();
        existPlanes.ForEach((obj) => Destroy(obj));
	}

    public void OnFloor()
    {
        if (xrInputSubsystem == null)
        {
            Debug.LogError("xrInputSubsystem is null");
            statusOrigin.text = "xrInputSubsystem is null";
            return;
        }


        if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Floor))
        {
            statusOrigin.text = "Set tracking origin to floor. Reset Objs";
			OnClearObjects();
        }
        else
        {
            statusOrigin.text = "Fail to set tracking origin to floor";
        }
    }

    public void OnDevice()
    {
        if (xrInputSubsystem == null)
        {
            Debug.LogError("xrInputSubsystem is null");
            statusOrigin.text = "xrInputSubsystem is null";
            return;
        }

        if (xrInputSubsystem.TrySetTrackingOriginMode(TrackingOriginModeFlags.Device))
        {
            statusOrigin.text = "Set tracking origin to device. Reset Objs";
            OnClearObjects();
        }
        else
        {
            statusOrigin.text = "Fail to set tracking origin to device";
        }
    }
}
