using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
using UnityEngine.UI;
using System.Linq;
using UnityEngine.Events;

namespace SimplifyXR
{

    public enum ObjectAnchorType
    {
        manualPlacement,
        autoPlacement
    }

    public enum ObjectAnchorModelType
    {
        gameObject,
        mesh
    }

    public enum ObjectAnchorPromptDefault
    {
        confirm,
        deny
    }

    public enum ManualPlacementBehavior
    {
        MRTK_TapToPlace,
        PointPlace
    }


    public class ObjectAnchorPanelFeatures : BasePanelFeatures
    {

        [Header("Object Anchor Panel Features")]
        [Tooltip("Customize the behavior of the Object Anchor Panel features.")]
        public bool customizeObjectAnchorPanelFeatures;


        public ObjectAnchorType objectAnchorType;
        public GameObject anchorGameObject;
        public ObjectAnchorModelType anchorModelType;
        public Object anchorObjectModel;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Conditional("objectAnchorType", ObjectAnchorType.manualPlacement, ComparisonType.Equals),
        Tooltip("Configure the placement behavior type of your manual placement")]
        public ManualPlacementBehavior manualPlacementBehavior;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Automatically create a GameObject to use as your anchor")]
        public bool autoAnchorObject = false;
       
       /* [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Start object anchor placement and alignment control flow when panel is displayed")]
        public bool startObjectPlacementOnDisplay = true;*/

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Require a prompt to confirm alignment of Object Anchor after placing")]
        public bool promptAlignmentConfirmation = true;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Require a prompt for the user to keep their Object Anchor Model after alignment confirmation")]
        public bool promptObjectAnchorRetainModel = true;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Conditional("promptObjectAnchorRetainModel", false, ComparisonType.Equals),
        Tooltip("If no prompt for reference model is given, keep or remove object anchor model")]
        public ObjectAnchorPromptDefault defaulObjectAnchorRetainModelOption = ObjectAnchorPromptDefault.confirm;


        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Apply a holographic effect to on your object anchor model")]
        public bool holographicObjectAnchorModel = true;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Conditional("holographicObjectAnchorModel", true, ComparisonType.Equals),
        Tooltip("When keeping your model, the option to keep holographic effect, or to revert and reapply the previous materials/shaders of your mesh")]
        public bool keepHolographicObjectAnchorModel = true;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Tooltip("Enables anchor placement through a Place voice command.")]
        public bool enableVoiceCommandPlacement = true;


        /*
         [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
         Tooltip("Automatically parent all other GameObject in hierarchy that are not in exclude list")]
         public bool autoParent = false;

         [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
        Conditional("autoParent", true, ComparisonType.Equals),
           Tooltip("List of gameobject to not automatically parent to anchor object")]
         public List<GameObject> anchorExclude;

         [Conditional("autoParent", true, ComparisonType.Equals),
         Tooltip("Add any GameObjects generated at runtime to ObjectAnchor as children")]
         public bool parentRuntimeGameObjects = false;

        [Conditional("anchorModelType", ObjectAnchorModelType.gameObject, ComparisonType.Equals),
            Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
         Tooltip("Disable scripts/components of anchor model during alignment")]
        public bool disableAnchorModelBehavior = false;

        [Conditional("customizeObjectAnchorPanelFeatures", true, ComparisonType.Equals),
            Conditional("autoAnchorObject", false, ComparisonType.Equals),
         Tooltip("Automatically parent all other GameObject in hierarchy that are not in exclude list")]
        public bool disableAnchorObjectBehavior = false;
        */


        Button placementConfirmButton => baseRoot.GetElementAsType<Button>("Alignment Confirm Button");
        Button placementResetButton => baseRoot.GetElementAsType<Button>("Alignment Reset Button");
        Button retainModelButton => baseRoot.GetElementAsType<Button>("Retain Model Button");
        Button removeModelButton => baseRoot.GetElementAsType<Button>("Remove Model Button");
        GameObject photoPositionDisplay => baseRoot.GetElementAsType<GameObject>("Photo Position Panel");
        GameObject processingDisplay => baseRoot.GetElementAsType<GameObject>("Processing");
        GameObject alignmentConfirmDisplay => baseRoot.GetElementAsType<GameObject>("Alignment Confirm Panel");
        GameObject startplacementDisplay => baseRoot.GetElementAsType<GameObject>("Start Placement");
        GameObject moveConfirmDisplay => baseRoot.GetElementAsType<GameObject>("Move Confirm Panel");
        GameObject retainModelConfirmDisplay => baseRoot.GetElementAsType<GameObject>("Retain Model Confirm Panel");


        public UnityEvent OnObjectAnchorPanelDisplayed;
        public UnityEvent ObjectAnchorPlacementStarted;
        public UnityEvent ObjectAnchorPlaced;
        public UnityEvent ObjectAnchorAlignmentConfirmed;
        public UnityEvent ObjectAnchorModelRetained;
        public UnityEvent ObjectAnchorModelRemoved;


        public IObjectAnchorAlignment objectAnchorAlignment;

        private int anchorStateIndex = 0;
        private List<GameObject> anchorStateList = new List<GameObject>();

        private List<Renderer> anchorObjectMeshRenderers = new List<Renderer>();
        private List<Material[]> anchorObjectMeshMaterials = new List<Material[]>();

        private GameObject anchorModelInstance;

        private List<Transform> objectAnchorChildren = new List<Transform>();
        private List<Collider> anchorChildrenColliders = new List<Collider>();

        private ISpeechHandlerMapping speechHandler;

        public new void Awake()
        {
            base.Awake();

    
            if (promptAlignmentConfirmation)
            {
                placementConfirmButton.onClick.AddListener(() => OnAlignmentConfirm(true));
                placementResetButton.onClick.AddListener(() => OnAlignmentConfirm(false));
            }
            if (promptObjectAnchorRetainModel)
            {
                retainModelButton.onClick.AddListener(() => OnRetainModelConfirm(true));
                removeModelButton.onClick.AddListener(() => OnRetainModelConfirm(false));
            }

            speechHandler = GetComponent<ISpeechHandlerMapping>();
            if (speechHandler == null && enableVoiceCommandPlacement)
            {
                Debug.LogError("Cannot enable voice commands as there is no valid handler");
                enableVoiceCommandPlacement = false;
            }
          
            SetupPanelDisplay();
        }

        private void SetupPanelDisplay()
        {
            switch (objectAnchorType)
            {
                case ObjectAnchorType.manualPlacement:

                    anchorStateList.Add(startplacementDisplay);
                    anchorStateList.Add(alignmentConfirmDisplay);
                    anchorStateList.Add(retainModelConfirmDisplay);

                    break;

                case ObjectAnchorType.autoPlacement:
                
                    break;

            }
                    anchorStateIndex = 0;
        }

        private void GoToNextDisplay()
        {
            if (anchorStateIndex < anchorStateList.Count - 1)
                anchorStateIndex++;
            GoToDisplay(anchorStateIndex);
        }

        private void GoToPreviousDisplay()
        {
            if (anchorStateIndex > 0)
                anchorStateIndex--;
            GoToDisplay(anchorStateIndex);
        }

        private void GoToDisplay(int index, bool setRestInactive = true)
        {
            if (index > anchorStateList.Count || index < 0)
            {
                Debug.LogError("Attempt to access display with out of range index: " + index);
                return;
            }

            if (setRestInactive)
            {
                foreach (GameObject go in anchorStateList)
                {
                    go.SetActive(false);
                }
            }

            anchorStateList[index].SetActive(true);
        }

        public void DisplayObjectAnchorPanel()
        {
            gameObject.SetActive(false);
            gameObject.SetActive(true);
            OnObjectAnchorPanelDisplayed.Invoke();
        }

        public void OnEnable()
        {
            SetupAnchorObject();

        }

        private void OnDisable()
        {
            AttachAnchorChildren();
            RemoveAlignment();
        }

        private void RemoveAlignment()
        {
            if (enableVoiceCommandPlacement)
                speechHandler.UnregisterHandler("Place", HandlePlaceCommand);

            switch (objectAnchorType)
            {

                case ObjectAnchorType.manualPlacement:

                    Destroy(anchorGameObject.GetComponent<ManualObjectAnchorAlignment>());
                    break;
            }
        }

        private void ResetAlignment()
        {
            objectAnchorAlignment.StopAlignment();

            anchorStateIndex = 0;
            GoToDisplay(anchorStateIndex);

            if (enableVoiceCommandPlacement)
                speechHandler.RegisterHandler("Place", HandlePlaceCommand);

            objectAnchorAlignment.StartAlignment();
        }

        public new void OnDestroy()
        {
            base.OnDestroy();

            placementConfirmButton.onClick.RemoveAllListeners();
            placementResetButton.onClick.RemoveAllListeners();
            retainModelButton.onClick.RemoveAllListeners();
            removeModelButton.onClick.RemoveAllListeners();

            if (enableVoiceCommandPlacement)
                speechHandler.UnregisterHandler("Place", HandlePlaceCommand);

            RemoveAlignmentListeners();
        }

        private void AddAlignmentListeners()
        {
            objectAnchorAlignment.placementStarted.AddListener(HandlePlacementStarted);
            objectAnchorAlignment.placementEnded.AddListener(HandleObjectPlaced);
        }
        
        private void RemoveAlignmentListeners()
        {
            objectAnchorAlignment.placementStarted.RemoveAllListeners();
            objectAnchorAlignment.placementEnded.RemoveAllListeners();
        }

        private void SetupAnchorObject()
        {
            anchorStateIndex = 0;
            GoToDisplay(anchorStateIndex);

            //in case anchor wasn't set up in panel or auto enabled
            if (anchorGameObject == null)
            {
                Debug.LogWarning("No object anchor found, auto creating default. If this is deliberate please enable 'Auto Anchor Object' in panel inspector");
                anchorGameObject = new GameObject("ObjectAnchor_Auto");
            }

            anchorGameObject.SetActive(true);

            //reset anchor pose
            anchorGameObject.transform.position = Vector3.zero;
            anchorGameObject.transform.rotation = Quaternion.identity;

            SetupAnchorObjectModel();

            switch (objectAnchorType)
            {
                case ObjectAnchorType.manualPlacement:
                    objectAnchorAlignment = anchorGameObject.GetOrAddNewComponent<ManualObjectAnchorAlignment>();
                    (objectAnchorAlignment as ManualObjectAnchorAlignment).SetBehavior(manualPlacementBehavior);

                    AddAlignmentListeners();
                    break;
                case ObjectAnchorType.autoPlacement:
                    Debug.LogError("AutoPlacement is currently not supported, please use ManualPlacement Anchor Type.");

                    break;
                default:
                    break;

            }

            AddHolographicModelEffect();

            //if (startObjectPlacementOnDisplay)
                StartPlacement();
            
        }

        private void SetupAnchorObjectModel()
        {
            if (anchorObjectModel == null)
            {
                anchorModelType = ObjectAnchorModelType.gameObject;
                Debug.LogWarning("No object model found, using default drop point model");
                var prefab = Resources.Load("Utilities/Components/simpleAR Standard Assets/Prefabs/Object Anchor Panel/Standard Assets Drop Point") as GameObject;
                anchorObjectModel = prefab; 
            }

            DetachAnchorChildren();

            if (anchorModelType == ObjectAnchorModelType.gameObject)
            {
                var objectGO = anchorObjectModel as GameObject;

                //for now we just make a copy of the gameobject/prefab this, future improvement to use active game objects in hierarchy
                if (anchorModelInstance == null)
                {
                    anchorModelInstance = Instantiate(objectGO, Vector3.zero, Quaternion.identity);//, anchorGameObject.transform);
                    anchorModelInstance.transform.SetParent(anchorGameObject.transform, false);
                }
                else
                    anchorModelInstance.transform.SetParent(anchorGameObject.transform);

                anchorModelInstance.SetActive(true);
            }
            if (anchorModelType == ObjectAnchorModelType.mesh)
            {
                var objectMesh = anchorObjectModel as Mesh;
                anchorGameObject.GetOrAddNewComponent<MeshFilter>().mesh = objectMesh;


                //generate default material
                GameObject primitive = GameObject.CreatePrimitive(PrimitiveType.Plane);
                primitive.SetActive(false);
                Material diffuse = primitive.GetComponent<MeshRenderer>().sharedMaterial;
                DestroyImmediate(primitive);

                anchorGameObject.GetOrAddNewComponent<MeshRenderer>().material = diffuse;
                anchorGameObject.GetOrAddNewComponent<BoxCollider>();
            }

        }


        private void DetachAnchorChildren()
        {
            if (objectAnchorChildren.Count == 0)
            {
                for (int i = 0; i < anchorGameObject.transform.childCount; i++)
                {
                    objectAnchorChildren.Add(anchorGameObject.transform.GetChild(i));
                }
            }
            anchorGameObject.transform.DetachChildren();
        }

        private void AttachAnchorChildren()
        {
            foreach (Transform child in objectAnchorChildren)
            {
                //model instance should still be parented
                if (child.gameObject == anchorModelInstance)
                    continue;

                child.SetParent(anchorGameObject.transform, false);

            }
            objectAnchorChildren.Clear();
        }

        private void HandlePlaceCommand()
        {
            SetObjectAnchorPose(Vector3.zero, Vector3.zero, true); ;
        }

        public void StartPlacement()
        {
            if (enableVoiceCommandPlacement)
                speechHandler.RegisterHandler("Place", HandlePlaceCommand);

            objectAnchorAlignment.StartAlignment();
        }

        public void AddHolographicModelEffect()
        {
            var renderers = anchorModelInstance.GetComponentsInChildren<Renderer>();

            anchorObjectMeshRenderers.Clear();
            anchorObjectMeshMaterials.Clear();

            if (holographicObjectAnchorModel)
            {
                renderers.ToList().ForEach(r =>
                {
                    anchorObjectMeshRenderers.Add(r);
                    Material[] matArr = new Material[r.materials.Length];
                    for (int i = 0; i < matArr.Length; i++)
                    {
                        matArr[i] = new Material(r.materials[i]);
                        SetMaterialToTransparent(r.materials[i]);
                    }
                    anchorObjectMeshMaterials.Add(matArr);

                });
            }
        }
        public void RemoveHolographicModelEffect()
        {
            if (holographicObjectAnchorModel && !keepHolographicObjectAnchorModel)
            {
                var renderers = anchorModelInstance.GetComponentsInChildren<Renderer>();

                int matIndex = 0;
                foreach (Renderer r in anchorObjectMeshRenderers)
                {
                    if (r != null && anchorObjectMeshMaterials[matIndex] != null)
                        r.materials = anchorObjectMeshMaterials[matIndex];
                    matIndex++;
                }
            }
        }

        public void SetMaterialToTransparent(Material standardShaderMaterial)
        {
            standardShaderMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
            standardShaderMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
            standardShaderMaterial.SetInt("_ZWrite", 0);
            standardShaderMaterial.DisableKeyword("_ALPHATEST_ON");
            standardShaderMaterial.DisableKeyword("_ALPHABLEND_ON");
            standardShaderMaterial.EnableKeyword("_ALPHAPREMULTIPLY_ON");
            standardShaderMaterial.renderQueue = 3000;

            var color = standardShaderMaterial.color;
            color.a = 0.25f;

            color.r = 0.1f;
            color.g = 0.9686f;
            color.b = 0.9921f;

            standardShaderMaterial.color = color;
        }

        public void OnAlignmentConfirm(bool confirm)
        {
            if (objectAnchorAlignment.alignmentState != AlignmentState.Manipulate)
                return;

            if (confirm)
            {
                objectAnchorAlignment.ConfirmAlignment();

                EnableChildColliders();

                if (promptObjectAnchorRetainModel)
                {
                    GoToNextDisplay();
                }
                else
                {
                    if (defaulObjectAnchorRetainModelOption == ObjectAnchorPromptDefault.confirm)
                        OnRetainModelConfirm(true);
                    else
                        OnRetainModelConfirm(false);
                }

                ObjectAnchorAlignmentConfirmed?.Invoke();
                
            }
            else
            {
                //reset and go back to placement
                ResetAlignment();
            }
        }

        public void OnRetainModelConfirm(bool confirm)
        {
            if (objectAnchorAlignment.alignmentState != AlignmentState.ConfirmedAlignment)
                return;

            if (confirm)
            {
                RemoveHolographicModelEffect();
                      
                ObjectAnchorModelRetained?.Invoke();
            }
            else
            {
                if (anchorModelType == ObjectAnchorModelType.mesh)
                {
                    anchorGameObject.GetComponent<Renderer>().enabled = false;
                }
                else
                {
                    //disable
                    if (anchorModelInstance != null)
                    {
                        anchorModelInstance.SetActive(false);
                        //Destroy(anchorModelInstance);
                        //anchorModelInstance = null;
                    }
                }

                ObjectAnchorModelRemoved?.Invoke();
            }

            AttachAnchorChildren();

            //disable panel for now
            gameObject.SetActive(false);
        }

        private void DisableChildColliders()
        {
            var colliders = anchorGameObject.GetComponentsInChildren<Collider>();

            colliders.ToList().ForEach(col =>
           {
               //only add enabled
               if (col.enabled)
               {
                   anchorChildrenColliders.Add(col);
                   col.enabled = false;
               }
           });

            //renable the anchorGameObject colliders
            var anchorCol = anchorGameObject.GetComponents<Collider>();
            foreach (Collider col in anchorCol)
            {
                col.enabled = true;
            }
        }

        private void EnableChildColliders()
        {
            //renable the anchorGameObject colliders
            foreach (Collider col in anchorChildrenColliders)
            {
                if (col)
                col.enabled = true;
            }

            anchorChildrenColliders.Clear();
        }


        private void HandlePlacementStarted()
        {
            ObjectAnchorPlacementStarted?.Invoke();

            var collider = anchorGameObject.GetOrAddNewComponent<BoxCollider>();

            AttachAnchorChildren();

            DisableChildColliders();
        }

        private void HandleObjectPlaced()
        {
            if (enableVoiceCommandPlacement)
                speechHandler.UnregisterHandler("Place", HandlePlaceCommand);

            ObjectAnchorPlaced?.Invoke();
            GoToNextDisplay();

            if (!promptAlignmentConfirmation)
            {
                StartCoroutine(DelayRoutine(
                    () => {
                OnAlignmentConfirm(true);
                    }
                ));
            }

        }


        IEnumerator DelayRoutine(System.Action delayedAction)
        {
            yield return new WaitForSeconds(0.1f);
            delayedAction?.Invoke();
        }

        private void HandleManipulatePlacement()
        {
        }

        private void HandleConfirmPlacement()
        {
        }

        public void SetObjectAnchorPose(Vector3 pos, Vector3 rot, bool useCurrentPose)
        {
            if (objectAnchorAlignment != null)
            {
                objectAnchorAlignment.SetAlignment(pos, rot, useCurrentPose);
            }
        }
    }
}

