import Foundation
import Maths


struct VMeshLoader: LoaderProtocol
{
	typealias T = Mesh<VMeshVertex>

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

	static func read(url: URL) throws -> T
	{
		let file = try FileHandle(forReadingFrom: url)

		// read header fields
		let header = VMeshHeader(
			magic:        try file.read(upToCount: 4),
			version:      try file.read(as: UInt8.self).littleEndian,
			idxSize:      try file.read(as: UInt8.self).littleEndian,
			subMeshCount: try file.read(as: UInt16.self).littleEndian,
			vertexCount:  try file.read(as: UInt32.self).littleEndian,
			indexCount:   try file.read(as: UInt32.self).littleEndian)

		// header sanity checks
		guard header.magic == Data("VMSH".utf8)
		else { throw NSError() }
		guard header.version == 2
		else { throw NSError() }

		// check index type
		let idxSize = switch header.idxSize
		{
		case 1, 2, 4: header.idxSize
		default: throw NSError()
		}

		// fail on empty data lengths
		guard header.vertexCount > 0, header.indexCount > 0
		else { throw NSError() }

		// read submeshes
		var subMeshes = [VMeshSubMesh](repeating: .empty, count: Int(header.subMeshCount))
		for i in 0..<Int(header.subMeshCount)
		{
			guard let nameStr = try file.read(upToCount: 24)
			else { throw NSError() }
			subMeshes[i] = .init(
				name: String(decoding: nameStr, as: UTF8.self),
				offset: try file.read(as: UInt32.self),
				count: try file.read(as: UInt32.self))
		}

		// read vertices
		var vertices = [VMeshVertex](repeating: .empty, count: Int(header.vertexCount))
		for i in 0..<Int(header.vertexCount)
		{
			vertices[i] = .init(
				position: .init(try file.read(as: Float.self), try file.read(as: Float.self), try file.read(as: Float.self)),
				//texCoord: .init(try file.read(as: UInt16.self), try file.read(as: UInt16.self)),
				texCoord: .init(
					Float(try file.read(as: UInt16.self)) / Float(UInt16.max),
					1.0 - Float(try file.read(as: UInt16.self)) / Float(UInt16.max)),
				normal: .init(try file.read(as: Int8.self), try file.read(as: Int8.self), try file.read(as: Int8.self), try file.read(as: Int8.self)),
				tangent: .init(try file.read(as: Int8.self), try file.read(as: Int8.self), try file.read(as: Int8.self), try file.read(as: Int8.self)))
		}

		let indices = switch idxSize
		{
		case 1: try (0..<Int(header.indexCount)).map { _ in UInt16(try file.read(as: UInt8.self)) }
		case 2: try (0..<Int(header.indexCount)).map { _ in UInt16(try file.read(as: UInt16.self)) }
		case 4: try (0..<Int(header.indexCount)).map { _ in UInt16(try file.read(as: UInt32.self)) }
		default: fatalError()
		}

		// create & return mesh
		return .init(
			vertices: vertices, indices: indices,
			subMeshes: .init(uniqueKeysWithValues:
				subMeshes.map { ($0.name, .init(start: Int($0.offset), length: Int($0.count))) }
			)
		)
	}

	struct VMeshHeader
	{
		let magic: Data?
		let version, idxSize: UInt8
		let subMeshCount: UInt16
		let vertexCount, indexCount: UInt32
	}
}


public struct VMeshVertex: Vertex
{
	let position: Vec3f
	//let texCoord: SIMD2<UInt16>
	let texCoord: Vec2f
	let normal: SIMD4<Int8>
	let tangent: SIMD4<Int8>
}

extension VMeshVertex
{
	static let empty: Self = .init(position: .init(), texCoord: .init(), normal: .init(), tangent: .init())
}

struct VMeshSubMesh
{
	let name: String
	let offset: UInt32, count: UInt32
}

extension VMeshSubMesh
{
	static let empty: Self = .init(name: .init(), offset: .init(), count: .init())
}
