using System.Globalization;
using OpenTK.Mathematics;

namespace Engine.Asset.Mesh.Loader;

public class ObjMeshLoader : IMeshLoader
{
  private static readonly ObjMeshLoader INSTANCE = new();

  public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
  {
    return INSTANCE.LoadMesh(parReader, parParameters);
  }

  private ObjMeshLoader()
  {
  }

  public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
  {
    var mesh = new Mesh();

    var tempVertices = new List<Vector3>();
    var tempNormals = new List<Vector3>();
    var tempUVs = new List<Vector2>();
    var index = 0u;

    var loadNormals = parParameters.HasFlag(MeshLoaderParameters.LoadNormals);
    var loadUVs = parParameters.HasFlag(MeshLoaderParameters.LoadUVs);
    while (parReader.ReadLine() is { } line)
    {
      var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);

      if (parts.Length == 0 || parts[0].StartsWith('#'))
      {
        continue;
      }

      switch (parts[0])
      {
        case "v":
          tempVertices.Add(new Vector3(
            float.Parse(parts[1], CultureInfo.InvariantCulture),
            float.Parse(parts[2], CultureInfo.InvariantCulture),
            float.Parse(parts[3], CultureInfo.InvariantCulture)
          ));
          break;

        case "vt" when loadUVs:
          tempUVs.Add(new Vector2(
            float.Parse(parts[1], CultureInfo.InvariantCulture),
            float.Parse(parts[2], CultureInfo.InvariantCulture)
          ));
          break;

        case "vn" when loadNormals:
          tempNormals.Add(new Vector3(
            float.Parse(parts[1], CultureInfo.InvariantCulture),
            float.Parse(parts[2], CultureInfo.InvariantCulture),
            float.Parse(parts[3], CultureInfo.InvariantCulture)
          ));
          break;

        case "f":
          for (var i = 1; i < parts.Length; i++)
          {
            var faceComponents = parts[i].Split('/');
            var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] };

            if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "")
            {
              meshVertex._uv = tempUVs[int.Parse(faceComponents[1]) - 1];
            }

            if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "")
            {
              meshVertex._normal = tempNormals[int.Parse(faceComponents[2]) - 1];
            }

            mesh.VerticesInternal.Add(meshVertex);
            mesh.IndicesInternal.Add(index++);
          }

          break;
      }
    }

    if (parParameters.HasFlag(MeshLoaderParameters.Optimize))
    {
      mesh = IMeshLoader.Optimize(mesh);
    }

    return mesh;
  }
}