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

namespace Framefield.Core.IDda3fbf4a_e0f6_44d0_9a6a_8f5eb319fedb
{
    public class Class_Replicate : 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
        }
        //<<< _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;
            //<<< _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 = new Vector3((float) (rand.NextDouble() - 0.5)*Radius*StretchX,
                                            (float) (rand.NextDouble() - 0.5)*Radius*StretchY,
                                            (float) (rand.NextDouble() - 0.5)*Radius*StretchZ);
                            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 l1 = i%(int) (Layer.X*Layer.Y);
                            float l2 = i%(int) Layer.Y;
                            float a = (i - l1)/Count;
                            t = new Vector3((float) (Math.Sin(Math.PI*2*a)*(Radius + l1*Stretch.X)),
                                            (l2/Layer.Y - 0.5f)*Stretch.Y,
                                            (float) (Math.Cos(Math.PI*2*a)*(Radius + l1*Stretch.Z)));
                            break;
                        }
                        //
                        case 3:
                        {
                            //Logger.Warn(this, "Plane Shape not implemented");
                            break;
                        }
                    }

                    if (NoiseAmount != 0)
                    {
                        _noiseTime = NoiseTime;
                        _frequency = 1/NoiseScale;
                        var noiseOffset = new Vector3(getNoise(t.X / Radius - 6.3f), getNoise(t.Y / Radius + 9.3f), getNoise((t.Z - 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() * (OffsetRandomX + Scatter),
                                                                OffsetY + (float)rand.NextDouble() * (OffsetRandomY + Scatter),
                                                                OffsetZ + (float)rand.NextDouble() * (OffsetRandomZ + Scatter)));
                    transform = t2*transform;

                    if (Orientation == 0)
                    {
                        if (t.Length() > 0)
                        {
                            var dir = -t;
                            dir.Normalize();
                            var helperDir = new Vector3(1, 0, 0);
                            if (Math.Abs(dir.Y) < 0.1 && Math.Abs(dir.Z) < 0.1)
                                helperDir = new Vector3(0, 1, 0);
                            var yAxis = Vector3.Cross(dir, helperDir);
                            yAxis.Normalize();
                            var xAxis = Vector3.Cross(yAxis, dir);
                            xAxis.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;
        }

        #endregion

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

