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.ID272b2877_fc59_495a_8a04_20e42e0b7629
{
    public class Class_Cylinder : OperatorPart.Function,
                               IMeshSupplier
    {
        //>>> _inputids
        private enum InputId
        {
            RadiusTop = 0,
            RadiusBottom = 1,
            TessellateCaps = 2,
            TessellateRadius = 3,
            SpinHull = 4,
            PortionHull = 5,
            ColorR = 6,
            ColorG = 7,
            ColorB = 8,
            ColorA = 9,
            SmoothAngle = 10,
            Height = 11,
            DrawCaps = 12,
            TessellateHeight = 13
        }
        //<<< _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 RadiusTop = inputs[(int)InputId.RadiusTop].Eval(context).Value;
            var RadiusBottom = inputs[(int)InputId.RadiusBottom].Eval(context).Value;

            var tessCaps = Math.Max(1, (int)inputs[(int)InputId.TessellateCaps].Eval(context).Value);
            var tessRadius = Math.Max(3, (int)inputs[(int)InputId.TessellateRadius].Eval(context).Value);
            var tessHeight = Math.Max(1, (int)inputs[(int)InputId.TessellateHeight].Eval(context).Value);

            var spinHullInRad = Utilities.DegreeToRad(inputs[(int)InputId.SpinHull].Eval(context).Value);

            var portionHull = inputs[(int)InputId.PortionHull].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;

            var height = (float)inputs[(int)InputId.Height].Eval(context).Value;

            var drawCaps = inputs[(int)InputId.DrawCaps].Eval(context).Value;
            var drawTop = (drawCaps == 0) || (drawCaps == 1);
            var drawBottom = (drawCaps == 0) || (drawCaps == 2);

            var uvCenterTopCap = new Vector2(0.225f, 0.25f);

            var uvCenterBottomCap = new Vector2(0.775f, 0.25f);

            Dispose();
            var color = new Vector4(colorR, colorG, colorB, colorA);
            var tangent = new Vector3(0.0f, 0.0f, 1.0f);
            var binormal = new Vector3(1.0f, 0.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 numQuadsForHull = tessRadius * tessHeight ;
            int numQuadsCaps = tessCaps * tessRadius;
            int numQuads = numQuadsForHull 
                           + (drawTop    ? numQuadsCaps :0)  
                           + (drawBottom ? numQuadsCaps :0);
                                    
            bool smoothHull = portionHull / (int)tessRadius > smoothAngle / 360;
            const int attributesSize = 76;
            int numTriangles = numQuads * 2;
            
            int streamSize =  numTriangles * 3 * attributesSize;
            using (var vertexStream = new DataStream(streamSize, true, true))
            {
                double hullAngleFraction = portionHull / tessRadius * 2.0 * Math.PI;

                for (int z = 0; z < tessRadius; ++z)
                {
                    double tubeAngle = z * hullAngleFraction + spinHullInRad;
                    float tubePosition1Ytop = (float)Math.Cos(tubeAngle) * RadiusTop;
                    float tubePosition1Xtop = (float)Math.Sin(tubeAngle) * RadiusTop;
                    float tubePosition2Ytop = (float)Math.Cos(tubeAngle + hullAngleFraction) * RadiusTop;
                    float tubePosition2Xtop = (float)Math.Sin(tubeAngle + hullAngleFraction) * RadiusTop;
                    float tubePositionZtop = (float)0.5f * height;

                    float Pos1YFraction = (float)Math.Cos(tubeAngle) * (RadiusTop - RadiusBottom) / tessHeight;
                    float Pos1XFraction = (float)Math.Sin(tubeAngle) * (RadiusTop - RadiusBottom) / tessHeight;
                    float Pos2YFraction = (float)Math.Cos(tubeAngle + hullAngleFraction) * (RadiusTop - RadiusBottom) / tessHeight;
                    float Pos2XFraction = (float)Math.Sin(tubeAngle + hullAngleFraction) * (RadiusTop - RadiusBottom) / tessHeight;
                    float PosZFraction = (float)height / tessHeight;

                    float u0 = (float)((z) / (tessRadius * 1.0f));
                    float u1 = (float)((z + 1) / (tessRadius * 1.0f));

                    float v0 = 0;
                    float v1 = 0;

                    var uv0 = new Vector2(u1, v0);
                    var uv1 = new Vector2(u1, v1);
                    var uv2 = new Vector2(u0, v0);
                    var uv3 = new Vector2(u0, v1);

                    // calc the 4 vertices of quad
                    var p1 = new Vector3((tubePosition2Ytop),
                                         tubePositionZtop, tubePosition2Xtop);
                    var p0 = p1;
                    var p3 = new Vector3((tubePosition1Ytop),
                                         tubePositionZtop, tubePosition1Xtop);
                    var p2 = p3;



                    var tubeCenter1 = new Vector3((float)0, 0.0f, 0.0f);
                    var tubeCenter2 = new Vector3(0.0f, -0.5f * height, 0.0f);
                    //var normal0 = Vector3.Normalize(p0 - tubeCenter2);
                    //var normal1 = Vector3.Normalize(p1 - tubeCenter1);
                    //var normal2 = Vector3.Normalize(p2 - tubeCenter1);
                    //var normal3 = Vector3.Normalize(p3 - tubeCenter2);
                    Vector3 normal0;
                    Vector3 normal1;
                    Vector3 normal2;
                    Vector3 normal3;

                    Vector3 tangent0, binormal0;

                    Vector3 tangent1, binormal1;

                    Vector3 tangent2, binormal2;

                    Vector3 tangent3, binormal3;

                    for (int y = 0; y < tessHeight; y++)
                    {

                        p0.X -= Pos2YFraction; p0.Y -= PosZFraction; p0.Z -= Pos2XFraction;
                        p2.X -= Pos1YFraction; p2.Y -= PosZFraction; p2.Z -= Pos1XFraction;

                        v0 = 0.5f + (y + 1) / (tessHeight * 2.0f);
                        v1 = 0.5f + (y) / (tessHeight * 2.0f);

                        uv0.Y = v0;
                        uv1.Y = v1;
                        uv2.Y = v0;
                        uv3.Y = v1;


                        var heightCenterTop = new Vector3(0.0f, tubePositionZtop - y * PosZFraction, 0.0f);
                        var heightCenterBot = new Vector3(0.0f, tubePositionZtop - (y + 1) * PosZFraction, 0.0f);
                        //var normal0 = Vector3.Normalize(p0 - tubeCenter2);
                        //var normal1 = Vector3.Normalize(p1 - tubeCenter1);
                        //var normal2 = Vector3.Normalize(p2 - tubeCenter1);
                        //var normal3 = Vector3.Normalize(p3 - tubeCenter2);
                        normal0 = Vector3.Normalize(smoothHull ? Vector3.Cross(p0 - p3, p0 - p1) : p0 - heightCenterBot);
                        normal1 = Vector3.Normalize(smoothHull ? Vector3.Cross(p1 - p0, p1 - p3) : p1 - heightCenterTop);
                        normal2 = Vector3.Normalize(smoothHull ? Vector3.Cross(p2 - p3, p2 - p0) : p2 - heightCenterBot);
                        normal3 = Vector3.Normalize(smoothHull ? Vector3.Cross(p3 - p0, p3 - p2) : p3 - heightCenterTop);

                        MeshUtilities.CalcTBNSpace(p0, uv0, p3, uv3, p1, uv1, normal0, out tangent0, out binormal0);

                        MeshUtilities.CalcTBNSpace(p1, uv1, p0, uv0, p3, uv3, normal1, out tangent1, out binormal1);

                        MeshUtilities.CalcTBNSpace(p2, uv2, p3, uv3, p0, uv0, normal2, out tangent2, out binormal2);

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

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

                        // 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 3
                        vertexStream.Write(new Vector4(p0, 1));
                        vertexStream.Write(normal0);
                        vertexStream.Write(color);
                        vertexStream.Write(uv0);
                        vertexStream.Write(tangent0);
                        vertexStream.Write(binormal0);

                        // 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);

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


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

                        // update vertices
                        p1 = p0;
                        p3 = p2;

                    }

                    p1 = new Vector3((tubePosition2Ytop),
                                         tubePositionZtop, tubePosition2Xtop);
                    p3 = new Vector3((tubePosition1Ytop),
                                         tubePositionZtop, tubePosition1Xtop);

                    // now the caps
                    var tubeCenter3 = new Vector3(0.0f, 0.5f * height, 0.0f);

                    // top cap
                    Vector3 tangent4, binormal4;
                    var normal4 = Vector3.Normalize(tubeCenter3);
                    MeshUtilities.CalcTBNSpace(tubeCenter3, uv0, p3, uv1, p1, uv2, normal4, out tangent4, out binormal4);

                    // bottom cap
                    Vector3 tangent5, binormal5;
                    var normal5 = Vector3.Normalize(tubeCenter2);
                    MeshUtilities.CalcTBNSpace(tubeCenter2, uv0, p2, uv1, p0, uv2, normal5, out tangent5, out binormal5);

                    if (drawTop || drawBottom)
                    {
                        var uvAngle1 = 2 * Math.PI * (z - 0.5f) / tessRadius;
                        var uvAngle0 = 2 * Math.PI * (z + 0.5f) / tessRadius;


                        for (int x = 0; x < tessCaps; ++x)
                        {
                            var decreaseScale = (float)(tessCaps - x - 1) / (tessCaps - x);
                            var p5 = new Vector3(p1.X * decreaseScale, p1.Y, p1.Z * decreaseScale);
                            var p6 = new Vector3(p3.X * decreaseScale, p3.Y, p3.Z * decreaseScale);
                            var p7 = new Vector3(p0.X * decreaseScale, p0.Y, p0.Z * decreaseScale);
                            var p8 = new Vector3(p2.X * decreaseScale, p2.Y, p2.Z * decreaseScale);


                            if (drawTop)
                            {

                                uv0 = uv2 = new Vector2((float)Math.Cos(uvAngle0), -(float)Math.Sin(uvAngle0));
                                uv1 = uv3 = new Vector2((float)Math.Cos(uvAngle1), -(float)Math.Sin(uvAngle1));

                                uv0 *= 0.2f * (tessCaps - x) / tessCaps;
                                uv1 *= 0.2f * (tessCaps - x) / tessCaps;
                                uv2 *= 0.2f * (tessCaps - x - 1) / tessCaps;
                                uv3 *= 0.2f * (tessCaps - x - 1) / tessCaps;

                                uv0 += uvCenterTopCap;
                                uv1 += uvCenterTopCap;
                                uv2 += uvCenterTopCap;
                                uv3 += uvCenterTopCap;

                                // outter tri vert 1
                                vertexStream.Write(new Vector4(p1, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv0);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                // outter tri vert 2
                                vertexStream.Write(new Vector4(p3, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv1);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                // outter tri vert 3
                                vertexStream.Write(new Vector4(p5, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv2);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                // inner tri vert 1
                                vertexStream.Write(new Vector4(p5, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv2);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                // inner tri vert 2
                                vertexStream.Write(new Vector4(p3, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv1);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                // inner tri vert 3
                                vertexStream.Write(new Vector4(p6, 1));
                                vertexStream.Write(normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv3);
                                vertexStream.Write(tangent4);
                                vertexStream.Write(binormal4);

                                p1 = p5;
                                p3 = p6;
                            }

                            if (drawBottom)
                            {

                                uv0 = uv2 = new Vector2((float)Math.Cos(uvAngle1), (float)Math.Sin(uvAngle1));
                                uv1 = uv3 = new Vector2((float)Math.Cos(uvAngle0), (float)Math.Sin(uvAngle0));

                                uv0 *= 0.2f * (tessCaps - x) / tessCaps;
                                uv1 *= 0.2f * (tessCaps - x) / tessCaps;
                                uv2 *= 0.2f * (tessCaps - x - 1) / tessCaps;
                                uv3 *= 0.2f * (tessCaps - x - 1) / tessCaps;

                                uv0 += uvCenterBottomCap;
                                uv1 += uvCenterBottomCap;
                                uv2 += uvCenterBottomCap;
                                uv3 += uvCenterBottomCap;
                                // outter tri vert 1
                                vertexStream.Write(new Vector4(p7, 1));
                                vertexStream.Write(normal5);
                                vertexStream.Write(color);
                                vertexStream.Write(uv3);
                                vertexStream.Write(tangent5);
                                vertexStream.Write(binormal5);

                                // outter tri vert 2
                                vertexStream.Write(new Vector4(p2, 1));
                                vertexStream.Write(normal5);
                                vertexStream.Write(color);
                                vertexStream.Write(uv0);
                                vertexStream.Write(tangent5);
                                vertexStream.Write(binormal5);

                                // outter tri vert 3
                                vertexStream.Write(new Vector4(p0, 1));
                                vertexStream.Write(normal5);
                                vertexStream.Write(color);
                                vertexStream.Write(uv1);
                                vertexStream.Write(tangent5);
                                vertexStream.Write(binormal5);

                                // inner tri vert 1
                                vertexStream.Write(new Vector4(p8, 1));
                                vertexStream.Write(normal5);
                                vertexStream.Write(color);
                                vertexStream.Write(uv2);
                                vertexStream.Write(tangent5);
                                vertexStream.Write(binormal5);

                                // inner tri vert 2
                                vertexStream.Write(new Vector4(p2, 1));
                                vertexStream.Write(normal5);
                                vertexStream.Write(color);
                                vertexStream.Write(uv0);
                                vertexStream.Write(-tangent4);
                                vertexStream.Write(-binormal4);

                                // inner tri vert 3
                                vertexStream.Write(new Vector4(p7, 1));
                                vertexStream.Write(-normal4);
                                vertexStream.Write(color);
                                vertexStream.Write(uv3);
                                vertexStream.Write(tangent5);
                                vertexStream.Write(binormal5);

                                p0 = p7;
                                p2 = p8;
                            }

                        }
                    }
                }

                //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();
    }
}


