using System;
using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct3D11;
using Framefield.Core.Rendering;
using Buffer = SharpDX.Direct3D11.Buffer;

namespace Framefield.Core.ID8eac3568_94ea_4207_8e5d_05cfeea0a9dd
{

    class Force {
        public Vector3 Position {get;set;}
        public float Scatter {get;set;}
        public float Push {get;set;}
        public float Range {get;set;}
        public float FallOff {get;set;}
        public float Shrink {get;set;}
        
        public Vector3 DistortPoint(Vector3 p, Vector3 pCenter, Random rand) 
        {
            var direction = (pCenter-Position);
            var distance = direction.Length();            
            var x =   Utilities.Clamp(( Range - distance) / FallOff, 0, 1);
            var influence = x*x*(3 - 2*x);  // SmoothStep
            
            if(Shrink !=0) {
                var shrinkScale = influence * Shrink;
                p+= (pCenter-p)*shrinkScale;
            }
            
            p+= direction/distance * influence * (Scatter * (float)rand.NextDouble() + Push)  ;                                    
            return p;
        }        
    }


    public class Class_ScatteredTorus : OperatorPart.Function,
                               Framefield.Core.OperatorPartTraits.IMeshSupplier
    {
       
    
    // >>> _inputids
    private enum InputId
    {
        RadiusMajor = 0,
        RadiusMinor = 1,
        TesselateMajor = 2,
        TesselateMinor = 3,
        SpinMajor = 4,
        SpinMinor = 5,
        PortionMajor = 6,
        PortionMinor = 7,
        ColorR = 8,
        ColorG = 9,
        ColorB = 10,
        ColorA = 11,
        SmoothAngle = 12,
        ForceAPositionX = 13,
        ForceAPositionY = 14,
        ForceAPositionZ = 15,
        ForceARangeSize = 16,
        ForceARangeFallOff = 17,
        ForceAPush = 18,
        ForceAScatter = 19,
        ForceAShrink = 20,
        ForceBPositionX = 21,
        ForceBPositionY = 22,
        ForceBPositionZ = 23,
        ForceBRangeSize = 24,
        ForceBRangeFallOff = 25,
        ForceBPush = 26,
        ForceBScatter = 27,
        ForceBShrink = 28
    }
    // <<< _inputids
        // IMeshSupplier
        public void AddMeshesTo(ICollection<Mesh> meshes)
        {
            UpdateMesh(new OperatorPartContext(), OperatorPart.Connections);
            meshes.Add(_torus);
        }

        public override void Dispose() 
        {
            _torus.Dispose();
        }



        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            // >>> _params
            var RadiusMajor = inputs[(int)InputId.RadiusMajor].Eval(context).Value;
            var RadiusMinor = inputs[(int)InputId.RadiusMinor].Eval(context).Value;
            var Radius = new Vector2(RadiusMajor, RadiusMinor);
            var TesselateMajor = inputs[(int)InputId.TesselateMajor].Eval(context).Value;
            var TesselateMinor = inputs[(int)InputId.TesselateMinor].Eval(context).Value;
            var Tesselate = new Vector2(TesselateMajor, TesselateMinor);
            var SpinMajor = inputs[(int)InputId.SpinMajor].Eval(context).Value;
            var SpinMinor = inputs[(int)InputId.SpinMinor].Eval(context).Value;
            var Spin = new Vector2(SpinMajor, SpinMinor);
            var PortionMajor = inputs[(int)InputId.PortionMajor].Eval(context).Value;
            var PortionMinor = inputs[(int)InputId.PortionMinor].Eval(context).Value;
            var Portion = new Vector2(PortionMajor, PortionMinor);
            var ColorR = inputs[(int)InputId.ColorR].Eval(context).Value;
            var ColorG = inputs[(int)InputId.ColorG].Eval(context).Value;
            var ColorB = inputs[(int)InputId.ColorB].Eval(context).Value;
            var ColorA = inputs[(int)InputId.ColorA].Eval(context).Value;
            var Color = new Color4(ColorR, ColorG, ColorB, ColorA);
            var SmoothAngle = inputs[(int)InputId.SmoothAngle].Eval(context).Value;
            var ForceAPositionX = inputs[(int)InputId.ForceAPositionX].Eval(context).Value;
            var ForceAPositionY = inputs[(int)InputId.ForceAPositionY].Eval(context).Value;
            var ForceAPositionZ = inputs[(int)InputId.ForceAPositionZ].Eval(context).Value;
            var ForceAPosition = new Vector3(ForceAPositionX, ForceAPositionY, ForceAPositionZ);
            var ForceARangeSize = inputs[(int)InputId.ForceARangeSize].Eval(context).Value;
            var ForceARangeFallOff = inputs[(int)InputId.ForceARangeFallOff].Eval(context).Value;
            var ForceARange = new Vector2(ForceARangeSize, ForceARangeFallOff);
            var ForceAPush = inputs[(int)InputId.ForceAPush].Eval(context).Value;
            var ForceAScatter = inputs[(int)InputId.ForceAScatter].Eval(context).Value;
            var ForceAShrink = inputs[(int)InputId.ForceAShrink].Eval(context).Value;
            var ForceA = new Vector3(ForceAPush, ForceAScatter, ForceAShrink);
            var ForceBPositionX = inputs[(int)InputId.ForceBPositionX].Eval(context).Value;
            var ForceBPositionY = inputs[(int)InputId.ForceBPositionY].Eval(context).Value;
            var ForceBPositionZ = inputs[(int)InputId.ForceBPositionZ].Eval(context).Value;
            var ForceBPosition = new Vector3(ForceBPositionX, ForceBPositionY, ForceBPositionZ);
            var ForceBRangeSize = inputs[(int)InputId.ForceBRangeSize].Eval(context).Value;
            var ForceBRangeFallOff = inputs[(int)InputId.ForceBRangeFallOff].Eval(context).Value;
            var ForceBRange = new Vector2(ForceBRangeSize, ForceBRangeFallOff);
            var ForceBPush = inputs[(int)InputId.ForceBPush].Eval(context).Value;
            var ForceBScatter = inputs[(int)InputId.ForceBScatter].Eval(context).Value;
            var ForceBShrink = inputs[(int)InputId.ForceBShrink].Eval(context).Value;
            var ForceB = new Vector3(ForceBPush, ForceBScatter, ForceBShrink);
            // <<< _params

            try 
            {
                _forceA= new Force() {
                    Position= ForceAPosition,
                    Range = ForceARangeSize,
                    FallOff = ForceARangeFallOff,
                    Push = ForceAPush,
                    Scatter = ForceAScatter,
                    Shrink= ForceAShrink
                };
            
                _forceB= new Force() {
                    Position= ForceBPosition,
                    Range = ForceBRangeSize,
                    FallOff = ForceBRangeFallOff,
                    Push = ForceBPush,
                    Scatter = ForceBScatter,
                    Shrink= ForceBShrink
                };


                UpdateMesh(context, inputs);

                context.Renderer.SetupEffect(context);
                context.Renderer.Render(_torus, context);
            }
            catch (Exception ex) 
            {
                Logger.Error(this,"error: {0}", ex);
            }

            return context;
        }



        private void UpdateMesh(OperatorPartContext context, IList<OperatorPart> inputs)
        {
            if (_torus != null && !Changed) 
                return;

            var radiusMajor = inputs[0].Eval(context).Value;
            var radiusMinor = inputs[1].Eval(context).Value;

            var tessX = Math.Max(1, (int) inputs[2].Eval(context).Value);
            var tessY = Math.Max(1, (int) inputs[3].Eval(context).Value);

            var spinMajorInRad = Utilities.DegreeToRad(inputs[4].Eval(context).Value);
            var spinMinorInRad = Utilities.DegreeToRad(inputs[5].Eval(context).Value);

            var portionMajor = inputs[6].Eval(context).Value/360.0;
            var portionMinor = inputs[7].Eval(context).Value/360.0;

            var colorR = inputs[8].Eval(context).Value;
            var colorG = inputs[9].Eval(context).Value;
            var colorB = inputs[10].Eval(context).Value;
            var colorA = inputs[11].Eval(context).Value;
            var smoothAngle = inputs[(int)InputId.SmoothAngle].Eval(context).Value;

            Dispose();
            _rand = new Random(0);
            var color = new Vector4(colorR, colorG, colorB, colorA);
            var tangent = new Vector3(1.0f, 0.0f, 0.0f);
            var binormal = new Vector3(0.0f, 1.0f, 0.0f);

            var inputElements = new[]
                                    {
                                        new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                                        new InputElement("NORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 16, 0),
                                        new InputElement("COLOR", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 28, 0),
                                        new InputElement("TEXCOORD", 0, SharpDX.DXGI.Format.R32G32_Float, 44, 0),
                                        new InputElement("TANGENT", 0, SharpDX.DXGI.Format.R32G32B32_Float, 52, 0),
                                        new InputElement("BINORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 64, 0)
                                    };

            int numQuads = tessX*tessY;
            
            bool smoothMajor= portionMajor/(int)tessX > smoothAngle / 360 || portionMinor/(int)tessY > smoothAngle /360;
            

            const int attributesSize = 76;
            int numTriangles = numQuads*2;
            int streamSize = numTriangles*3*attributesSize;
            using (var vertexStream = new DataStream(streamSize, true, true))
            {
                double axisAngleFraction = portionMajor/tessX*2.0*Math.PI;
                double tubeAngleFraction = portionMinor/tessY*2.0*Math.PI;

                for (int z = 0; z < tessY; ++z)
                {
                    double tubeAngle = z*tubeAngleFraction + spinMinorInRad;
                    double tubePosition1Y = Math.Cos(tubeAngle)*radiusMinor;
                    double tubePosition1X = Math.Sin(tubeAngle)*radiusMinor;
                    double tubePosition2Y = Math.Cos(tubeAngle + tubeAngleFraction)*radiusMinor;
                    double tubePosition2X = Math.Sin(tubeAngle + tubeAngleFraction)*radiusMinor;

                    float v0 = (float) (z/(double) tessY);
                    float v1 = (float) ((z + 1)/(double) tessY);

                    for (int x = 0; x < tessX; ++x)
                    {
                        //float u0 = (float)(x / (tessX+1.0f)); 
                        //float u1 = (float)((x-1) / (tessX+1.0f));
                        float u0 = (float) ((x + 1)/(tessX*1.0f));
                        float u1 = (float) ((x)/(tessX*1.0f));

                        double axisAngle = x*axisAngleFraction + spinMajorInRad;

                        // calc the 4 vertices of quad
                        var p0 = new Vector3((float) (Math.Sin(axisAngle + axisAngleFraction)*(tubePosition2X + radiusMajor)),
                                             (float) (Math.Cos(axisAngle + axisAngleFraction)*(tubePosition2X + radiusMajor)), (float) tubePosition2Y);
                        var p1 = new Vector3((float) (Math.Sin(axisAngle)*(tubePosition2X + radiusMajor)),
                                             (float) (Math.Cos(axisAngle)*(tubePosition2X + radiusMajor)), (float) tubePosition2Y);
                        var p2 = new Vector3((float) (Math.Sin(axisAngle)*(tubePosition1X + radiusMajor)),
                                             (float) (Math.Cos(axisAngle)*(tubePosition1X + radiusMajor)), (float) tubePosition1Y);
                        var p3 = new Vector3((float) (Math.Sin(axisAngle + axisAngleFraction)*(tubePosition1X + radiusMajor)),
                                             (float) (Math.Cos(axisAngle + axisAngleFraction)*(tubePosition1X + radiusMajor)), (float) tubePosition1Y);

                        var pCenter= (p0+p1+p2+p3)/4;
                        
                        p0=_forceA.DistortPoint(p0,pCenter, _rand);
                        p1=_forceA.DistortPoint(p1,pCenter, _rand);
                        p2=_forceA.DistortPoint(p2,pCenter, _rand);
                        p3=_forceA.DistortPoint(p3,pCenter, _rand);
                        
                        
                        if(_forceB.Push !=0 || _forceB.Scatter !=0) 
                        {
                            p0=_forceB.DistortPoint(p0,pCenter, _rand);
                            p1=_forceB.DistortPoint(p1,pCenter, _rand);
                            p2=_forceB.DistortPoint(p2,pCenter, _rand);
                            p3=_forceB.DistortPoint(p3,pCenter, _rand);
                        }
                        
                        
                        var uv0 = new Vector2(u0, v1);
                        var uv1 = new Vector2(u1, v1);
                        var uv2 = new Vector2(u1, v0);
                        var uv3 = new Vector2(u0, v0);

                        var tubeCenter1 = new Vector3((float) Math.Sin(axisAngle), (float) Math.Cos(axisAngle), 0.0f)*radiusMajor;
                        var tubeCenter2 = new Vector3((float) Math.Sin(axisAngle + axisAngleFraction), (float) Math.Cos(axisAngle + axisAngleFraction), 0.0f)*
                                          radiusMajor;

                        var normal0 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p0-p1, p0-p2) :  p0 - tubeCenter2);
                        var normal1 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p1-p2, p1-p0) :  p1 - tubeCenter1);
                        var normal2 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p2-p0, p2-p1) :  p2 - tubeCenter1);
                        var normal3 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p3-p0, p3-p2) :  p3 - tubeCenter2);

                        Vector3 tangent0, binormal0;
                        MeshUtilities.CalcTBNSpace(p0, uv0, p1, uv1, p2, uv2, normal0, out tangent0, out binormal0);

                        Vector3 tangent1, binormal1;
                        MeshUtilities.CalcTBNSpace(p1, uv1, p0, uv0, p2, uv2, normal1, out tangent1, out binormal1);

                        Vector3 tangent2, binormal2;
                        MeshUtilities.CalcTBNSpace(p2, uv2, p1, uv1, p0, uv0, normal2, out tangent2, out binormal2);

                        Vector3 tangent3, binormal3;
                        MeshUtilities.CalcTBNSpace(p3, uv3, p2, uv2, p0, uv0, normal3, out tangent3, out binormal3);

                        // tri 1 vert 3
                        vertexStream.Write(new Vector4(p0, 1));
                        vertexStream.Write(normal0);
                        vertexStream.Write(color);
                        vertexStream.Write(uv0);
                        vertexStream.Write(tangent0);
                        vertexStream.Write(binormal0);

                        // tri 1 vert 2
                        vertexStream.Write(new Vector4(p1, 1));
                        vertexStream.Write(normal1);
                        vertexStream.Write(color);
                        vertexStream.Write(uv1);
                        vertexStream.Write(tangent1);
                        vertexStream.Write(binormal1);

                        // tri 1 vert 1
                        vertexStream.Write(new Vector4(p2, 1));
                        vertexStream.Write(normal2);
                        vertexStream.Write(color);
                        vertexStream.Write(uv2);
                        vertexStream.Write(tangent2);
                        vertexStream.Write(binormal2);

                        normal0 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p0-p2, p0-p3) :  p0 - tubeCenter2);
                        normal1 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p1-p2, p1-p0) :  p1 - tubeCenter1);
                        normal2 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p2-p3, p2-p0) :  p2 - tubeCenter1);
                        normal3 = Vector3.Normalize( smoothMajor ? Vector3.Cross( p3-p0, p3-p2) :  p3 - tubeCenter2);

                        // tri 2 vert 3
                        vertexStream.Write(new Vector4(p2, 1));
                        vertexStream.Write(normal2);
                        vertexStream.Write(color);
                        vertexStream.Write(uv2);
                        vertexStream.Write(tangent2);
                        vertexStream.Write(binormal2);

                        // tri 2 vert 2
                        vertexStream.Write(new Vector4(p3, 1));
                        vertexStream.Write(normal3);
                        vertexStream.Write(color);
                        vertexStream.Write(uv3);
                        vertexStream.Write(tangent3);
                        vertexStream.Write(binormal3);

                        // tri 2 vert 1
                        vertexStream.Write(new Vector4(p0, 1));
                        vertexStream.Write(normal0);
                        vertexStream.Write(color);
                        vertexStream.Write(uv0);
                        vertexStream.Write(tangent0);
                        vertexStream.Write(binormal0);
                    }
                }

                //positionDict.CalcNormals(vertexStream);

                vertexStream.Position = 0;

                var vertices = new Buffer(context.D3DDevice, vertexStream, new BufferDescription
                                                                               {
                                                                                   BindFlags = BindFlags.VertexBuffer,
                                                                                   CpuAccessFlags = CpuAccessFlags.None,
                                                                                   OptionFlags = ResourceOptionFlags.None,
                                                                                   SizeInBytes = streamSize,
                                                                                   Usage = ResourceUsage.Default
                                                                               });

                _torus = new Mesh { InputElements = inputElements, Vertices = vertices, NumTriangles = numTriangles, AttributesSize = attributesSize };
            }

            Changed = false;
        }

        private Force _forceA;
        private Force _forceB;
        private Mesh _torus = new Mesh();
        private Random _rand;
    }
}

 