import Foundation
import OrderedCollections


extension ObjMaterial
{
	func convert(content: UnsafeMutablePointer<ContentManager>? = nil) -> Material
	{
		var m = Material()
		m.diffuse = self.diffuse.setAlpha(self.alpha)
		if ![ .colour, .lambert, .shadowOnly ].contains(self.model)
		{
			m.specular = self.specular
			m.specularExp = self.specularExp
		}
		if let content = content
		{
			if let albedo = self.diffuseMap
			{
				let filter = albedo.flags.contains(.blendHoriz) || albedo.flags.contains(.blendVert)
				m.texture = (try? content.pointee.load(albedo.path,
					params: Texture2DParameters(
						minFilter: filter ? .linear : .point,
						magFilter: filter ? .linear : .point,
						wrapMode: albedo.flags.contains(.clamp) ? .clampBorder : .repeating,
						mipMode: .linear)))?.id ?? RenderTexture2D.empty
			}
		}
		return m
	}
}

public struct ObjLoader: LoaderProtocol
{
	typealias V = VertexPositionNormalColourTexcoord
	public typealias T = Mesh<VertexPositionNormalColourTexcoord>

	public init() {}

	public func load(url: URL) -> T?
	{
		return try? Self.read(model: try ObjReader.read(url: url), content: nil)
	}

	public func load(url: URL, content: inout ContentManager) -> T?
	{
		return try? Self.read(model: try ObjReader.read(url: url), content: &content)
	}

	public static func read(model: ObjModel, content: UnsafeMutablePointer<ContentManager>?) throws -> T
	{
		var subMeshes = OrderedDictionary<String, T.SubMesh>()
		var materials = OrderedDictionary<String, Material>()
		var vertices = [V]()
		var indices = [UInt16]()

		for (key, mtl) in model.materials
		{
			materials[key] = mtl.convert(content: content)
		}

		let readIndex =
		{ (v: ObjModel.Index) -> UInt16 in
			let colour = model.colours.isEmpty ? .white : Colour(
				r: model.colours[v.p][0],
				g: model.colours[v.p][1],
				b: model.colours[v.p][2]).linear
			let vertex = V(
				position: model.positions[v.p],
				colour: colour,
				normal: model.normals[v.n],
				texCoord: model.texCoords[v.t])
			if let index = vertices.firstIndex(of: vertex)
			{
				indices.append(UInt16(index))
				return UInt16(index)
			}
			else
			{
				let index = UInt16(vertices.count)
				indices.append(index)
				vertices.append(vertex)
				return index
			}
		}

		for object in model.objects.filter({ $0.key != "Collision3D" })
		{
			var id = 0
			for mesh: ObjModel.Mesh in object.value.meshes
			{
				let start = indices.count
				for face: ObjModel.Face in mesh.faces
				{
					switch face
					{
					case .triangle(let v1, let v2, let v3):
						for v in [ v1, v2, v3 ] { _ = readIndex(v) }
					case .quad(let v1, let v2, let v3, let v4):
						let n1 = readIndex(v1)
						_ = readIndex(v2)
						indices.append(readIndex(v3))
						_ = readIndex(v4)
						indices.append(n1)
					case .ngon(_): fallthrough
					default: break
					}
				}
				let length = indices.count - start
				if length > 0
				{
					subMeshes["\(object.key)_\(id)"] = .init(
						start: start, length: length,
						material: materials.index(forKey: mesh.material) ?? -1)
				}
				id += 1
			}
		}

		return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes, materials: materials.values.map { $0 })
	}
}
