using System;
using System.Collections.Generic;
using SharpDX;

namespace Framefield.Core.ID864afd1c_7dfb_4bdd_92c1_66ad92249951
{
    public class Class_ReplicateMoths : OperatorPart.Function
    {
        //>>> _inputids
        private enum InputId
        {
            SceneInput = 0,
            Count = 1,
            Radius = 2,
            StretchX = 3,
            StretchY = 4,
            StretchZ = 5,
            Shape = 6,
            LayerX = 7,
            LayerY = 8,
            Scatter = 9,
            OffsetX = 10,
            OffsetY = 11,
            OffsetZ = 12,
            OffsetRandomX = 13,
            OffsetRandomY = 14,
            OffsetRandomZ = 15,
            NoiseTime = 16,
            NoiseAmount = 17,
            NoiseScale = 18,
            Size = 19,
            SizeRandom = 20,
            Orientation = 21,
            RotateX = 22,
            RotateY = 23,
            RotateZ = 24,
            RotateRandomX = 25,
            RotateRandomY = 26,
            RotateRandomZ = 27,
            Seed = 28,
            IndexVariableName = 29
        }
        //<<< _inputids
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            //>>> _params
            var SceneInput = inputs[(int)InputId.SceneInput];
            var Count = inputs[(int)InputId.Count].Eval(context).Value;
            var Radius = inputs[(int)InputId.Radius].Eval(context).Value;
            var StretchX = inputs[(int)InputId.StretchX].Eval(context).Value;
            var StretchY = inputs[(int)InputId.StretchY].Eval(context).Value;
            var StretchZ = inputs[(int)InputId.StretchZ].Eval(context).Value;
            var Stretch = new Vector3(StretchX, StretchY, StretchZ);
            var Shape = (int) inputs[(int)InputId.Shape].Eval(context).Value;
            var LayerX = inputs[(int)InputId.LayerX].Eval(context).Value;
            var LayerY = inputs[(int)InputId.LayerY].Eval(context).Value;
            var Layer = new Vector2(LayerX, LayerY);
            var Scatter = inputs[(int)InputId.Scatter].Eval(context).Value;
            var OffsetX = inputs[(int)InputId.OffsetX].Eval(context).Value;
            var OffsetY = inputs[(int)InputId.OffsetY].Eval(context).Value;
            var OffsetZ = inputs[(int)InputId.OffsetZ].Eval(context).Value;
            var Offset = new Vector3(OffsetX, OffsetY, OffsetZ);
            var OffsetRandomX = inputs[(int)InputId.OffsetRandomX].Eval(context).Value;
            var OffsetRandomY = inputs[(int)InputId.OffsetRandomY].Eval(context).Value;
            var OffsetRandomZ = inputs[(int)InputId.OffsetRandomZ].Eval(context).Value;
            var OffsetRandom = new Vector3(OffsetRandomX, OffsetRandomY, OffsetRandomZ);
            var NoiseTime = inputs[(int)InputId.NoiseTime].Eval(context).Value;
            var NoiseAmount = inputs[(int)InputId.NoiseAmount].Eval(context).Value;
            var NoiseScale = inputs[(int)InputId.NoiseScale].Eval(context).Value;
            var Size = inputs[(int)InputId.Size].Eval(context).Value;
            var SizeRandom = inputs[(int)InputId.SizeRandom].Eval(context).Value;
            var Orientation = (int) inputs[(int)InputId.Orientation].Eval(context).Value;
            var RotateX = inputs[(int)InputId.RotateX].Eval(context).Value;
            var RotateY = inputs[(int)InputId.RotateY].Eval(context).Value;
            var RotateZ = inputs[(int)InputId.RotateZ].Eval(context).Value;
            var Rotate = new Vector3(RotateX, RotateY, RotateZ);
            var RotateRandomX = inputs[(int)InputId.RotateRandomX].Eval(context).Value;
            var RotateRandomY = inputs[(int)InputId.RotateRandomY].Eval(context).Value;
            var RotateRandomZ = inputs[(int)InputId.RotateRandomZ].Eval(context).Value;
            var RotateRandom = new Vector3(RotateRandomX, RotateRandomY, RotateRandomZ);
            var Seed = inputs[(int)InputId.Seed].Eval(context).Value;
            var IndexVariableName = inputs[(int)InputId.IndexVariableName].Eval(context).Text;
            //<<< _params        

            if (SceneInput.Connections.Count == 0)
            {
                return context;
            }

            const float toRad = (float) Math.PI/180f;

            var prevTransform = context.ObjectTWorld;
            var rand = new Random((int) Seed);
            Layer.X = Math.Max(1, (int) Layer.X);
            Layer.Y = Math.Max(1, (int) Layer.Y);

            for (var i = 0; i < Count; ++i)
            {
                    Vector3 t = new Vector3();

                    
                    switch (Shape)
                    {
                        // Point
                        case 0:
                            t = Vector3.Normalize(new Vector3(
                                            (float) (rand.NextDouble() - 0.5)*Radius*StretchX,
                                            (float) (rand.NextDouble() - 0.5)*Radius*StretchY,
                                            (float) (rand.NextDouble() - 0.5)*Radius*StretchZ)) 
                               * (float)rand.NextDouble() * Radius;
                                      
                            break;
                        // Sphere
                        case 1:
                        {
                            var inc = Math.PI*(3 - Math.Sqrt(5));
                            var off = 2/(Count + 1);
                            //var y = i*off*Stretch.Y + Stretch.Z - 1 + (off/2);
                            var y = i*off*Stretch.Y - 1 + (off/2);
                            var r = Math.Sqrt(1 - y*y);
                            var phi = i*Stretch.X*inc;
                            t = new Vector3((float) (Math.Cos(phi)*r*Radius),
                                            (float) (y*Radius),
                                            (float) (Math.Sin(phi)*r*Radius));
                            break;
                        }
                        // Ring
                        case 2:
                        {
                            float itemsPerShell =  Count/(Layer.X);    // with even distribution                            
                            float indexInShell = i%itemsPerShell;
                            float shellIndex = (i-indexInShell)/itemsPerShell;
                            
                            float itemsPerRing = (itemsPerShell / Math.Max(1,Layer.Y));
                            float indexInRing = (int)(indexInShell % itemsPerRing);
                            float layerIndex = (int)(indexInShell / itemsPerRing);
                          
                            float a = indexInRing / itemsPerRing;
                            
                            t = new Vector3((float) (Math.Sin(Math.PI*2*a)*(Radius + shellIndex * Stretch.X)),
                                            Layer.Y <= 1 ? 0
                                                         : (layerIndex/(Layer.Y-1)  -0.5f) *Stretch.Y ,
                                            (float) (Math.Cos(Math.PI*2*a)*(Radius + shellIndex*Stretch.Z)));
                            break;
                        }
                        // Box (can also be used as a plane)
                        case 3:
                        {                            
                            var volume = Math.Abs(StretchX) * Math.Abs(StretchY) * Math.Abs(StretchZ);                            
                            var s1= new Vector3(Math.Abs(StretchX), Math.Abs(StretchY), Math.Abs(StretchZ));
                            
                            /*
                           var volumePerUnit = volume/Count;
                           
                           
                           
                           
                           var gridResolution = new Vector3 ((float)Math.Ceiling(gridResolution.X),(float)Math.Ceiling(gridResolution.Y),(float)Math.Ceiling(gridResolution.Z));
                           
                           
                           var numGridCells = gridResolution.X*gridResolution.Y*gridResolution.Z;
                           
            
                           
                           var gridCoords = new Vector3(i % gridResolution.X,
                                        (float)Math.Floor(i/gridResolution.X) % gridResolution.Y,
                                        (float)Math.Floor(i / (gridResolution.X * gridResolution.Y)) % gridResolution.Z);
                           
                           
                           
                           var gridCellSize = new Vector3((float)(s1.X/gridResolution.X), (float)(s1.Y/gridResolution.Y), (float)(s1.Z/gridResolution.Z));
                    
                           
                           t = 0.5f*gridCellSize + gridCellSize*gridCoords - 0.5f*Stretch;
                           */
                            
                            
                            s1.Normalize();
                            var a= s1.Y/s1.X;
                            var b= s1.Z/s1.X;
                            
                            var xx = (float)Math.Pow( 1/ (a*b), 1.0/3.0);
                            var s2 = s1 * (xx/s1.X);

                            var edgeCount= Math.Pow(Count,1.0/3.0);

                            var itemsX= Math.Max(1, edgeCount * s2.X);
                            var itemsY= Math.Max(1, edgeCount * s2.Y);
                            var itemsZ= Math.Max(1, edgeCount * s2.Z);
                            //var itemsX= Math.Max(1, Math.Floor(edgeCount * s2.X));
                            //var itemsY= Math.Max(1, Math.Floor(edgeCount * s2.Y));
                            //var itemsZ= Math.Max(1, Math.Floor(edgeCount * s2.Z));
                            
                            var x = (int)(i % itemsX)          / itemsX;
                            var y = (int)((i/itemsX) % itemsY) / itemsY;
                            var z = (int)((i/itemsX/itemsY))   / itemsZ;
                            
                            
                            t = new Vector3((float) (x),
                                            (float) (y),
                                            (float) (z)) * Stretch - 0.5f * Stretch;
                            
                            break;
                        }
                    }

/*                    if (NoiseAmount != 0)
                    {
                        _noiseTime = NoiseTime;
                        _frequency = 1/NoiseScale;
                        var noiseOffset = new Vector3(
                            getNoise(t.X / Radius - 36.1f), 
                            getNoise(t.Y / Radius + 1.0f), 
                            getNoise((t.Z + 12.1f))
                        );
                        t += noiseOffset * NoiseAmount;
                    }*/


                    if (NoiseAmount != 0)
                    {
                        _noiseTime = NoiseTime;
                        _frequency = 1/NoiseScale;
                        var noiseOffset = new Vector3(getNoise(t.X / Radius - 6.3f), getNoise(t.Z / Radius + 9.3f), getNoise((t.Y - 0.3f)));
                        t += noiseOffset * NoiseAmount;
                    }

                    float s = Size + (float)rand.NextDouble() * SizeRandom;
                    var scale = new Vector3(s, s, s);
                    
                    //Matrix rotation;
                    Matrix transform;
                    Matrix.RotationYawPitchRoll(Rotate.Y * toRad + (float)(rand.NextDouble()) * RotateRandom.Y * toRad,
                                                Rotate.X * toRad + (float)(rand.NextDouble()) * RotateRandom.X * toRad,
                                                Rotate.Z * toRad + (float)(rand.NextDouble()) * RotateRandom.Z * toRad,
                                                out transform);
                    
                                                                    
                    var tScale = Matrix.Scaling( scale.X, scale.Y, scale.Z);
                    transform= tScale * transform;
                    var t2= Matrix.Translation(new Vector3(OffsetX + (float)(rand.NextDouble() -0.5f) * (OffsetRandomX + Scatter),
                                                           OffsetY + (float)(rand.NextDouble() -0.5f) * (OffsetRandomY + Scatter),
                                                           OffsetZ + (float)(rand.NextDouble() -0.5f) * (OffsetRandomZ + Scatter)));
                    transform = t2*transform;

                    if (Orientation == 0)
                    {
                        if (t.Length() > 0)
                        {
                            var dir = -t;
                            dir.Normalize();
                            var helperDir = new Vector3(0, -1, 0);
                            if (Math.Abs(dir.X) < 0.001 && Math.Abs(dir.Z) < 0.001)
                                helperDir = new Vector3(0, 0, 1);
                            var xAxis = Vector3.Cross(dir, helperDir);
                            xAxis.Normalize();
                            var yAxis = Vector3.Cross(-xAxis, dir);
                            yAxis.Normalize();
                            var m = Matrix.Identity;
                            m.Row1 = new Vector4(xAxis, 0);
                            m.Row2 = new Vector4(yAxis, 0);
                            m.Row3 = new Vector4(dir, 0);
                            transform *= m;
                        }
                    }
                    else if (Orientation == 2)
                    {
                        //if (Shape == 2)
                        //{
                        //    var rotationAroundCenter = Matrix.Identity;
                        //    Matrix.RotationY((float)(2 * Math.PI * i / Count), out rotationAroundCenter);
                        //    transform *= rotationAroundCenter;
                        //}
                        var cameraToWorld = context.WorldToCamera;
                        cameraToWorld.Invert();
                        var newObjectToWorld = context.ObjectTWorld * cameraToWorld;
                        newObjectToWorld.Row4 = context.ObjectTWorld.Row4;
                        transform *=    newObjectToWorld;                     
                        
                    }

                    transform *= Matrix.Transformation(new Vector3(), new Quaternion(), new Vector3(1,1,1), new Vector3(), new Quaternion(), t);
                    
                    
                    transform *= prevTransform; 
                context.ObjectTWorld = transform*prevTransform;
                SceneInput.Connections[i%SceneInput.Connections.Count].Eval(context);
                context.ObjectTWorld = prevTransform;
                
                //SceneInput.Connections[i%SceneInput.Connections.Count].Eval(context);
                //context.ObjectTWorld = prevTransform;
            }

            context.ObjectTWorld = prevTransform;

            return context;
        }

        #region helpers

        public float Noise(int x, int seed)
        {
            int n = x + seed;
            n = (n << 13) ^ n;
            return (float) (1.0 - ((n*(n*n*15731 + 789221) + 1376312589) & 0x7fffffff)/1073741824.0);
        }

        public float Lerp(float a, float b, float t)
        {
            return a + t*(b - a);
        }

        public float Fade(float t)
        {
            return t*t*t*(t*(t*6 - 15) + 10);
        }

        public float Interpolate(float a, float b, float t)
        {
            float ft = t*3.1415927f;
            float f = (float) (1.0 - Math.Cos(ft))*0.5f;
            return a*(1.0f - f) + b*f;
        }

        private float getNoise(float value)
        {
            float noiseSum = 0.0f;
            value *= _frequency;
            value += _noiseTime+10000;  // Noise is not static between -0.0001 and 0.0001


            noiseSum = Lerp(Noise((int) value, _seed),
                            Noise((int) value + 1, _seed),
                            Fade(value - (float) Math.Floor(value)));
            //Logger.Info("{0:0.000} -> {1:0.00}", value, noiseSum);                            
            return noiseSum;
        }
        
        //private Vector getNoise3(float value)
        //{
        //}        

        #endregion

        private int _seed = 0;
        private float _frequency = 1;
        private float _noiseTime;
    }
}

