import Foundation


public protocol ContentLoaderParametersProtocol
{
	associatedtype T: Resource
}

public struct ContentManager
{
	private var loaders = Dictionary<String, any LoaderProtocol>()
	private var resources = [any RendererResource]()
	private let renderer: Renderer
	private let bundle: Bundle
}

extension ContentManager
{
	internal init(_ renderer: inout Renderer, bundle: Bundle)
	{
		self.renderer = renderer
		self.bundle = bundle
	}

	public mutating func setLoader<T: Resource, Loader: LoaderProtocol>(extensions exts: [String], loader: Loader) where Loader.T == T
	{
		for i in exts
		{
			setLoader(extension: i, loader: loader)
		}
	}

	public mutating func setLoader<T: Resource, Loader: LoaderProtocol>(extension ext: String, loader: Loader) where Loader.T == T
	{
		loaders[ext] = loader
	}

	public mutating func create<V: Vertex>(mesh: Mesh<V>) throws -> RenderMesh<V>
	{
		let rendMesh = try renderer.createMesh(mesh: mesh)
		resources.append(rendMesh)
		return rendMesh
	}

	public mutating func create(texture: Image, params: Texture2DParameters = .init()) throws -> Texture2D
	{
		try texture.pixels.withUnsafeBytes
		{
			raw in
			let data = raw.baseAddress!
			let rendTex = try renderer.createTexture(
				data: data, width: texture.width, height: texture.height,
				filter: params.magFilter, mipMode: params.mipMode)
			resources.append(rendTex)
			return Texture2D(id: rendTex, width: texture.width, height: texture.height)
		}
	}

	public func getResource(_ path: String) throws -> URL
	{
		guard let extIndex = path.lastIndex(of: ".")
		else { throw ContentError.badPath }
		let name = String(path[..<extIndex]), ext = String(path[extIndex...])
		guard let resourceUrl: URL = bundle.url(
			forResource: name,
			withExtension: ext)
		else { throw ContentError.resourceNotFound }
		return resourceUrl
	}

	public mutating func load<T: Resource>(_ path: String) throws -> T
	{
		let resourceUrl = try getResource(path)

		let loader = loaders[resourceUrl.pathExtension.lowercased()]
		guard let resource = loader?.load(url: resourceUrl)
		else { throw ContentError.loadFailure }

		if T.self == Mesh<VertexPositionNormalTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
			return mesh as! T
		}
		if T.self == Mesh<VertexPositionNormalColourTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
			return mesh as! T
		}
		if T.self == Image.self
		{
			guard let image = resource as? Image else { throw ContentError.loadFailure }
			return image as! T
		}
		if T.self == Texture2D.self
		{
			guard let image = resource as? Image else { throw ContentError.loadFailure }
			let texture2D = try self.create(texture: image)
			return texture2D as! T
		}
		throw ContentError.loadFailure
	}

	public mutating func load<T: Resource, Params: ContentLoaderParametersProtocol>(_ path: String, params: Params) throws
		-> T where Params.T == T
	{
		let resourceUrl = try getResource(path)

		let loader = loaders[resourceUrl.pathExtension.lowercased()]
		guard let resource = loader?.load(url: resourceUrl)
		else { throw ContentError.loadFailure }

		if T.self == Mesh<VertexPositionNormalTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
			return mesh as! T
		}
		if T.self == Mesh<VertexPositionNormalColourTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
			return mesh as! T
		}
		if T.self == Image.self
		{
			guard let image = resource as? Image else { throw ContentError.loadFailure }
			return image as! T
		}
		if T.self == Texture2D.self
		{
			guard let image = resource as? Image else { throw ContentError.loadFailure }
			let texture2D = try self.create(texture: image, params: params as! Texture2DParameters)
			return texture2D as! T
		}
		throw ContentError.loadFailure
	}

	public mutating func load<T: RendererResource>(_ path: String) throws -> T
	{
		let resourceUrl = try getResource(path)

		let loader = loaders[resourceUrl.pathExtension]
		guard let resource = loader?.load(url: resourceUrl)
		else { throw ContentError.loadFailure }

		if T.self == RenderMesh<VertexPositionNormalTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalTexcoord> else { throw ContentError.loadFailure }
			let renderResource = try self.create(mesh: mesh)
			return renderResource as! T
		}
		if T.self == RenderMesh<VertexPositionNormalColourTexcoord>.self
		{
			guard let mesh = resource as? Mesh<VertexPositionNormalColourTexcoord> else { throw ContentError.loadFailure }
			let renderResource = try self.create(mesh: mesh)
			return renderResource as! T
		}
		throw ContentError.loadFailure
	}

	internal mutating func releaseAll()
	{
		for resource in resources.reversed()
		{
			if resource is RenderMesh<VertexPositionNormalTexcoord>
			{
				renderer.deleteMesh(resource as! RenderMesh<VertexPositionNormalTexcoord>)
			}
			else if resource is RenderMesh<VertexPositionNormalColourTexcoord>
			{
				renderer.deleteMesh(resource as! RenderMesh<VertexPositionNormalColourTexcoord>)
			}
			else if resource is RenderTexture2D
			{
				renderer.deleteTexture(resource as! RenderTexture2D)
			}
		}
		resources.removeAll()
	}
}

public enum ContentError: Error
{
	case badPath //else { fatalError("Malformed resource path \"\(path)\"") }
	case resourceNotFound //else { fatalError("Resource \"\(path)\" doesn't exist") }
	case loadFailure //else { fatalError("Failed to load resource \"\(path)\"") }
}
