using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RayFire
{
    [Serializable]
    public class RFLimitations
    {
        [Tooltip ("Local Object solidity multiplier for object. Low Solidity makes object more fragile.")]
        [Range (0.0f, 10f)]
        public float solidity;

        [Space(1)]
        [Tooltip ("Defines how deep object can be demolished. Depth is limitless if set to 0.")]
        [Range (0, 7)]
        public int depth;

        [Space(1)]
        [Tooltip ("Safe time. Measures in seconds and allows to prevent fragments from being demolished right after they were just created.")]
        [Range (0.05f, 10f)]
        public float time;

        [Space(1)]
        [Tooltip ("Prevent objects with bounding box size less than defined value to be demolished.")]
        [Range (0.01f, 5f)]
        public float size;
        
        [Space(1)]
        [Tooltip ("Object will be demolished only if it is visible to any camera including scene camera.")]
        public bool visible;
        
        //[Tooltip ("")]
        //[Range (1, 100)]
        // TODO public int probability;
        
        [Tooltip ("Allows object to be sliced by object with RayFire Blade component.")]
        public bool sliceByBlade;

        // Non serialized
        [NonSerialized] public List<Vector3> slicePlanes;
        [NonSerialized] public ContactPoint contactPoint;
        [NonSerialized] public Vector3 contactVector3;
        [NonSerialized] public Vector3 contactNormal;
        [NonSerialized] public bool demolitionShould;
        [NonSerialized] public bool demolished;
        [NonSerialized] public float birthTime;
        [NonSerialized] public float bboxSize;
        [NonSerialized] public int currentDepth;
        
        [NonSerialized] public RayfireRigid       ancestor;
        [NonSerialized] public List<RayfireRigid> descendants;
        
        // Hidden
        [HideInInspector] public Bounds bound;

        static string rootStr = "_root";
        
        /// /////////////////////////////////////////////////////////
        /// Constructor
        /// /////////////////////////////////////////////////////////
        
        // Constructor
        public RFLimitations()
        {
            solidity         = 0.1f;
            depth            = 1;
            time             = 0.2f;
            size             = 0.1f;
            visible          = false;
            
            sliceByBlade     = false;
            
            currentDepth     = 0;
            birthTime        = 0f;
            bboxSize         = 0f;

            ancestor = null;
            descendants = null;
            
            Reset();
        }

        // Copy from
        public void CopyFrom (RFLimitations limitations)
        {
            solidity         = limitations.solidity;
            depth            = limitations.depth;
            time             = limitations.time;
            size             = limitations.size;
            visible          = limitations.visible;
            
            sliceByBlade     = limitations.sliceByBlade;
            
            // Do not copy currentDepth. Set in other place
            
            Reset();
        }
        
        // Reset
        public void Reset()
        {
            slicePlanes      = new List<Vector3>();
            contactVector3   = Vector3.zero;
            contactNormal    = Vector3.down;
            demolitionShould = false;
            demolished       = false;
        }
        
        /// /////////////////////////////////////////////////////////
        /// Static
        /// /////////////////////////////////////////////////////////
         
        // Cache velocity for fragments 
        public IEnumerator DemolishableCor(RayfireRigid scr)
        {
            while (scr.demolitionType != DemolitionType.None)
            {
                // Max depth reached
                if (scr.limitations.depth > 0 && scr.limitations.currentDepth >= scr.limitations.depth)
                    scr.demolitionType = DemolitionType.None;

                // Init demolition
                if (scr.limitations.demolitionShould == true)
                    scr.Demolish();
                
                // Check for slicing planes and init slicing
                else if (scr.limitations.sliceByBlade == true && scr.limitations.slicePlanes.Count > 1)
                    scr.Slice();

                yield return null;
            }
        }
        
        /// /////////////////////////////////////////////////////////
        /// Static
        /// /////////////////////////////////////////////////////////
        
        // Check for user mistakes
        public static void Checks (RayfireRigid scr)
        {
   
            // TODO static and cluster
            if (scr.objectType == ObjectType.Mesh)
            {
                                 
                
            }
            
            // ////////////////
            // Sim Type
            // ////////////////
            
            // Static and demolishable
            if (scr.simulationType == SimType.Static)
            {
                if (scr.demolitionType != DemolitionType.None)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Simulation Type set to " + scr.simulationType.ToString() + " but Demolition Type is not None. Static object can not be demolished. Demolition Type set to None.", scr.gameObject);
                    scr.demolitionType = DemolitionType.None;
                }
            }
            
            // Non static simulation but static property
            if (scr.simulationType != SimType.Static)
            {
                if (scr.gameObject.isStatic == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Simulation Type set to " + scr.simulationType.ToString() + " but object is Static. Turn off Static checkbox in Inspector.", scr.gameObject);
                }
            }
           
            // ////////////////
            // Object Type
            // ////////////////
            
            // Object can not be simulated as mesh
            if (scr.objectType == ObjectType.Mesh)
            {
                // Has no mesh
                if (scr.meshFilter == null || scr.meshFilter.sharedMesh == null)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Object Type set to " + scr.objectType.ToString() + " but object has no mesh. Object Excluded from simulation.", scr.gameObject);
                    scr.physics.exclude = true;
                }
                
                // Not readable mesh 
                if (scr.demolitionType != DemolitionType.None)
                {
                    if (scr.meshFilter != null && scr.meshFilter.sharedMesh != null && scr.meshFilter.sharedMesh.isReadable == false)
                    {
                        Debug.Log ("RayFire Rigid: " + scr.name + " Mesh is not readable. Demolition type set to None. Open Import Settings and turn On Read/Write Enabled property", scr.meshFilter.gameObject);
                        scr.demolitionType         = DemolitionType.None;
                        scr.meshDemolition.badMesh = 10;
                    }
                }
            }
            
            // Object can not be simulated as cluster
            else if (scr.objectType == ObjectType.NestedCluster || scr.objectType == ObjectType.ConnectedCluster)
            {
                if (scr.transForm.childCount == 0)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Object Type set to " + scr.objectType.ToString() + " but object has no children. Object Excluded from simulation.", scr.gameObject);
                    scr.physics.exclude = true;
                }
            }
            
            // Object can not be simulated as mesh
            else if (scr.objectType == ObjectType.SkinnedMesh)
            {
                if (scr.skinnedMeshRend == null)
                    Debug.Log ("RayFire Rigid: " + scr.name + " Object Type set to " + scr.objectType.ToString() + " but object has no SkinnedMeshRenderer. Object Excluded from simulation.", scr.gameObject);
                
                // Excluded from sim by default
                scr.physics.exclude = true;
            }
            
            // ////////////////
            // Demolition Type
            // ////////////////
            
            // Demolition checks
            if (scr.demolitionType != DemolitionType.None)
            {
                // // Static
                // if (scr.simulationType == SimType.Static)
                // {
                //     Debug.Log ("RayFire Rigid: " + scr.name + " Simulation Type set to " + scr.simulationType.ToString() + " but Demolition Type is " + scr.demolitionType.ToString() + ". Demolition Type set to None.", scr.gameObject);
                //     scr.demolitionType = DemolitionType.None;
                // }
                
                // Set runtime demolition for clusters and skinned mesh
                if (scr.objectType == ObjectType.SkinnedMesh ||
                    scr.objectType == ObjectType.NestedCluster ||
                    scr.objectType == ObjectType.ConnectedCluster)
                {
                    if (scr.demolitionType != DemolitionType.Runtime && scr.demolitionType != DemolitionType.ReferenceDemolition)
                    {
                        Debug.Log ("RayFire Rigid: " + scr.name + " Object Type set to " + scr.objectType.ToString() + " but Demolition Type is " + scr.demolitionType.ToString() + ". Demolition Type set to Runtime.", scr.gameObject);
                        scr.demolitionType = DemolitionType.Runtime;
                    }
                }
                
                // No Shatter component for runtime demolition with Use Shatter on
                if (scr.meshDemolition.scrShatter == null && scr.meshDemolition.useShatter == true)
                {
                    if (scr.demolitionType == DemolitionType.Runtime ||
                        scr.demolitionType == DemolitionType.AwakePrecache ||
                        scr.demolitionType == DemolitionType.AwakePrefragment)
                    {
                        
                        Debug.Log ("RayFire Rigid: " + scr.name + "Demolition Type is " + scr.demolitionType.ToString() + ". Has no Shatter component, but Use Shatter property is On. Use Shatter property was turned Off.", scr.gameObject);
                        scr.meshDemolition.useShatter = false;
                    }
                }
            }
            
            // None check
            if (scr.demolitionType == DemolitionType.None)
            {
                if (scr.HasMeshes == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to None. Had manually precached meshes which were destroyed.", scr.gameObject);
                    scr.DeleteCache();
                }

                if (scr.HasFragments == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to None. Had manually prefragmented objects which were destroyed.", scr.gameObject);
                    scr.DeleteFragments();
                }

                if (scr.HasRfMeshes == true)
                {
                    Debug.Log ("RayFire Rigid:" + scr.name + " Demolition Type set to None. Had manually precached serialized meshes which were destroyed.", scr.gameObject);
                    scr.DeleteCache();
                }
            }

            // Runtime check
            else if (scr.demolitionType == DemolitionType.Runtime)
            {
                if (scr.HasMeshes == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Runtime. Had manually precached meshes which were destroyed.", scr.gameObject);
                    scr.DeleteCache();
                }

                if (scr.HasFragments == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Runtime. Had manually prefragmented objects which were destroyed.", scr.gameObject);
                    scr.DeleteFragments();
                }

                if (scr.HasRfMeshes == true)
                {
                    Debug.Log ("RayFire Rigid:" + scr.name + " Demolition Type set to Runtime. Had manually precached serialized meshes which were destroyed.", scr.gameObject);
                    scr.DeleteCache();
                }
                
                // No runtime caching for rigid with shatter with tets/slices/glue
                if (scr.meshDemolition.useShatter == true && scr.meshDemolition.runtimeCaching.type != CachingType.Disable)
                {
                    if (scr.meshDemolition.scrShatter.type == FragType.Decompose ||
                        scr.meshDemolition.scrShatter.type == FragType.Tets ||
                        scr.meshDemolition.scrShatter.type == FragType.Slices || 
                        scr.meshDemolition.scrShatter.clusters.enable == true)
                    {
                        Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type is Runtime, Use Shatter is On. Unsupported fragments type. Runtime Caching supports only Voronoi, Splinters, Slabs and Radial fragmentation types. Runtime Caching was Disabled.", scr.gameObject);
                        scr.meshDemolition.runtimeCaching.type = CachingType.Disable;
                    }
                }
            }

            // Awake precache check
            else if (scr.demolitionType == DemolitionType.AwakePrecache)
            {
                if (scr.HasMeshes == true)
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Awake Precache. Had manually precached Unity meshes which were overwritten.", scr.gameObject);
                
                if (scr.HasFragments == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Awake Precache. Had manually prefragmented objects which were destroyed.", scr.gameObject);
                    scr.DeleteFragments();
                }

                if (scr.HasRfMeshes == true)
                {
                    Debug.Log ("RayFire Rigid:" + scr.name + " Demolition Type set to Awake Precache. Has manually precached serialized meshes.", scr.gameObject);
                }
            }

            // Awake prefragmented check
            else if (scr.demolitionType == DemolitionType.AwakePrefragment)
            {
                if (scr.HasFragments == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Awake Prefragment. Has manually prefragmented objects", scr.gameObject);
                }

                if (scr.HasMeshes == true)
                {
                    Debug.Log ("RayFire Rigid: " + scr.name + " Demolition Type set to Awake Prefragment. Has manually precached Unity meshes.", scr.gameObject);
                }

                if (scr.HasRfMeshes == true)
                {
                    Debug.Log ("RayFire Rigid:" + scr.name + " Demolition Type set to Awake Prefragment. Has manually precached serialized meshes.", scr.gameObject);
                }
            }
            
            // TODO Tag and Layer check
        }
        
        // Set bound and size
        public static void SetBound (RayfireRigid scr)
        {
            if (scr.objectType == ObjectType.Mesh)
                scr.limitations.bound = scr.meshRenderer.bounds;
            else if (scr.objectType == ObjectType.SkinnedMesh)
                scr.limitations.bound = scr.skinnedMeshRend.bounds;
            else if (scr.objectType == ObjectType.NestedCluster || scr.objectType == ObjectType.ConnectedCluster)
                scr.limitations.bound = RFCluster.GetChildrenBound (scr.transForm);
            scr.limitations.bboxSize = scr.limitations.bound.size.magnitude;
        }
        
        // Set ancestor
        public static void SetAncestor (RayfireRigid scr)
        {
            // Set ancestor to this if it is ancestor
            if (scr.limitations.ancestor == null)
                for (int i = 0; i < scr.fragments.Count; i++)
                    scr.fragments[i].limitations.ancestor = scr;
            else
                for (int i = 0; i < scr.fragments.Count; i++) 
                    scr.fragments[i].limitations.ancestor = scr.limitations.ancestor;
        }
        
        // Set descendants 
        public static void SetDescendants (RayfireRigid scr)
        {
            if (scr.limitations.ancestor == null)
                scr.limitations.descendants.AddRange (scr.fragments);
            else
                scr.limitations.ancestor.limitations.descendants.AddRange (scr.fragments);
        }
        
        // Create root
        public static void CreateRoot (RayfireRigid rfScr)
        {
           GameObject root = new GameObject(rfScr.gameObject.name + rootStr);
           rfScr.rootChild          = root.transform;
           rfScr.rootChild.position = rfScr.transForm.position;
           rfScr.rootChild.rotation = rfScr.transForm.rotation;
           
           rfScr.rootChild.SetParent (rfScr.gameObject.transform.parent);
        }
    }
}