using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ChiseDrive;
using ChiseDrive.Units;
using ChiseDrive.Graphics;
using ChiseDrive.Particles;

namespace SamuraiVsZombie.Players
{
    /// <summary>
    /// This system tracks bone accuracy impacts from weapons and then
    /// creates a bleeding effect from the injured character.
    /// </summary>
    public class BleedSystem
    {
        /// <summary>
        /// A nested BleedEffect class, contained by the BleedSystem.
        /// </summary>
        public class BleedEffect : IFollow
        {
            /// <summary>
            /// The character that the bone comes from
            /// </summary>
            GameModel Skeleton;

            /// <summary>
            /// The name of the particular bone
            /// </summary>
            string BoneName;

            /// <summary>
            /// The distance down the bone to travel
            /// </summary>
            float BoneOffset;

            /// <summary>
            /// The direction the blood is spraying in
            /// </summary>
            Vector3 BloodSpray;

            /// <summary>
            /// The particle emitter that bleeds
            /// </summary>
            ParticleEmitter BloodEmitter;

            /// <summary>
            /// Is this bleed effect finished?
            /// </summary>
            public bool IsDone
            {
                get
                {
                    return BloodEmitter.IsDone;
                }
            }

            #region IFollow  Position, Velocity, Rotation
            Vector3 position;
            public Vector3 Position
            {
                get
                {
                    return position;
                }
                set
                {
                    throw new NotImplementedException();
                }
            }
            public Vector3 Velocity
            {
                get
                {
                    Matrix bone = Skeleton.BoneTransform(BoneName);
                    bone.Forward += BloodSpray;
                    return bone.Forward;
                }
            }
            public Matrix RotationPosition
            {
                get
                {
                    Matrix bone = Skeleton.BoneTransform(BoneName);
                    bone.Translation += bone.Forward * BoneOffset;
                    return bone;
                }
            }
            public Quaternion Rotation
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }
            public float Scale
            {
                get
                {
                    return 1f;
                }
            }
            #endregion

            /// <summary>
            /// Creates a new bleed effect.
            /// </summary>
            /// <param name=emitterSettings>The settings to use for setting
            /// up a ParticleEmitter</param>
            public BleedEffect(ParticleEmitterSettings emitterSettings)
            {
                BloodEmitter = new ParticleEmitter(this, emitterSettings);
            }

            public void Dispose()
            {
                // members
                BloodEmitter.Dispose();
                BloodEmitter = null;

                // references
                Skeleton = null;
            }

            public void Reset()
            {
                BloodEmitter.Reset();
            }

            /// <summary>
            /// Starts a new blood spray effect
            /// </summary>
            /// <param name=skeleton>The skeleton it’s attached to</param>
            /// <param name=bone>The bone it’s attached to</param>
            /// <param name=offset>The distance down the bone</param>
            /// <param name=direction>The direction to spray</param>
            /// <param name=time>The time to spray</param>
            public void NewBleed(GameModel skeleton, 
                string bone, 
                float offset, 
                Vector3 direction)
            {
                Skeleton = skeleton;
                BoneName = bone;
                BoneOffset = offset;
                BloodSpray = direction;
                BloodEmitter.EmitOnce();
            }

            /// <summary>
            /// Updates the position/rotation/emitter.
            /// </summary>
            /// <param name=elapsed>Time elapsed.</param>
            public void Update(Time elapsed)
            {
                Matrix bone = Skeleton.BoneTransform(BoneName);

                bone *= Skeleton.RotationPosition;
                position = bone.Translation;

                position += bone.Up * BoneOffset * Skeleton.Scale;

                BloodEmitter.Update(elapsed);
            }            
        }

        /// <summary>
        /// Up to 200 blood sprays can be simultaneously occuring.
        /// </summary>
        BleedEffect[] BleedEffects = new BleedEffect[200];
        int ActiveBleeds = 0;

        public BleedSystem(SamuraiZombie Game)
        {
            ParticleEmitterSettings settings = 
                Game.Content.Load<ParticleEmitterSettings>(
                Particles/BleedEmitter);

            for (int i = 0; i < BleedEffects.Length; i++)
            {
                BleedEffects[i] = new BleedEffect(settings);
            }
        }

        public void Dispose()
        {
            for (int i = 0; i < BleedEffects.Length; i++)
            {
                BleedEffects[i].Dispose();
                BleedEffects[i] = null;
            }
            BleedEffects = null;
            ActiveBleeds = 0;
        }

        public void ResetAll()
        {
            foreach (BleedEffect bleed in BleedEffects)
            {
                bleed.Reset();
            }
        }

        public override string ToString()
        {
            return BleedSystem;
        }

        /// <summary>
        /// Blade to Bone accuracy requires a clean bone cut to start a 
        /// bleed effect.
        /// </summary>
        /// <param name=bladeEdge>A blade edge to test against.</param>
        /// <param name=skeleton>The skeleton of the character to
        /// test.</param>
        /// <returns>The point of impact or null.</returns>
        public Vector3? Slash(
            BladeEdge bladeEdge, 
            GameModel skeleton)
        {
            // This is a bounding box that contains the full sweep
            // that this sword has gone through in this frame.
            BoundingBox bladeSweep = bladeEdge.Area;

            // Check to see if at some point in this frame this
            // sword might have intersected the skeleton.
            // This is a relatively fast operation, and if it
            // fails to find an intersect, we dont waste time
            // with all the intense computations inside the statement.
            if (skeleton.BoundingBox.Contains(bladeSweep)
                == ContainmentType.Intersects)
            {
                foreach (string name in skeleton.GetBoneNames())
                {
                    // skeleton.BoneTransform returns a skinned
                    // transform thats relative to the skeleton
                    // heiarchy, but we need global relativity
                    // so apply the skeletons RotationPosition.
                    Matrix bone = skeleton.BoneTransform(name)
                        * skeleton.RotationPosition;

                    float boneLength = skeleton.BoneLength(name);

                    // Check this bone.
                    float? intersect = CheckSingleBone(
                        ref bone, 
                        ref bladeEdge, 
                        ref boneLength);

                    if (intersect != null) 
                    {
                        // The blade sliced through this bone
                        Ray test = new Ray(
                            bone.Translation,
                            bone.Up);

                        // We want the blood to travel in
                        // the opposite direction that this
                        // blade is traveling in.
                        Vector3 bleedDirection =
                            bladeEdge.Direction;

                        return StartBleed(
                            skeleton,
                            name,
                            test,
                            (float)intersect,
                            bleedDirection);
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// When precise slashes aren’t working well enough, try
        /// this more forgiving method that fudges the numbers
        /// to find a bone that was almost hit by the blade.
        /// </summary>
        /// <param name=bladeEdge>A blade to test against.</param>
        /// <param name=skeleton>A skeleton to test.</param>
        /// <param name=accuracy>0 for only true strikes, 
        /// larger values for more generous fudging.</param>
        /// <returns>The point of impact or null.</returns>
        public Vector3? SlashNearest(
            BladeEdge bladeEdge,
            GameModel skeleton,
            float accuracy)
        {
            // The same simple discard for nonintersection as above.
            if (skeleton.BoundingBox.Contains(bladeEdge.Area)
                == ContainmentType.Intersects)
            {
                // Find the center and base of the blade arc
                Vector3 bladeCenter = bladeEdge.Center;
                Vector3 bladeBase = bladeEdge.Base;

                float closestDistance = float.MaxValue;
                Matrix closestBone = Matrix.Identity;
                string closestName = string.Empty;

                foreach (string name in skeleton.GetBoneNames())
                {
                    // skeleton.BoneTransform returns a skinned
                    // transform thats relative to the skeleton
                    // heiarchy, but we need global relativity
                    // so apply the skeletons RotationPosition.
                    Matrix bone = skeleton.BoneTransform(name)
                        * skeleton.RotationPosition;

                    float boneLength = skeleton.BoneLength(name);

                    // Check this bone.
                    float? intersect = CheckSingleBone(
                        ref bone, 
                        ref bladeEdge, 
                        ref boneLength);

                    // If we have a true intersect break the loop
                    if (intersect != null) 
                    {
                        closestDistance = 0f;
                        closestBone = bone;
                        closestName = name;
                        break;
                    }

                    // Find out how close we came to the bone,
                    // calculated against the base and center,
                    // but excluding the tip.
                    float distance1 = Vector3.Distance(
                        bladeBase, bone.Translation);
                    float distance2 = Vector3.Distance(
                        bladeCenter, bone.Translation);

                    float distance = 
                        distance1 < distance2 ? distance1 : distance2;

                    // Remember the bone we were the closest to.
                    if (distance < closestDistance)
                    {
                        closestDistance = distance;
                        closestBone = bone;
                        closestName = name;
                    }
                }                

                if (closestDistance <= accuracy)
                {
                    // We almost slashed this bone, which is
                    // good enough for the fudge method.
                    Ray bone = new Ray(
                        closestBone.Translation,
                        closestBone.Up);

                    // We want the blood to travel in
                    // the opposite direction that this
                    // blade is traveling in.
                    Vector3 bleedDirection =
                        bladeEdge.Direction;

                    return StartBleed(
                        skeleton,
                        closestName,
                        bone,
                        closestDistance,// possibly out of the mesh
                        bleedDirection);
                }
            }

            return null;
        }

        /// <summary>
        /// Checks a single bone for a clean cut.  Private,
        /// as it uses references to data to keep a lean
        /// efficient inner loop.
        /// </summary>
        /// <param name=bone></param>
        /// <param name=bladeEdge></param>
        /// <param name=boneLength></param>
        /// <returns></returns>
        float? CheckSingleBone(
            ref Matrix bone,
            ref BladeEdge bladeEdge,
            ref float boneLength)
        {
            Ray test = new Ray(
                bone.Translation,   // Position
                bone.Up);           // Direction

            // Check to see if the bone intersects the
            // swords plane, and if that intersection
            // occurs within our bones length
            float? intersect = test.Intersects(bladeEdge.Plane);
            if (intersect != null
                && (float)intersect < boneLength)
            {
                // Now move on to very expensive triangle collision
                Helper.RayIntersectsTriangle(
                    ref test,
                    ref bladeEdge.First.Point1,
                    ref bladeEdge.First.Point2,
                    ref bladeEdge.Second.Point1,
                    out intersect);// Will set intersect to null if fails

                if (intersect == null)
                {
                    // Second triangle
                    Helper.RayIntersectsTriangle(
                        ref test,
                        ref bladeEdge.Second.Point1,
                        ref bladeEdge.Second.Point2,
                        ref bladeEdge.First.Point1,
                        out intersect);// Will set intersect to null if fails
                }
            }
            else
            {
                // The intersect was greater than the bone length,
                // so set it to null.
                intersect = null;
            }

            // May be null!
            return intersect;
        }

        /// <summary>
        /// Starts a bleed effect.
        /// </summary>
        /// <param name=skeleton>The skeleton to attach to.</param>
        /// <param name=name>The bone name.</param>
        /// <param name=bone>A Ray representing the bone.</param>
        /// <param name=intersect>The intersect point along 
        /// the ray.</param>
        /// <param name=direction>The direction of the impact</param>
        /// <returns>The point on the skeleton.</returns>
        Vector3 StartBleed(
            GameModel skeleton, 
            string name, 
            Ray bone, 
            float intersect, 
            Vector3 direction)
        {
            if (ActiveBleeds < BleedEffects.Length)
            {
                Vector3 returnvalue = bone.Position;
                returnvalue += bone.Direction * (float)intersect;

                // A safe normalize that will catch corrupted
                // data in DEBUG mode
                Helper.Normalize(ref direction);

                // Start the actual bleed effect.
                BleedEffects[ActiveBleeds].NewBleed(skeleton, name, 
                    (float)intersect, direction);
                ActiveBleeds++;

                return returnvalue;
            }

            return Vector3.Zero;
        }

        /// <summary>
        /// Compacts bleeds.
        /// </summary>
        void CompactBleeds()
        {
            if (ActiveBleeds == 0) return;

            for (int i = 0; i < BleedEffects.Length; i++)
            {
                if (BleedEffects[i].IsDone)
                {
                    // Assume this expired bleed is our last one!
                    ActiveBleeds = i;

                    for (int j = ActiveBleeds; j <= BleedEffects.Length; j++)
                    {
                        // Weve looked through all the rest
                        // and found nothing to compact, so escape
                        // from the double nested for loops
                        if (== BleedEffects.Length) return;

                        if (!BleedEffects[j].IsDone)
                        {
                            // This one is active! Move it up in the list!
                            BleedEffect temp = BleedEffects[i];
                            BleedEffects[i] = BleedEffects[j];
                            BleedEffects[j] = temp;
                            break;// for j <= BleedEffects.Length
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Updates all bleed effects and compacts them when done.
        /// </summary>
        /// <param name=elapsed>Simulation time to apply.</param>
        public void Update(Time elapsed)
        {
            for (int i = 0; i < ActiveBleeds; i++)
            {
                BleedEffects[i].Update(elapsed);
            }

            CompactBleeds();
        }
    }
}