using System;
using System.Collections.Generic;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
namespace FarseerPhysics.Factories
{
    /// 
    /// An easy to use manager for creating paths.
    /// 
    public static class PathManager
    {
        #region LinkType enum
        public enum LinkType
        {
            Revolute,
            Slider
        }
        #endregion
        //Contributed by Matthew Bettcher
        /// 
        /// Convert a path into a set of edges and attaches them to the specified body.
        /// Note: use only for static edges.
        /// 
        /// The path.
        /// The body.
        /// The subdivisions.
        public static void ConvertPathToEdges(Path path, Body body, int subdivisions)
        {
            Vertices verts = path.GetVertices(subdivisions);
            if (path.Closed)
            {
                LoopShape loop = new LoopShape(verts);
                body.CreateFixture(loop);
            }
            else
            {
                for (int i = 1; i < verts.Count; i++)
                {
                    body.CreateFixture(new EdgeShape(verts[i], verts[i - 1]));
                }
            }
        }
        /// 
        /// Convert a closed path into a polygon.
        /// Convex decomposition is automatically performed.
        /// 
        /// The path.
        /// The body.
        /// The density.
        /// The subdivisions.
        public static void ConvertPathToPolygon(Path path, Body body, float density, int subdivisions)
        {
            if (!path.Closed)
                throw new Exception("The path must be closed to convert to a polygon.");
            List verts = path.GetVertices(subdivisions);
            List decomposedVerts = EarclipDecomposer.ConvexPartition(new Vertices(verts));
            //List decomposedVerts = BayazitDecomposer.ConvexPartition(new Vertices(verts));
            foreach (Vertices item in decomposedVerts)
            {
                body.CreateFixture(new PolygonShape(item, density));
            }
        }
        /// 
        /// Duplicates the given Body along the given path for approximatly the given copies.
        /// 
        /// The world.
        /// The path.
        /// The shapes.
        /// The type.
        /// The copies.
        /// 
        /// 
        public static List EvenlyDistributeShapesAlongPath(World world, Path path, IEnumerable shapes,
                                                                 BodyType type, int copies, object userData)
        {
            List centers = path.SubdivideEvenly(copies);
            List bodyList = new List();
            for (int i = 0; i < centers.Count; i++)
            {
                Body b = new Body(world);
                // copy the type from original body
                b.BodyType = type;
                b.Position = new Vector2(centers[i].X, centers[i].Y);
                b.Rotation = centers[i].Z;
                foreach (Shape shape in shapes)
                {
                    b.CreateFixture(shape, userData);
                }
                bodyList.Add(b);
            }
            return bodyList;
        }
        public static List EvenlyDistributeShapesAlongPath(World world, Path path, IEnumerable shapes,
                                                                 BodyType type, int copies)
        {
            return EvenlyDistributeShapesAlongPath(world, path, shapes, type, copies, null);
        }
        /// 
        /// Duplicates the given Body along the given path for approximatly the given copies.
        /// 
        /// The world.
        /// The path.
        /// The shape.
        /// The type.
        /// The copies.
        /// The user data.
        /// 
        public static List EvenlyDistributeShapesAlongPath(World world, Path path, Shape shape, BodyType type,
                                                                 int copies, object userData)
        {
            List shapes = new List(1);
            shapes.Add(shape);
            return EvenlyDistributeShapesAlongPath(world, path, shapes, type, copies, userData);
        }
        public static List EvenlyDistributeShapesAlongPath(World world, Path path, Shape shape, BodyType type,
                                                                 int copies)
        {
            return EvenlyDistributeShapesAlongPath(world, path, shape, type, copies, null);
        }
        //TODO: Comment better
        /// 
        /// Moves the body on the path.
        /// 
        /// The path.
        /// The body.
        /// The time.
        /// The strength.
        /// The time step.
        public static void MoveBodyOnPath(Path path, Body body, float time, float strength, float timeStep)
        {
            Vector2 destination = path.GetPosition(time);
            Vector2 positionDelta = body.Position - destination;
            Vector2 velocity = (positionDelta / timeStep) * strength;
            body.LinearVelocity = -velocity;
        }
        /// 
        /// Attaches the bodies with revolute joints.
        /// 
        /// The world.
        /// The bodies.
        /// The local anchor A.
        /// The local anchor B.
        /// if set to true [connect first and last].
        /// if set to true [collide connected].
        public static List AttachBodiesWithRevoluteJoint(World world, List bodies,
                                                                        Vector2 localAnchorA,
                                                                        Vector2 localAnchorB, bool connectFirstAndLast,
                                                                        bool collideConnected)
        {
            List joints = new List(bodies.Count + 1);
            for (int i = 1; i < bodies.Count; i++)
            {
                RevoluteJoint joint = new RevoluteJoint(bodies[i], bodies[i - 1], localAnchorA, localAnchorB);
                joint.CollideConnected = collideConnected;
                world.AddJoint(joint);
                joints.Add(joint);
            }
            if (connectFirstAndLast)
            {
                RevoluteJoint lastjoint = new RevoluteJoint(bodies[0], bodies[bodies.Count - 1], localAnchorA,
                                                            localAnchorB);
                lastjoint.CollideConnected = collideConnected;
                world.AddJoint(lastjoint);
                joints.Add(lastjoint);
            }
            return joints;
        }
        /// 
        /// Attaches the bodies with revolute joints.
        /// 
        /// The world.
        /// The bodies.
        /// The local anchor A.
        /// The local anchor B.
        /// if set to true [connect first and last].
        /// if set to true [collide connected].
        /// Minimum length of the slider joint.
        /// Maximum length of the slider joint.
        /// 
        public static List AttachBodiesWithSliderJoint(World world, List bodies, Vector2 localAnchorA,
                                                                    Vector2 localAnchorB, bool connectFirstAndLast,
                                                                    bool collideConnected, float minLength,
                                                                    float maxLength)
        {
            List joints = new List(bodies.Count + 1);
            for (int i = 1; i < bodies.Count; i++)
            {
                SliderJoint joint = new SliderJoint(bodies[i], bodies[i - 1], localAnchorA, localAnchorB, minLength,
                                                    maxLength);
                joint.CollideConnected = collideConnected;
                world.AddJoint(joint);
                joints.Add(joint);
            }
            if (connectFirstAndLast)
            {
                SliderJoint lastjoint = new SliderJoint(bodies[0], bodies[bodies.Count - 1], localAnchorA, localAnchorB,
                                                        minLength, maxLength);
                lastjoint.CollideConnected = collideConnected;
                world.AddJoint(lastjoint);
                joints.Add(lastjoint);
            }
            return joints;
        }
    }
}