//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using
using Buffer = SharpDX.Direct3D11.Buffer;

namespace Framefield.Core.ID3fb4a74f_dd01_4fa8_aa4d_7501430890c3
{
    public class Class_Cube : OperatorPart.Function,
                              Framefield.Core.OperatorPartTraits.IMeshSupplier
    {
        // IMeshSupplier
        public void AddMeshesTo(ICollection<Mesh> meshes)
        {
            UpdateMesh(new OperatorPartContext(), OperatorPart.Connections);
            meshes.Add(_mesh);
        }

        //>>> _inputids
        private enum InputId
        {
            SizeWidth = 0,
            SizeHeight = 1,
            SizeDepth = 2,
            ColorR = 3,
            ColorG = 4,
            ColorB = 5,
            ColorA = 6,
            TesselateWidth = 7,
            TesselateHeight = 8,
            TesselateDepth = 9
        }
        //<<< _inputids

        public override void Dispose() 
        {
            Utilities.DisposeObj(ref _mesh);
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) 
        {
            try 
            {
                UpdateMesh(context, inputs);

                context.Renderer.SetupEffect(context);
                context.Renderer.Render(_mesh, context);
            }
            catch (Exception) 
            {
                //Logger.Debug(this,"error");
            }

            return context;
        }

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

            //>>> _params
            var SizeWidth = inputs[(int)InputId.SizeWidth].Eval(context).Value;
            var SizeHeight = inputs[(int)InputId.SizeHeight].Eval(context).Value;
            var SizeDepth = inputs[(int)InputId.SizeDepth].Eval(context).Value;
            var Size = new Vector3(SizeWidth, SizeHeight, SizeDepth);
            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 TesselateWidth = inputs[(int)InputId.TesselateWidth].Eval(context).Value;
            var TesselateHeight = inputs[(int)InputId.TesselateHeight].Eval(context).Value;
            var TesselateDepth = inputs[(int)InputId.TesselateDepth].Eval(context).Value;
            var Tesselate = new Vector3(TesselateWidth, TesselateHeight, TesselateDepth);
            //<<< _params

            //>>> Function
            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 tessX = (int) Tesselate.X;
            int tessY = (int) Tesselate.Y;
            int tessZ = (int) Tesselate.Z;
            int numQuads = (tessY - 1)*(tessX - 1)*2 + (tessY - 1)*(tessZ - 1)*2 + (tessZ - 1)*(tessX - 1)*2;

            const int attributesSize = 76;
            int numTriangles = numQuads*2;
            int streamSize = numTriangles*3*attributesSize;

            if (_mesh == null || streamSize != _mesh.NumTriangles*3*_mesh.AttributesSize)
            {
                Utilities.DisposeObj(ref _mesh);
                using (var stream = new DataStream(streamSize, true, true))
                {
                    var vertices = new Buffer(context.D3DDevice, stream, new BufferDescription
                                                                             {
                                                                                 BindFlags = BindFlags.VertexBuffer,
                                                                                 CpuAccessFlags = CpuAccessFlags.Write,
                                                                                 OptionFlags = ResourceOptionFlags.None,
                                                                                 SizeInBytes = streamSize,
                                                                                 Usage = ResourceUsage.Dynamic
                                                                             });
                    _mesh = new Mesh
                                {
                                    InputElements = inputElements,
                                    Vertices = vertices,
                                    NumTriangles = numTriangles,
                                    AttributesSize = attributesSize
                                };
                }
            }
            DataStream vertexStream;
            DataBox box = context.D3DDevice.ImmediateContext.MapSubresource(_mesh.Vertices, MapMode.WriteDiscard, MapFlags.None, out vertexStream);
            using (vertexStream)
            {
                vertexStream.Position = 0;

                Vector3 start = Size*-0.5f;
                Vector3 end = start*-1.0f;

                float uPart = 0.25f;
                float uPart2 = 0.5f;
                float uPart3 = 0.75f;
                float vPart = 0.33333f;

                // texture coord layout : 1 - front, 2 - right, 3 - back, 4 - left, 5 - top, 6 - bottom, X - not used
                // 
                //   v^
                //    |
                //    |5XXX
                //    |1234  <- Texture
                //    |6XXX
                //     ------> 
                //           u

                for (int y = 0; y < (tessY - 1); ++y)
                {
                    float normalizedBottom = (float) y/(float) (tessY - 1);
                    float bottom = start.Y + (Size.Y*normalizedBottom);
                    float normalizedTop = (float) (y + 1)/(float) (tessY - 1);
                    float top = start.Y + (Size.Y*normalizedTop);

                    float lowerV = vPart + normalizedBottom*vPart;
                    float upperV = vPart + normalizedTop*vPart;

                    for (int x = 0; x < (tessX - 1); ++x)
                    {
                        float normalizedLeft = (float) x/(float) (tessX - 1);
                        float left = start.X + (Size.X*normalizedLeft);
                        float normalizedRight = (float) (x + 1)/(float) (tessX - 1);
                        float right = start.X + (Size.X*normalizedRight);
                        // front
                        float leftU = normalizedLeft*uPart;
                        float rightU = normalizedRight*uPart;
                        addQuad(vertexStream, Color,
                                new Vector3(left, bottom, end.Z), new Vector2(leftU, lowerV),
                                new Vector3(right, bottom, end.Z), new Vector2(rightU, lowerV),
                                new Vector3(right, top, end.Z), new Vector2(rightU, upperV),
                                new Vector3(left, top, end.Z), new Vector2(leftU, upperV));

                        // back
                        rightU = uPart3 - normalizedLeft*uPart;
                        leftU = uPart3 - normalizedRight*uPart;
                        addQuad(vertexStream, Color,
                                new Vector3(right, bottom, start.Z), new Vector2(leftU, lowerV),
                                new Vector3(left, bottom, start.Z), new Vector2(rightU, lowerV),
                                new Vector3(left, top, start.Z), new Vector2(rightU, upperV),
                                new Vector3(right, top, start.Z), new Vector2(leftU, upperV));
                    }

                    for (int z = 0; z < (tessZ - 1); ++z)
                    {
                        float normalizedBack = (float) (z)/(float) (tessZ - 1);
                        float back = start.Z + (Size.Z*normalizedBack);
                        float normalizedFront = (float) (z + 1)/(float) (tessZ - 1);
                        float front = start.Z + (Size.Z*normalizedFront);

                        // left side
                        float leftU = uPart3 + normalizedBack*uPart;
                        float rightU = uPart3 + normalizedFront*uPart;
                        addQuad(vertexStream, Color,
                                new Vector3(start.X, bottom, front), new Vector2(rightU, lowerV),
                                new Vector3(start.X, top, front), new Vector2(rightU, upperV),
                                new Vector3(start.X, top, back), new Vector2(leftU, upperV),
                                new Vector3(start.X, bottom, back), new Vector2(leftU, lowerV));

                        // right side
                        leftU = uPart2 - normalizedFront*uPart;
                        rightU = uPart2 - normalizedBack*uPart;
                        addQuad(vertexStream, Color,
                                new Vector3(end.X, bottom, front), new Vector2(leftU, lowerV),
                                new Vector3(end.X, bottom, back), new Vector2(rightU, lowerV),
                                new Vector3(end.X, top, back), new Vector2(rightU, upperV),
                                new Vector3(end.X, top, front), new Vector2(leftU, upperV));
                    }
                }

                for (int z = 0; z < (tessZ - 1); ++z)
                {
                    float normalizedBack = (float) (z)/(float) (tessZ - 1);
                    float back = start.Z + (Size.Z*normalizedBack);
                    float normalizedFront = (float) (z + 1)/(float) (tessZ - 1);
                    float front = start.Z + (Size.Z*normalizedFront);

                    for (int x = 0; x < (tessX - 1); ++x)
                    {
                        float normalizedLeft = (float) (x)/(float) (tessX - 1);
                        float left = start.X + (Size.X*normalizedLeft);
                        float normalizedRight = (float) (x + 1)/(float) (tessX - 1);
                        float right = start.X + (Size.X*normalizedRight);
                        float leftU = normalizedLeft*uPart;
                        float rightU = normalizedRight*uPart;

                        // bottom
                        float lowerV = normalizedBack*vPart;
                        float upperV = normalizedFront*vPart;
                        addQuad(vertexStream, Color,
                                new Vector3(right, start.Y, front), new Vector2(rightU, upperV),
                                new Vector3(left, start.Y, front), new Vector2(leftU, upperV),
                                new Vector3(left, start.Y, back), new Vector2(leftU, lowerV),
                                new Vector3(right, start.Y, back), new Vector2(rightU, lowerV));

                        // top
                        upperV = 1.0f - normalizedBack*vPart;
                        lowerV = 1.0f - normalizedFront*vPart;
                        addQuad(vertexStream, Color,
                                new Vector3(left, end.Y, front), new Vector2(leftU, lowerV),
                                new Vector3(right, end.Y, front), new Vector2(rightU, lowerV),
                                new Vector3(right, end.Y, back), new Vector2(rightU, upperV),
                                new Vector3(left, end.Y, back), new Vector2(leftU, upperV));
                    }
                }

                context.D3DDevice.ImmediateContext.UnmapSubresource(_mesh.Vertices, 0);
            }
            //<<< Function

            Changed = false;
        }

        private void addQuad(DataStream stream, Color4 color,
                             Vector3 p0, Vector2 tx0,
                             Vector3 p1, Vector2 tx1,
                             Vector3 p2, Vector2 tx2,
                             Vector3 p3, Vector2 tx3) {
            var tangent = new Vector3(1.0f, 0.0f, 0.0f);
            var binormal = new Vector3(0.0f, -1.0f, 0.0f);

            //triangle 0, 1, 2
            var normal = Vector3.Cross(p1 - p0, p2 - p0);
            normal.Normalize();

            stream.Write(new Vector4(p0.X, p0.Y, p0.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx0);
            stream.Write(tangent);
            stream.Write(binormal);

            stream.Write(new Vector4(p1.X, p1.Y, p1.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx1);
            stream.Write(tangent);
            stream.Write(binormal);

            stream.Write(new Vector4(p2.X, p2.Y, p2.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx2);
            stream.Write(tangent);
            stream.Write(binormal);

            //triangle 0, 2, 3
            normal = Vector3.Cross(p2 - p0, p3 - p0);
            normal.Normalize();

            stream.Write(new Vector4(p0.X, p0.Y, p0.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx0);
            stream.Write(tangent);
            stream.Write(binormal);

            stream.Write(new Vector4(p2.X, p2.Y, p2.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx2);
            stream.Write(tangent);
            stream.Write(binormal);

            stream.Write(new Vector4(p3.X, p3.Y, p3.Z, 1));
            stream.Write(normal);
            stream.Write(color);
            stream.Write(tx3);
            stream.Write(tangent);
            stream.Write(binormal);
        }

        private Mesh _mesh;
    }
}