using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;

namespace RayFire
{
    [SelectionBase]
    [AddComponentMenu ("RayFire/Rayfire Debris")]
    [HelpURL ("http://rayfirestudios.com/unity-online-help/unity-debris-component/")]
    public class RayfireDebris : MonoBehaviour
    {
        [Header("  Emit Debris")]
        [Space (3)]
                
        public bool onDemolition = false;
        [Space (1)]
        public bool onActivation = false;
        [Space (1)]
        public bool onImpact = false;
        
        [Header("  Main")]
        [Space (3)]
        
        public GameObject debrisReference;
        [Space (2)]
        public Material debrisMaterial;
        [Space (2)]
        public Material emissionMaterial;
        
        [Header ("  Properties")]
        [Space (3)]
        
        public RFParticleEmission emission;
        [Space (2)]
        public RFParticleDynamicDebris dynamic;
        [Space (2)]
        public RFParticleNoise noise;
        [Space (2)]
        public RFParticleCollisionDebris collision;
        [Space (2)]
        public RFParticleLimitations limitations;
        [Space (2)]
        public RFParticleRendering rendering;
        
        // Hidden
        [HideInInspector] public RayfireRigid rigid;
        [HideInInspector] public ParticleSystem pSystem;
        [HideInInspector] public Transform hostTm;
        [HideInInspector] public bool initialized;
        [HideInInspector] public List<Mesh> debrisMeshList;
        [HideInInspector] public Vector3 refScale = Vector3.one;
        [HideInInspector] public List<RayfireDebris> children;
        [HideInInspector] public int amountFinal;
        
        /// /////////////////////////////////////////////////////////
        /// Common
        /// /////////////////////////////////////////////////////////
        
        // Constructor
        public RayfireDebris()
        {
            onDemolition = false;
            onActivation = false;
            onImpact = false;

            debrisReference = null;
            debrisMaterial = null;
            emissionMaterial = null;
            
            emission = new RFParticleEmission();
            dynamic = new RFParticleDynamicDebris();
            noise = new RFParticleNoise();
            collision = new RFParticleCollisionDebris();
            limitations = new RFParticleLimitations();
            rendering = new RFParticleRendering();
            
            // Hidden
            debrisMeshList = new List<Mesh>();
            
            pSystem = null;
            hostTm = null;
            initialized = false;
            amountFinal = 0;
        }

        // Copy from
        public void CopyFrom(RayfireDebris source)
        {
            onDemolition = source.onDemolition;
            onActivation = source.onActivation;
            onImpact = source.onImpact;

            debrisReference = source.debrisReference;
            debrisMaterial = source.debrisMaterial;
            emissionMaterial = source.emissionMaterial;
            
            emission.CopyFrom (source.emission);
            dynamic.CopyFrom (source.dynamic);
            noise.CopyFrom (source.noise);
            collision.CopyFrom (source.collision);
            limitations.CopyFrom (source.limitations);
            rendering.CopyFrom (source.rendering);

            // Hidden
            debrisMeshList = source.debrisMeshList;
            initialized = source.initialized;
        }

        /// /////////////////////////////////////////////////////////
        /// Methods
        /// ///////////////////////////////////////////////////////// 

        // Initialize
        public void Initialize()
        {
            // Do not initialize if already initialized or parent was initialized
            if (initialized == true)
                return;

            // Has rigid check or transform caching
            
            // TODO AmountCheck(RayfireRigid scrSource, int pType) and xollect if ok
            
            // Set debris ref meshes
            SetReferenceMeshes (debrisReference);
        }
        
        // Emit particles
        public void Emit()
        {
            // Initialize
            Initialize();
            
            // Emitter is not ready
            if (initialized == false)
                return;

            // Set material properties in case object has no rigid
            collision.SetMaterialProps (this);
            
            // Particle system
            ParticleSystem ps = CreateParticleSystem(this);

            // Get components
            MeshFilter emitMeshFilter = GetComponent<MeshFilter>();
            MeshRenderer meshRenderer = GetComponent<MeshRenderer>();

            // Get emit material index
            int emitMatIndex = RFParticles.GetEmissionMatIndex (meshRenderer, emissionMaterial);

            // TODO set amount
            this.amountFinal = 30;
            
            // Create debris
            CreateDebris(transform, this, emitMeshFilter, emitMatIndex, ps); 
        }
        
        /// /////////////////////////////////////////////////////////
        /// Create common
        /// /////////////////////////////////////////////////////////
        
        // Create host and particle system
        public ParticleSystem CreateParticleSystem(RayfireDebris scr)
        {
            // Create root
            GameObject host = new GameObject(scr.name + "_debris");
            host.transform.position = scr.transform.position;
            host.transform.rotation = scr.transform.rotation;
            host.transform.parent = scr.transform;
            host.transform.localScale = Vector3.one;

            // Particle system
            ParticleSystem ps = host.AddComponent<ParticleSystem>();
            scr.pSystem = ps;
            scr.hostTm = host.transform;

            // Destroy after all particles death
            Destroy(host, scr.emission.lifeMax + ps.main.duration);

            // Stop for further properties set
            ps.Stop();
            
            return ps;
        }
        
        // Create single debris particle system
        public void CreateDebris(Transform host, RayfireDebris scr, MeshFilter emitMeshFilter, int emitMatIndex, ParticleSystem ps)
        {
            // Set main module
            RFParticles.SetMain(ps.main, scr.emission.lifeMin, scr.emission.lifeMax, scr.emission.sizeMin, scr.emission.sizeMax, 
                scr.dynamic.gravityMin, scr.dynamic.gravityMax, scr.dynamic.speedMin, scr.dynamic.speedMax, 
                3.1f, scr.limitations.maxParticles, scr.emission.duration);

            // Emission over distance
            RFParticles.SetEmission(ps.emission, scr.emission.distanceRate, scr.amountFinal);

            // Emission from mesh or from impact point
            if (emitMeshFilter != null)
                RFParticles.SetShapeMesh(ps.shape, emitMeshFilter.sharedMesh, emitMatIndex, emitMeshFilter.transform.localScale);
            else
                RFParticles.SetShapeObject(ps.shape);

            // Inherit velocity
            RFParticles.SetVelocity(ps.inheritVelocity, scr.dynamic);
            
            // Size over lifetime
            RFParticles.SetSizeOverLifeTime(ps.sizeOverLifetime, scr.refScale);

            // Rotation by speed
            RFParticles.SetRotationBySpeed(ps.rotationBySpeed, scr.dynamic.rotationSpeed);

            // Collision
            RFParticles.SetCollisionDebris(ps.collision, scr.collision);

            // Noise
            RFParticles.SetNoise (ps.noise, scr.noise);
            
            // Renderer
            SetParticleRendererDebris(ps.GetComponent<ParticleSystemRenderer>(), scr);

            // Start playing
            ps.Play();
        }
        
        /// /////////////////////////////////////////////////////////
        /// Renderer
        /// /////////////////////////////////////////////////////////

        // Set renderer
        public void SetParticleRendererDebris (ParticleSystemRenderer rend, RayfireDebris scr)
        {
            // Common vars
            rend.renderMode = ParticleSystemRenderMode.Mesh;
            rend.alignment = ParticleSystemRenderSpace.World;

            
            
            // Set predefined meshes
            if (scr.debrisMeshList.Count > 0)
            {
                if (scr.debrisMeshList.Count <= 4)
                {
                    rend.SetMeshes (scr.debrisMeshList.ToArray());
                    rend.mesh = scr.debrisMeshList[0];
                }
                else
                {
                    List<Mesh> newList = new List<Mesh>();
                    for (int i = 0; i < 4; i++)
                        newList.Add (scr.debrisMeshList[Random.Range (0, scr.debrisMeshList.Count)]);
                    rend.SetMeshes (newList.ToArray());
                    rend.mesh = newList[0];
                }
            }

            // Set material
            rend.sharedMaterial = scr.debrisMaterial;

            // Shadow casting
            rend.shadowCastingMode = scr.rendering.castShadows == true 
                ? UnityEngine.Rendering.ShadowCastingMode.On 
                : UnityEngine.Rendering.ShadowCastingMode.Off;

            // Shadow receiving
            rend.receiveShadows = scr.rendering.receiveShadows;
        }
        
        // Get reference meshes
        void SetReferenceMeshes(GameObject refs)
        {
            // Local lists
            debrisMeshList = new List<Mesh>();
            
            // No reference. Use own mesh
            if (refs == null)
            {
                Debug.Log (gameObject.name + ": Debris reference not defined.", gameObject);
                return;
            }
            
            // Local lists
            List<MeshFilter> mfs = new List<MeshFilter>();

            // Add local mf
            MeshFilter meshFilter = refs.GetComponent<MeshFilter>();
            if (meshFilter != null)
                mfs.Add (meshFilter);

            // Add children mf
            if (refs.transform.childCount > 0)
                mfs.AddRange (refs.GetComponentsInChildren<MeshFilter>().ToList());

            // No mesh filters
            if (mfs.Count == 0)
            {
                Debug.Log (gameObject.name + ": Debris reference mesh is not defined.", gameObject);
                return;
            }

            // Get all meshes
            debrisMeshList = (from mf in mfs where mf.sharedMesh != null && mf.sharedMesh.vertexCount > 3 select mf.sharedMesh).ToList();

            // No meshes. Use own mesh
            if (debrisMeshList.Count == 0)
            {
                Debug.Log (gameObject.name + ": Debris reference mesh is not defined.", gameObject);
                return;
            }

            // Set debris material
            SetDebrisMaterial (mfs);
            
            // Set scale
            refScale = mfs[0].transform.lossyScale;
            initialized = true;
        }
        
        // Set debris material
        void SetDebrisMaterial(List<MeshFilter> mfs)
        {
            // Already defined
            if (debrisMaterial != null)
                return;
            
            Renderer rend;
            for (int i = 0; i < mfs.Count; i++)
            {
                rend = mfs[i].GetComponent<Renderer>();
                if (rend != null)
                {
                    if (rend.sharedMaterial != null)
                    {
                        debrisMaterial = rend.sharedMaterial;
                        return;
                    }
                }
            }

            // Set original object material
            if (debrisMaterial == null)
                debrisMaterial = GetComponent<Renderer>().sharedMaterial;
        }
        
        public bool HasChildren { get { return children != null && children.Count > 0; } }
    }
}

        // TODO       
        // spread particle generators over few frames if slow or at next frame
        // particles reset. reuse fragments and delete or reuse host with ps
        // particles copy check and copy disabling: no ref
        // few debris references: rocks, splinters
        // Depth debris slow down fix

        // AnimationCurve and keyframes caching, do not alloc everyframe
        // CreateDemolitionDebris. List<RayfireDebris> particlesTargets. do not alloc, 
        // Input all populated lists. clear first
        // create host reference pool with particle system and store in manager
        // cache all new waitforseconds for coroutines