﻿using System;
using System.Collections.Generic;

namespace Oni.Motoko
{
    internal class GeometryDaeWriter
    {
        private readonly TextureDaeWriter textureWriter;

        public GeometryDaeWriter(TextureDaeWriter textureWriter)
        {
            this.textureWriter = textureWriter;
        }

        public Dae.Node WriteNode(Geometry geometry, string name)
        {
            var daeGeometryInstance = WriteGeometryInstance(geometry, name);

            return new Dae.Node
            {
                Name = name,
                Instances = { daeGeometryInstance }
            };
        }

        public Dae.GeometryInstance WriteGeometryInstance(Geometry geometry, string name)
        {
            var daeGeometry = WriteGeometry(geometry, name);
            var daeGeometryInstance = new Dae.GeometryInstance(daeGeometry);

            if (geometry.Texture != null)
            {
                var daeMaterial = textureWriter.WriteMaterial(geometry.Texture);

                daeGeometryInstance.Materials.Add(new Dae.MaterialInstance("default", daeMaterial)
                {
                    Bindings = {
                        new Dae.MaterialBinding(
                            semantic: "diffuse_TEXCOORD",
                            input: daeGeometry.Primitives[0].Inputs.Find(i => i.Semantic == Dae.Semantic.TexCoord))
                    }
                });
            }

            return daeGeometryInstance;
        }

        private Dae.Geometry WriteGeometry(Geometry geometry, string name)
        {
            var points = geometry.Points;
            var normals = geometry.Normals;
            var texCoords = geometry.TexCoords;

            if (geometry.HasTransform)
            {
                points = Vector3.Transform(points, ref geometry.Transform);
                normals = Vector3.TransformNormal(normals, ref geometry.Transform);
            }

            int[] pointMap;

            points = WeldPoints(points, out pointMap);

            var positionInput = new Dae.IndexedInput(Dae.Semantic.Position, new Dae.Source(points));
            var normalInput = new Dae.IndexedInput(Dae.Semantic.Normal, new Dae.Source(normals));
            var texCoordInput = new Dae.IndexedInput(Dae.Semantic.TexCoord, new Dae.Source(texCoords));

            var primitives = new Dae.MeshPrimitives(Dae.MeshPrimitiveType.Polygons)
            {
                MaterialSymbol = "default",
                Inputs = { positionInput, normalInput, texCoordInput }
            };

            for (int triangleStart = 0; triangleStart < geometry.Triangles.Length; triangleStart += 3)
            {
                primitives.VertexCounts.Add(3);

                for (int i = 0; i < 3; i++)
                {
                    int index = geometry.Triangles[triangleStart + i];

                    positionInput.Indices.Add(pointMap[index]);
                    texCoordInput.Indices.Add(index);
                    normalInput.Indices.Add(index);
                }
            }

            return new Dae.Geometry
            {
                Name = name,
                Vertices = { positionInput },
                Primitives = { primitives }
            };
        }

        private static T[] WeldPoints<T>(T[] list, out int[] map)
        {
            var indicesMap = new int[list.Length];
            var index = new Dictionary<T, int>(list.Length);
            var result = new List<T>(list.Length);

            for (int i = 0; i < indicesMap.Length; i++)
            {
                var v = list[i];

                if (!index.TryGetValue(v, out indicesMap[i]))
                {
                    indicesMap[i] = result.Count;
                    result.Add(v);
                    index.Add(v, indicesMap[i]);
                }
            }

            map = indicesMap;

            return result.ToArray();
        }
    }
}
