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

namespace Framefield.Core.ID5e9777af_8372_4513_b704_8e11e688e196
{
    public class Class_Torus : OperatorPart.Function,
                               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
        }
        //<<< _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)
        {
            try 
            {
                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[(int)InputId.RadiusMajor].Eval(context).Value;
            var radiusMinor = inputs[(int)InputId.RadiusMinor].Eval(context).Value;

            var tessX = Math.Max(1, (int) inputs[(int)InputId.TesselateMajor].Eval(context).Value);
            var tessY = Math.Max(1, (int) inputs[(int)InputId.TesselateMinor].Eval(context).Value);

            var spinMajorInRad = Utilities.DegreeToRad(inputs[(int)InputId.SpinMajor].Eval(context).Value);
            var spinMinorInRad = Utilities.DegreeToRad(inputs[(int)InputId.SpinMinor].Eval(context).Value);

            var portionMajor = inputs[(int)InputId.PortionMajor].Eval(context).Value/360.0;
            var portionMinor = inputs[(int)InputId.PortionMinor].Eval(context).Value/360.0;

            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 smoothAngle = inputs[(int)InputId.SmoothAngle].Eval(context).Value;

            Dispose();
            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 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(p0 - tubeCenter2);
                        //var normal1 = Vector3.Normalize(p1 - tubeCenter1);
                        //var normal2 = Vector3.Normalize(p2 - tubeCenter1);
                        //var normal3 = Vector3.Normalize(p3 - tubeCenter2);
                        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 Mesh _torus = new Mesh();
    }
}


