package gay.pizza.CavesOfJolk

import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g3d.*
import com.badlogic.gdx.graphics.g3d.attributes.*
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight
import com.badlogic.gdx.graphics.g3d.environment.PointLight
import com.badlogic.gdx.graphics.g3d.model.data.*
import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder
import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor
import com.badlogic.gdx.graphics.g3d.utils.TextureProvider
import com.badlogic.gdx.math.*
import com.badlogic.gdx.utils.Array
import com.badlogic.gdx.utils.ScreenUtils
import gay.pizza.CavesOfJolk.Resources.Companion.assetManager

class Game: ApplicationAdapter()
{
	private lateinit var texJolk: Texture
	private lateinit var fntComic: BitmapFont
	private lateinit var cube: Model
	private lateinit var floor: Model

	private lateinit var spriteBatch: SpriteBatch
	private lateinit var modelBatch: ModelBatch
	private lateinit var env: Environment

	private lateinit var colin: Colin
	private var jolkRot = 0.0f
	private var lightTheta = 0.0f

	private lateinit var cubeInstance: ModelInstance
	private lateinit var floorInstance: ModelInstance
	private lateinit var suzanneInstance: ModelInstance

	private fun makeCube(texture: Texture): Model
	{
		val modelBuilder = ModelBuilder()
		val size = 2.0f
		val material = Material(
			ColorAttribute.createDiffuse(XnaColor.White),
			TextureAttribute(TextureAttribute.Diffuse,
				TextureDescriptor(texture,
					Texture.TextureFilter.Linear,
					Texture.TextureFilter.Linear,
					Texture.TextureWrap.ClampToEdge,
					Texture.TextureWrap.ClampToEdge)),
			ColorAttribute.createSpecular(XnaColor.Gray),
			FloatAttribute.createShininess(20.0f))
		val attribs = VertexAttributes.Usage.Position or VertexAttributes.Usage.TextureCoordinates or VertexAttributes.Usage.Normal
		return modelBuilder.createBox(size, size, size, material, attribs.toLong())
	}

	private fun makeFloor(): Model
	{
		val model = ModelData()

		val mesh = ModelMesh()
		mesh.id = "floormodel"
		mesh.attributes = arrayOf(
			VertexAttribute.Position(),
			VertexAttribute.Normal(),
			VertexAttribute.Tangent(),
			VertexAttribute.Binormal(),
			VertexAttribute.TexCoords(0))
		val normal = Vector3(0.0f, 1.0f, 0.0f)
		val tangent = Vector3(1.0f, 0.0f, 0.0f)
		val bitangent = Vector3(0.0f, 0.0f, -1.0f)
		val vertex = { pos: Vector3, tex: Vector2, norm: Vector3, tan: Vector3, bitan: Vector3 -> floatArrayOf(
			pos.x, pos.y, pos.z,
			norm.x, norm.y, norm.z,
			tan.x, tan.y, tan.z,
			bitan.x, bitan.y, bitan.z,
			tex.x, tex.y
		)}
		val size = 16.0f
		val texs = 4.0f
		mesh.vertices =
			vertex(Vector3(0.0f, 0.0f,  0.0f), Vector2(0.0f, texs), normal, tangent, bitangent) +
			vertex(Vector3(size, 0.0f,  0.0f), Vector2(texs, texs), normal, tangent, bitangent) +
			vertex(Vector3(0.0f, 0.0f, -size), Vector2(0.0f, 0.0f), normal, tangent, bitangent) +
			vertex(Vector3(size, 0.0f, -size), Vector2(texs, 0.0f), normal, tangent, bitangent)
		val part = ModelMeshPart()
		part.id = "floormesh"
		part.primitiveType = GL20.GL_TRIANGLES
		part.indices = shortArrayOf(
			0, 1, 2,
			3, 2, 1)
		mesh.parts = arrayOf(part)
		model.addMesh(mesh)

		val material = ModelMaterial()
		material.id = "floormat"
		material.diffuse  = XnaColor.White
		material.specular = XnaColor.BlanchedAlmond.mix(XnaColor.Black, 0.12f)
		material.shininess = 65.0f
		val diffuse = ModelTexture()
		diffuse.usage = ModelTexture.USAGE_DIFFUSE
		diffuse.fileName = "cobblestone.png"
		val normalMap = ModelTexture()
		normalMap.usage = ModelTexture.USAGE_NORMAL
		normalMap.fileName = "cobblestone_normal.png"
		val specular = ModelTexture()
		specular.usage = ModelTexture.USAGE_SPECULAR
		specular.fileName = "cobblestone_specular.png"
		material.textures = Array()
		material.textures.add(diffuse, normalMap, specular)
		model.materials.add(material)

		val node = ModelNode()
		node.id = "floornode"
		node.scale = Vector3(1.0f, 1.0f, 1.0f)
		node.rotation = Quaternion()
		val nodePart = ModelNodePart()
		nodePart.meshPartId = "floormesh"
		nodePart.materialId = "floormat"
		node.parts = arrayOf(nodePart)
		model.nodes.add(node)

		return Model(model, TextureProvider.FileTextureProvider(
			Texture.TextureFilter.Nearest,
			Texture.TextureFilter.Nearest,
			Texture.TextureWrap.Repeat,
			Texture.TextureWrap.Repeat,
			false))
	}

	override fun create()
	{
		Resources.instance.loadAssets()
		assetManager.finishLoading()

		texJolk = assetManager.get("jolkmeup.jpg")
		fntComic = assetManager.get("Comic Sans MS.ttf")
		val suzanne = assetManager.get("suzanne.g3db", Model::class.java)

		// Override ridiculous shininess because i cbf to fix the model
		for (i in suzanne.materials)
			i.set(FloatAttribute.createShininess(30.0f))

		cube = makeCube(texJolk)
		floor = makeFloor()

		spriteBatch = SpriteBatch()

		env = Environment()
		env.set(
			IntAttribute.createCullFace(GL20.GL_BACK),
			ColorAttribute.createAmbientLight(XnaColor.DarkSlateGray.lighten(-0.666)),
			ColorAttribute.createFog(XnaColor.CornflowerBlue))
		env.set(
			CustomIntAttribute.createFogMode(CustomIntAttribute.FogModes.Depth),
			CustomIntAttribute.createFogType(CustomIntAttribute.FogTypes.Smooth),
			CustomFloatAttribute.createFogNear(1.25f),
			CustomFloatAttribute.createFogFar(24.5f))
		/*
		env.set(
			CustomIntAttribute.createFogMode(CustomIntAttribute.FogModes.Distance),
			CustomIntAttribute.createFogType(CustomIntAttribute.FogTypes.Exp2),
			CustomFloatAttribute.createFogDensity(0.1f))
		 */
		env.add(DirectionalLight().set(
			XnaColor.BlanchedAlmond.lighten(-0.02).mix(XnaColor.LightSlateGray, 0.5f).mix(XnaColor.White, 0.125f),
			Vector3(1.0f, -1.0f, -1.0f).nor()))
		env.add(PointLight().set(
			XnaColor.Green.mix(XnaColor.Gray, 0.5f),
			Vector3(3.0f, 0.33f, -5.0f), 2.0f))
		env.add(PointLight().set(
			XnaColor.Red.mix(XnaColor.Gray, 0.5f),
			Vector3(5.5f, 0.33f, -6.0f), 2.0f))
		env.add(PointLight().set(
			XnaColor.Blue.mix(XnaColor.Gray, 0.5f),
			Vector3(4.0f, 0.33f, -7.0f), 2.0f))

		val shaderConfig = DefaultShader.Config()
		shaderConfig.numDirectionalLights = 1
		shaderConfig.numPointLights = 3
		shaderConfig.numBones = 0
		modelBatch = ModelBatch(CustomDefaultShaderProvider(shaderConfig))

		colin = Colin()

		cubeInstance = ModelInstance(cube)
		floorInstance = ModelInstance(floor)
		suzanneInstance = ModelInstance(suzanne)
		suzanneInstance.transform = Matrix4().translate(3.0f, 1.0f, -3.5f)
	}

	override fun resize(width: Int, height: Int)
	{
		colin.resize(width, height)
		spriteBatch.projectionMatrix.setToOrtho2D(0.0f, 0.0f,
			Gdx.graphics.getWidth().toFloat(),
			Gdx.graphics.getHeight().toFloat());
	}

	private fun update(deltaTime: Float)
	{
		colin.update(deltaTime)

		lightTheta += deltaTime
		jolkRot += 15.0f * deltaTime
	}

	override fun render()
	{
		val deltaTime = Gdx.graphics.deltaTime
		update(deltaTime)

		ScreenUtils.clear(XnaColor.CornflowerBlue, true)

		env.get(PointLightsAttribute.Type)?.let { it as PointLightsAttribute
			var thetaa = lightTheta * 6.0f * 0.12f
			var thetab = lightTheta * 6.0f * 0.011f
			var thetac = lightTheta * 6.0f * 0.056f

			for (light in it.lights)
			{
				val x =  4.0f  + 6.0f  * MathUtils.cos(thetaa)
				val z = -6.0f  + 3.0f  * MathUtils.sin(thetaa * 2.0f)
				val y =  0.33f + 0.33f * MathUtils.sin(thetab)
				val i =  3.1f  + 1.53f * MathUtils.cos(thetac)

				val spacing = 0.5f * MathUtils.PI * (2.0f / 3.0f)
				thetaa += spacing
				thetab += spacing * 0.98f
				thetac += spacing * 0.5566f

				light.setPosition(x, y, z)
				light.setIntensity(i)
			}
		}

		val jolkPos = Vector3(0.0f, 1.0f + MathUtils.sin(jolkRot * 0.25f) * 0.25f, -4.0f)
		val world = Matrix4()
			.setTranslation(jolkPos)
			.rotateRad(1.0f, 0.0f, 0.0f, jolkRot * 0.25f)
			.rotate(0.0f, 1.0f, 0.0f, jolkRot)
			.scale(0.25f, 0.25f, 0.25f)
		cubeInstance.transform = world

		modelBatch.begin(colin.camera)
		modelBatch.render(floorInstance, env)
		modelBatch.render(cubeInstance, env)
		modelBatch.render(suzanneInstance, env)
		modelBatch.end()

		spriteBatch.begin()
		colin.draw(spriteBatch)
		val textPos = colin.camera.project(jolkPos)
		if (textPos.z < 1.0)
			fntComic.draw(spriteBatch, "I am filled with jolk", textPos.x, textPos.y)
		fntComic.draw(spriteBatch, "${colin.position.x}\n${colin.position.y}", 10.0f, Gdx.graphics.height - 10.0f)
		spriteBatch.end()
	}

	override fun dispose()
	{
		floor.dispose()
		cube.dispose()
		modelBatch.dispose()
		spriteBatch.dispose()
		assetManager.dispose()
	}
}
