using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisiMath;
using VisiMath.Geometry;
using VisimBox.Scene;
using VisimBox;
using VisimBox.Renderer;
using VisimBox.Renderer.RenderObjects;
using VisimBox.Physics;


namespace EarthAttacks
{
    public class EarthAttack : Node, IContactGenerator
    {
        public VisimBox.Physics.PhysicalScene PhysScene;

        int EarthCount = 20;
        float EarthRadius = 1;
        float TargetRadius = 10;

        float EarthTargetRadius;
        float EarthTargetRadiusSq;
        float EarthRadiuses;
        float EarthRadiusesSq;

        protected List<PhysSphere> EarthList;
        protected PhysSphere Asteroid;

        public EarthAttack()
            : this(null)
        {
        }

        public EarthAttack(Node owner) : base(owner)
        {
            Construct();
        }

        private void Construct()
        {
            EarthTargetRadius = (EarthRadius + TargetRadius);
            EarthTargetRadiusSq = EarthTargetRadius * EarthTargetRadius;

            EarthRadiuses = 2 * EarthRadius;
            EarthRadiusesSq = EarthRadiuses * EarthRadiuses;

            //add skybox
            string[] textures = new string[6]{"Textures/Skybox/Space/left.jpg", "Textures/Skybox/Space/right.jpg",
                "Textures/Skybox/Space/front.jpg", "Textures/Skybox/Space/back.jpg",
                "Textures/Skybox/Space/top.jpg", "Textures/Skybox/Space/top.jpg"};

            AddNode(new SkyBoxNode(null, textures, new Vector3(200, 200, 200)), "skybox");

            var light = VisimEngine.Instance.Renderer.LightMan.AddDirectional("light", new Vector3(0, 0, 1), new ColorByte(20, 20, 20), ColorByte.White);

            var lnode = new LightNode(light);
            lnode.Trans = new QTransformation();
            lnode.Trans.Yaw(1.4f);
            lnode.UpdateLight(0);
            AddNode(lnode, "lightnode1");

            light = VisimEngine.Instance.Renderer.LightMan.AddDirectional("light2", new Vector3(1, 1, 0), ColorByte.White);

            lnode = new LightNode(light);
            lnode.Trans = new QTransformation();
            lnode.Trans.Pitch(1.77f);
            lnode.UpdateLight(0);
            AddNode(lnode, "lightnode2");


            
            //construct scene
            
            PhysScene = new VisimBox.Physics.PhysicalScene(50);

            MeshManager meshman = VisimEngine.Instance.Renderer.MeshMan;
            MaterialManager matman = VisimEngine.Instance.Renderer.MaterialMan;

            //create earth sphere

            EarthList = new List<PhysSphere>(EarthCount);
            Sphere EarthSphereBound;

            var matearth = matman.Add("earth", "Textures/Planets/EarthMap.png", new VisimBox.Renderer.StandardColorValues(ColorByte.White));

            var sph = GeoFactory.CreateSphere<TexturedVertex>(new Vector3(0, 0, 0), EarthRadius, out EarthSphereBound, 0.5f);
            var sphmesh = VisimEngine.Instance.Renderer.MeshMan.Add(sph, "sphere");

            RandomGenerator rg = new RandomGenerator();

            Vector3 StartPos = new Vector3(50, 0, 0);

            Vector3 EarthSpace = new Vector3(80, 80, 30);

            for (int i = 0; i < EarthCount; i++)
            {
                Vector3 pos = StartPos;

                while (pos.DistanceSquared(ref StartPos) < 150)
                {
                    pos = StartPos + rg.GetVector3(ref EarthSpace);
                }
                var earth = new PhysSphere(this, EarthSphereBound, sphmesh, matearth, pos, rg.GetVector3(1, true), 1);
                EarthList.Add(earth);
                AddNode(earth, "Earth#" + i.ToString());
                PhysScene.RegisterBody(earth);
            }
            
            //create target

            var matasteroid = matman.Add("MarsGround", "Textures/Ground/Mars.jpg", new VisimBox.Renderer.StandardColorValues(ColorByte.White));

            Sphere TargetBound;

            var astsph = GeoFactory.CreateSphere<TexturedVertex>(new Vector3(0, 0, 0), TargetRadius, out TargetBound, 0.5f);
            var astmesh = VisimEngine.Instance.Renderer.MeshMan.Add(astsph, "sphere2");

            Asteroid = new PhysSphere(this, TargetBound, astmesh, matasteroid, StartPos, new Vector3(0,0,0.5f), 1000);
            AddNode(Asteroid, "Asteroid");
            PhysScene.RegisterBody(Asteroid);

            PhysScene.RegisterContactGenerator(this);
        }

        protected float[,] Distances;
        protected Vector3[,] Directions;

        public override bool TimeTick(double time)
        {
            if (time > 0.1f)
                time = 0.1f;
            
            float awaymod = 0.0f * (float)time;
            float flockmod = 0.0001f * (float)time;
            float targetmod = 0.001f * (float)time;

            //move camera if too far away
            ICamera cam = VisimEngine.Instance.Renderer.MainContext.MainCamera;

            if (cam != null)
            {

                Vector3 position = cam.Position;

                Vector3 astpos = Asteroid.Position;

                Vector3 dir = astpos - position;

                float cameradist = dir.LengthSquared;

                if (cameradist > 100 * 100)
                {
                    dir.Divide(MathTools.Sqrt(cameradist));
                    cam.Move(dir);
                }
            }
            //check asteroid speed and damp it if it's too fast

            Vector3 asspeed = Asteroid.Velocity;
            float absspeed = asspeed.Length;

            if (absspeed > 10)
            {
                asspeed.Divide(absspeed - 10);
                Asteroid.Velocity = asspeed;
            }




            if (true)
            {
                //calculate distances to all spheres
                if (Distances == null)
                {
                    Distances = new float[EarthCount, EarthCount + 1];
                    Directions = new Vector3[EarthCount, EarthCount + 1];
                }

                for (int i = 0; i < EarthCount; i++)
                {
                    //calculate distance from earth to all earths
                    var first = EarthList[i];
                    Vector3 pos1 = first.Position;

                    for (int j = i+1; j < EarthCount; j++)
                    {
                        var second = EarthList[j];

                        Vector3 pos2 = second.Position;
                        Vector3 direction = pos2 - pos1;
                        float dist = direction.LengthSquared;
                        float invdist = 1 / dist;

                        //if too close, throw away

                        if (dist < 10)
                        {
                            first.AddForce(new Vector3(ref direction, -awaymod*dist));
                            second.AddForce(new Vector3(ref direction, awaymod * dist));
                        }
                        else
                        {
                            //otherwise pull closer
                            first.AddForce(new Vector3(ref direction, flockmod * dist));
                            second.AddForce(new Vector3(ref direction, -flockmod * dist));                            
                        }
                        Distances[i, j] = dist;
                        Directions[i, j] = direction;
                    }

                    Vector3 aspos = Asteroid.Position;
                    Vector3 asdir = aspos - pos1;
                    float asdis = asdir.LengthSquared;
                    Distances[i, EarthCount] = asdis;
                    Directions[i, EarthCount] = asdir;

                    if (asdis > 1.1f*EarthTargetRadiusSq)
                        first.AddForce(new Vector3(ref asdir, targetmod * Distances[i, EarthCount]));
                }                
                
                PhysScene.TimeTick((float)time);
            }
            return base.TimeTick(time);
        }

        /// <summary>
        /// Physical Scene callback - node should check for any collisions and save them at physical contact at index and beyond
        /// Note: node should NOT increase the contact list size (HW limit)
        /// </summary>
        /// <param name="List">List to save contact into</param>
        /// <param name="index">Starting index</param>
        /// <returns>Number of added contacts</returns>
        public virtual int AddContact(List<PhysicalContact> List, int index)
        {
            int rval = 0;
            for (int i = 0; i < EarthCount; i++)
            {
                if (List.Capacity == index)
                    return rval;

                float radiusessq;
                float radiuses;
                for (int j = i + 1; j < EarthCount + 1; j++)
                {
                    if (j == EarthCount)
                    {
                        radiuses = EarthTargetRadius;
                        radiusessq = EarthTargetRadiusSq;
                    }
                    else
                    {
                        radiuses = EarthRadiuses;
                        radiusessq = EarthRadiusesSq;
                    }

                    float dist = Distances[i, j];

                    if ((dist > 0) && (dist < radiusessq))
                    {
                        float distreal = MathTools.Sqrt(dist);
                        Vector3 normal = new Vector3(Directions[i, j], -1.0f / distreal);
                        PhysicalContact pl = List[index];
                        pl.ContactNormal = normal;
                        pl.ContactPoint1 = EarthList[i].Position;
                        pl.ContactPoint1.AddScaled(ref Directions[i, j], 0.5f);
                        pl.Penetration = radiuses - distreal;
                        
                        if (j == EarthCount)
                            pl.SetBodyData(EarthList[i], Asteroid, 0.2f, 0.01f);
                        else
                            pl.SetBodyData(EarthList[i], EarthList[j], 0.2f, 0.01f);
                        rval++;
                        index++;
                    }
                }
            }
            
            return rval;
        }

    }
}
