using Assimp;
using Assimp.Configs;
using GameApp.Model;
using OpenGL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace GameApp.Helper
{
    public class OBJLoader
    {
        public static RawModel loadObjModel2(string fileName, Loader loader)
        {
            string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "res", fileName + ".obj");

            AssimpContext importer = new AssimpContext();
            importer.SetConfig(new NormalSmoothingAngleConfig(66.0f));
            Scene scene = importer.ImportFile(filePath, PostProcessPreset.TargetRealTimeQuality | PostProcessSteps.FlipWindingOrder);

            if (scene == null || scene.HasMeshes == false)
            {
                return null;
            }

            float[] verticesArray = VerticesFromMesh(scene.Meshes[0]);
            float[] textureArray = TextureFromMesh(scene.Meshes[0]);
            int[] indicesArray = IndicesFromMesh(scene.Meshes[0]);
            float[] normalsArray = NormalsFromMesh(scene.Meshes[0]);

            return loader.loadToVAO(verticesArray, textureArray, indicesArray);
        }

        public static RawModel loadObjModel(string fileName, Loader loader)
        {
            List<Vertex3f> vertices = new List<Vertex3f>();
            List<Vertex2f> textures = new List<Vertex2f>();
            List<Vertex3f> normals = new List<Vertex3f>();
            List<int> indices = new List<int>();

            float[] verticesArray = null;
            float[] normalsArray = null;
            float[] textureArray = null;

            string filePath = $".\\res\\{fileName}.obj";

            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (StreamReader sr = new StreamReader(fs))
            {
                string line;

                while (true)
                {
                    line = sr.ReadLine();
                    string[] currentLine = line.Split(' ');

                    if (line.StartsWith("v "))
                    {
                        Vertex3f vertex = new Vertex3f(float.Parse(currentLine[1]), float.Parse(currentLine[2]), float.Parse(currentLine[3]));
                        vertices.Add(vertex);
                    }
                    else if (line.StartsWith("vt "))
                    {
                        Vertex2f texture = new Vertex2f(float.Parse(currentLine[1]), float.Parse(currentLine[2]));
                        textures.Add(texture);
                    }
                    else if (line.StartsWith("vn "))
                    {
                        Vertex3f normal = new Vertex3f(float.Parse(currentLine[1]), float.Parse(currentLine[2]), float.Parse(currentLine[3]));
                        normals.Add(normal);
                    }
                    else if (line.StartsWith("f "))
                    {
                        textureArray = new float[vertices.Count * 2];
                        normalsArray = new float[vertices.Count * 3];
                        break;
                    }
                }

                while (line != null)
                {
                    if (line.StartsWith("f ") == false)
                    {
                        line = sr.ReadLine();
                        continue;
                    }

                    string[] currentLine = line.Split(' ');
                    string[] vertex1 = currentLine[1].Split('/');
                    string[] vertex2 = currentLine[2].Split('/');
                    string[] vertex3 = currentLine[3].Split('/');

                    processVertex(vertex1, indices, textures, normals, textureArray, normalsArray);
                    processVertex(vertex2, indices, textures, normals, textureArray, normalsArray);
                    processVertex(vertex3, indices, textures, normals, textureArray, normalsArray);

                    line = sr.ReadLine();
                }
            }

            verticesArray = new float[vertices.Count * 3];

            int vertexPointer = 0;
            foreach (Vertex3f vertex in vertices)
            {
                verticesArray[vertexPointer++] = vertex.x;
                verticesArray[vertexPointer++] = vertex.y;
                verticesArray[vertexPointer++] = vertex.z;
            }

            int [] indicesArray = indices.ToArray();

            return loader.loadToVAO(verticesArray, textureArray, indicesArray);
        }

        static float[] TextureFromMesh(Mesh mesh)
        {
            List<float> list = new List<float>();

            foreach (Vector3D vertex in mesh.TextureCoordinateChannels[0])
            {
                list.Add(vertex.X);
                list.Add(1 - vertex.Y);
            }

            return list.ToArray();
        }

        static float[] VerticesFromMesh(Mesh mesh)
        {
            List<float> list = new List<float>();

            foreach (Vector3D vertex in mesh.Vertices)
            {
                list.Add(vertex.X);
                list.Add(vertex.Y);
                list.Add(vertex.Z);
            }

            return list.ToArray();
        }

        static int[] IndicesFromMesh(Mesh mesh)
        {
            List<int> list = new List<int>();

            foreach (Face face in mesh.Faces)
            {
                list.AddRange(face.Indices);
            }

            return list.ToArray();
        }

        private static float[] NormalsFromMesh(Mesh mesh)
        {
            List<float> list = new List<float>();

            foreach (Vector3D vector in mesh.Normals)
            {
                list.Add(vector.X);
                list.Add(vector.Y);
                list.Add(vector.Z);
            }

            return list.ToArray();
        }

        static void processVertex(string[] vertexData, List<int> indices, List<Vertex2f> textures, List<Vertex3f> normals, float[] textureArray, float[] normalsArray)
        {
            int currentVertexPointer = int.Parse(vertexData[0]) - 1;
            indices.Add(currentVertexPointer);

            Vertex2f currentTex = textures[int.Parse(vertexData[1]) - 1];
            textureArray[currentVertexPointer * 2] = currentTex.x;
            textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;

            Vertex3f currentNorm = normals[int.Parse(vertexData[2]) - 1];
            normalsArray[currentVertexPointer * 3] = currentNorm.x;
            normalsArray[currentVertexPointer * 3 + 1] = currentNorm.y;
            normalsArray[currentVertexPointer * 3 + 2] = currentNorm.z;
        }
    }
}
