using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisiMath;

namespace VisimBox.Physics
{
    /// <summary>
    /// Interface for force generators
    /// </summary>
    public interface IForceGenerator
    {
        /// <summary>
        /// Update node by time
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="time">elapsed time</param>
        void Update(RigidBody node, float time);
    }

    /// <summary>
    /// Constant gravity generator
    /// </summary>
    public class GravityForceGenerator : IForceGenerator
    {
        protected Vector3 Gravity;

        /// <summary>
        /// Create with gravity vector (default should be (0,0,-9.81)
        /// </summary>
        /// <param name="gravity">Gravity vector</param>
        public GravityForceGenerator(Vector3 gravity)
        {
            Gravity = gravity;
        }

        /// <summary>
        /// Update node by time
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="time">elapsed time</param>
        public virtual void Update(RigidBody node, float time)
        {
            if (node.IsStaticObject)
                return;

            node.AddForce(new Vector3(Gravity, node.Mass));
        }
    }

    /// <summary>
    /// Drag force generator, reducing speed of the object based on it's actual speed
    /// </summary>
    public class DragForceGenerator : IForceGenerator
    {
        protected float K;
        protected float Squaredk;

        /// <summary>
        /// Create generator
        /// </summary>
        /// <param name="k">Linear koeficient</param>
        /// <param name="sqk">Squared koeficient</param>
        public DragForceGenerator(float k, float sqk)
        {
            K = k;
            Squaredk = sqk;
        }

        /// <summary>
        /// Update node by time - slows object based on it's speed
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="time">elapsed time</param>
        public virtual void Update(RigidBody node, float time)
        {
            if (node.IsStaticObject)
                return;

            Vector3 vel = node.Velocity;
            float coef = vel.Length;
            vel.Divide(coef); //normalize
            coef = K * coef + Squaredk * coef * coef;
            vel.Mul(-coef);
            node.AddForce(ref vel);
        }
    }

    /// <summary>
    /// Classical spring
    /// </summary>
    public class Spring : IForceGenerator
    {
        /// <summary>
        /// Connection point in local coordinates
        /// </summary>
        Vector3 ConnectionPoint1;
        /// <summary>
        /// Connection point in local coordinates for target body
        /// </summary>
        Vector3 ConnectionPoint2;

        /// <summary>
        /// Target rigid body
        /// </summary>
        RigidBody TargetBody;

        /// <summary>
        /// Spring constant
        /// </summary>
        float SpringConstant;

        /// <summary>
        /// Spring length
        /// </summary>
        float SpringLength;

        public Spring(Vector3 cp1, Vector3 cp2, RigidBody target, float sprConst, float sprLength)
        {
            ConnectionPoint1 = cp1;
            ConnectionPoint2 = cp2;
            TargetBody = target;
            SpringConstant = sprConst;
            SpringLength = sprLength;
        }

        /// <summary>
        /// Update node by time
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="time">elapsed time</param>
        public virtual void Update(RigidBody node, float time)
        {
            if (node.IsStaticObject)
                return;
            
            Vector3 sourcep = node.GetPointInWorldSpace(ConnectionPoint1);
            Vector3 targetp = TargetBody.GetPointInWorldSpace(ConnectionPoint2);

            Vector3 force = Vector3.Substract(ref sourcep, ref targetp);

            float length = force.Length;

            float newlength = SpringConstant * MathTools.Abs(length - SpringLength);

            force.Mul(-newlength / length);

            node.AddForceWorld(ref sourcep, ref force); //note - possible optimization
        }
    }

    public class Explosion : IForceGenerator
    {
        /// <summary>
        /// Elapsed time from the explosion
        /// </summary>
        float ElapsedTime;
        
        /// <summary>
        /// Source of the explosion
        /// </summary>
        public Vector3 ExplosionPoint;

        /// <summary>
        /// Minimal radius for initial implosion
        /// </summary>
        public float ImplosionMinRadius;
        /// <summary>
        /// Maximalradius for initial implosion
        /// </summary>
        public float ImplosionMaxRadius;
        /// <summary>
        /// Force of implosion
        /// </summary>
        public float ImplosionForce;
        /// <summary>
        /// Speed of shockwave
        /// </summary>
        public float ShockWaveSpeed;
        /// <summary>
        /// Thickness of shockwave (force modifier for distance)
        /// </summary>
        public float ShockWaveThickness;

        /// <summary>
        /// Maximal force at the center of explosion
        /// </summary>
        public float PeakConcussionForce;
        /// <summary>
        /// Time of concussion wave
        /// </summary>
        public float ConcussionDuration;
        /// <summary>
        /// Maximal force in convection chimney
        /// </summary>
        public float PeakConvectionForce;
        /// <summary>
        /// Time of convection wave
        /// </summary>
        public float ConvectionDuration;
        /// <summary>
        /// Radius of convection chimney (in x/y plane)
        /// </summary>
        public float ChimneyRadius;
        /// <summary>
        /// Height of the convection chimney
        /// </summary>
        public float ChimneyHeight;


        /// <summary>
        /// Default constructor
        /// </summary>
        public Explosion()
        {
            DefaultSettings(Vector3.Zero);
        }

        /// <summary>
        /// Sets reasonable default settings
        /// </summary>
        /// <param name="position"></param>
        public void DefaultSettings(Vector3 position)
        {
            ExplosionPoint = position;
            //unfinished
        }

        /// <summary>
        /// Update node by time
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="time">elapsed time</param>
        public virtual void Update(RigidBody node, float time)
        {
            if (node.IsStaticObject)
                return;

            ElapsedTime += time;

            if ((ElapsedTime > ConvectionDuration) && (ElapsedTime > ConcussionDuration))
                return;
            //unfinished
        }
    
    }



}
