import JolkEngine
import Foundation
import simd


struct Scene1: Scene
{
	private var env = Environment()

	private var worldMesh = RenderMesh<VertexPositionNormalColourTexcoord>.empty
	private var cube = RenderMesh<VertexPositionNormalTexcoord>.empty
	private var suzanne = RenderMesh<VertexPositionNormalTexcoord>.empty
	private var toybox = RenderMesh<VertexPositionNormalTexcoord>.empty
	private var jolkTex = Texture2D.empty, suzanneDiffuse = Texture2D.empty

	private var drawEdges = false
	private var world = Collision()
	private var colin = Colin()
	private var jolkCube = JolkCube(position: .init(0, 1, -4))
	private var lightTheta: Float = 0


	mutating func setup(render: inout any JolkEngine.Renderer)
	{
		render.clearColour = XnaColour.CornflowerBlue

		env.setFog(
			colour: XnaColour.CornflowerBlue,
			//mode: .depth, falloff: .range(type: .linear, start: 4, end: 38))
			//mode: .depth, falloff: .range(type: .linear, start: 1.25, end: 24.5))
			//mode: .distance, falloff: .factor(type: .exp2, density: 0.1))
			mode: .depth, falloff: .factor(type: .exp2, density: 0.0222))

		env.addAmbientLight(colour: XnaColour.DarkSlateGray.lighten(by: -0.666))
		env.addDirectionalLight(
			direction: -Vec3f(1, -1, -1).normalised,
			colour: XnaColour.BlanchedAlmond
				.lighten(by: -0.02)
				.mix(with: XnaColour.LightSlateGray, 0.5)
				.mix(with: XnaColour.White, 0.125))
		env.addPointLight(
			position: Vec3f(3, 0.33, -5),
			colour: XnaColour.Green.mix(with: XnaColour.Gray, 0.5),
			intensity: 2)
		env.addPointLight(
			position: Vec3f(5.5, 0.33, -6),
			colour: XnaColour.Red.mix(with: XnaColour.Gray, 0.5),
			intensity: 2)
		env.addPointLight(
			position: Vec3f(4, 0.33, -7),
			colour: XnaColour.Blue.mix(with: XnaColour.Gray, 0.5),
			intensity: 2)
	}
	
	mutating func loadContent(content: inout JolkEngine.ContentManager) throws
	{
		try loadWorld(&content)
		cube = try content.create(mesh: .init(
			vertices: [
				.init(position: Vec3f(-1, -1,  1), normal: .forward, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f( 1, -1,  1), normal: .forward, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f(-1,  1,  1), normal: .forward, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f( 1,  1,  1), normal: .forward, texCoord: Vec2f(1, 1)),
				.init(position: Vec3f( 1, -1,  1), normal:   .right, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f( 1, -1, -1), normal:   .right, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f( 1,  1,  1), normal:   .right, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f( 1,  1, -1), normal:   .right, texCoord: Vec2f(1, 1)),
				.init(position: Vec3f( 1, -1, -1), normal:    .back, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f(-1, -1, -1), normal:    .back, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f( 1,  1, -1), normal:    .back, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f(-1,  1, -1), normal:    .back, texCoord: Vec2f(1, 1)),
				.init(position: Vec3f(-1, -1, -1), normal:    .left, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f(-1, -1,  1), normal:    .left, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f(-1,  1, -1), normal:    .left, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f(-1,  1,  1), normal:    .left, texCoord: Vec2f(1, 1)),
				.init(position: Vec3f(-1, -1, -1), normal:    .down, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f( 1, -1, -1), normal:    .down, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f(-1, -1,  1), normal:    .down, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f( 1, -1,  1), normal:    .down, texCoord: Vec2f(1, 1)),
				.init(position: Vec3f(-1,  1,  1), normal:      .up, texCoord: Vec2f(0, 0)),
				.init(position: Vec3f( 1,  1,  1), normal:      .up, texCoord: Vec2f(1, 0)),
				.init(position: Vec3f(-1,  1, -1), normal:      .up, texCoord: Vec2f(0, 1)),
				.init(position: Vec3f( 1,  1, -1), normal:      .up, texCoord: Vec2f(1, 1))
			],
			indices: [
				 0,  1,  2,  2,  1,  3,
				 4,  5,  6,  6,  5,  7,
				 8,  9, 10, 10,  9, 11,
				12, 13, 14, 14, 13, 15,
				16, 17, 18, 18, 17, 19,
				20, 21, 22, 22, 21, 23 ]))
		suzanne = try content.load("suzanne.g3db")
		toybox = try content.load("toybox.g3db")
		

		let linearMipped = Texture2DParameters(
			minFilter: .linear, magFilter: .linear,
			wrapMode: .repeating,
			mipMode: .linear)
		let linearClamp = Texture2DParameters(
			minFilter: .linear, magFilter: .linear,
			wrapMode: .clampEdge)

		jolkTex = try content.load("jolkmeup.jpg", params: linearClamp)
		suzanneDiffuse = try content.load("suzanne_diffuse.png", params: linearMipped)
	}

	private mutating func loadWorld(_ content: inout ContentManager) throws
	{
		let name = "World.obj"
		let obj = try ObjReader.read(url: try content.getResource(name))
		let mesh: Mesh = try ObjLoader.read(model: obj, content: &content)
		worldMesh = try content.create(mesh: mesh)

		if let collision = obj.objects["Collision3D"]
		{
			world.build(obj: obj, collision: collision)
		}
	}

	mutating func update(deltaTime: Float)
	{
		colin.update(deltaTime: deltaTime, world: world)
		jolkCube.update(deltaTime: deltaTime, world: world)

		// Update point lights
		lightTheta += deltaTime
		var theta = Vec3f(repeating: lightTheta) * Vec3f(0.12, 0.011, 0.056) * 6
		var i = env.lights.startIndex
		while i != env.lights.endIndex
		{
			if case .point(let colour, _, _) = env.lights[i]
			{
				env.lights[i] = .point(
					colour: colour,
					position: Vec3f(
						 4    + 6    * cos(theta[0]),
						 0.33 + 0.33 * sin(theta[1]),
						-6    + 3    * sin(theta[0] * 2)),
					intensity:  3.1  + 1.53 * cos(theta[2]))

				let spacing = 0.5 * Float.pi * (2 / 3.0)
				theta += spacing * Vec3f(1, 0.98, 0.5566)
			}
			i = env.lights.index(after: i)
		}

		if Input.instance.keyboard.keyPressed(.c) { drawEdges = !drawEdges }
	}

	func draw(render: inout Renderer, deltaTime: Float, aspectRatio aspect: Float)
	{
		// Setup camera
		render.setProjection(matrix:
			.perspective(fovY: Float.rad(fromDeg: colin.fov), aspect: aspect, zNear: 0.1, zFar: 100))
		render.setView(matrix: colin.transform)

		// Draw world
		for s in worldMesh.subMeshes
		{
			//render.setMaterial(Material(
			//	specular: XnaColour.BlanchedAlmond.mix(with: .black, 0.12),
			//	texture: texture.id))
			if s.material != -1
			{
				render.setMaterial(worldMesh.materials[s.material])
			}
			else
			{
				render.setMaterial(.init())
			}
			render.draw(mesh: worldMesh, subMesh: s, environment: env)
		}

		// Draw jolked up shit
		render.setMaterial(Material(
			specular: XnaColour.Gray,
			gloss: 20.0,
			texture: jolkTex.id))
		render.draw(mesh: cube, model: jolkCube.transform, environment: env)

		render.setMaterial(Material(texture: suzanneDiffuse.id))
		render.draw(mesh: suzanne, model: .translate(.up + Vec3f(3.0, 0.0, -3.5) * 2.5), environment: env)

		render.draw(mesh: toybox,
			model: .translate(Vec3f(6.0, 0.667, -3.5) * Vec3f(2.5, 1, 2.5))
				* .rotate(y: lightTheta * 0.5) * .scale(scalar: 0.25), environment: env)

		if drawEdges
		{
			world.draw(render, position: colin.position)
		}
	}
}
