package main

import (
	"fmt"
	"math"

	rl "github.com/gen2brain/raylib-go/raylib"
)

const MAX_INSTANCES = 100000

func main() {
	var (
		screenWidth         = int32(800)   // Framebuffer width
		screenHeight        = int32(450)   // Framebuffer height
		fps                 = 60           // Frames per second
		speed               = 30           // Speed of jump animation
		groups              = 2            // Count of separate groups jumping around
		amp                 = float32(10)  // Maximum amplitude of jump
		variance            = float32(0.8) // Global variance in jump height
		loop                = float32(0.0) // Individual cube's computed loop timer
		textPositionY int32 = 300
		framesCounter       = 0
	)

	rl.SetConfigFlags(rl.FlagMsaa4xHint) // Enable Multi Sampling Anti Aliasing 4x (if available)
	rl.InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mesh instancing")

	// Define the camera to look into our 3d world
	camera := rl.Camera{
		Position:   rl.NewVector3(-125.0, 125.0, -125.0),
		Target:     rl.NewVector3(0.0, 0.0, 0.0),
		Up:         rl.NewVector3(0.0, 1.0, 0.0),
		Fovy:       45.0,
		Projection: rl.CameraPerspective,
	}

	cube := rl.GenMeshCube(1.0, 1.0, 1.0)

	rotations := make([]rl.Matrix, MAX_INSTANCES)    // Rotation state of instances
	rotationsInc := make([]rl.Matrix, MAX_INSTANCES) // Per-frame rotation animation of instances
	translations := make([]rl.Matrix, MAX_INSTANCES) // Locations of instances

	// Scatter random cubes around
	for i := 0; i < MAX_INSTANCES; i++ {
		x := float32(rl.GetRandomValue(-50, 50))
		y := float32(rl.GetRandomValue(-50, 50))
		z := float32(rl.GetRandomValue(-50, 50))
		translations[i] = rl.MatrixTranslate(x, y, z)

		x = float32(rl.GetRandomValue(0, 360))
		y = float32(rl.GetRandomValue(0, 360))
		z = float32(rl.GetRandomValue(0, 360))
		axis := rl.Vector3Normalize(rl.NewVector3(x, y, z))
		angle := float32(rl.GetRandomValue(0, 10)) * rl.Deg2rad

		rotationsInc[i] = rl.MatrixRotate(axis, angle)
		rotations[i] = rl.MatrixIdentity()
	}

	transforms := make([]rl.Matrix, MAX_INSTANCES)

	shader := rl.LoadShader("glsl330/base_lighting_instanced.vs", "glsl330/lighting.fs")
	shader.UpdateLocation(rl.ShaderLocMatrixMvp, rl.GetShaderLocation(shader, "mvp"))
	shader.UpdateLocation(rl.ShaderLocVectorView, rl.GetShaderLocation(shader, "viewPos"))
	shader.UpdateLocation(rl.ShaderLocMatrixModel, rl.GetShaderLocationAttrib(shader, "instanceTransform"))

	// ambient light level
	ambientLoc := rl.GetShaderLocation(shader, "ambient")
	rl.SetShaderValue(shader, ambientLoc, []float32{0.2, 0.2, 0.2, 1.0}, rl.ShaderUniformVec4)
	NewLight(LightTypeDirectional, rl.NewVector3(50.0, 50.0, 0.0), rl.Vector3Zero(), rl.White, shader)

	material := rl.LoadMaterialDefault()
	material.Shader = shader
	mmap := material.GetMap(rl.MapDiffuse)
	mmap.Color = rl.Red

	rl.SetTargetFPS(int32(fps))
	for !rl.WindowShouldClose() {
		// Update
		//----------------------------------------------------------------------------------

		textPositionY = 300
		framesCounter++

		if rl.IsKeyDown(rl.KeyUp) {
			amp += 0.5
		}
		if rl.IsKeyDown(rl.KeyDown) {
			if amp <= 1 {
				amp = 1
			} else {
				amp -= 1
			}
		}
		if rl.IsKeyDown(rl.KeyLeft) {
			if variance < 0 {
				variance = 0
			} else {
				variance -= 0.01
			}
		}
		if rl.IsKeyDown(rl.KeyRight) {
			if variance > 1 {
				variance = 1
			} else {
				variance += 0.01
			}
		}
		if rl.IsKeyDown(rl.KeyOne) {
			groups = 1
		}
		if rl.IsKeyDown(rl.KeyTwo) {
			groups = 2
		}
		if rl.IsKeyDown(rl.KeyThree) {
			groups = 3
		}
		if rl.IsKeyDown(rl.KeyFour) {
			groups = 4
		}
		if rl.IsKeyDown(rl.KeyFive) {
			groups = 5
		}
		if rl.IsKeyDown(rl.KeySix) {
			groups = 6
		}
		if rl.IsKeyDown(rl.KeySeven) {
			groups = 7
		}
		if rl.IsKeyDown(rl.KeyEight) {
			groups = 8
		}
		if rl.IsKeyDown(rl.KeyNine) {
			groups = 9
		}
		if rl.IsKeyDown(rl.KeyW) {
			groups = 7
			amp = 25
			speed = 18
			variance = float32(0.70)
		}
		if rl.IsKeyDown(rl.KeyEqual) {
			if float32(speed) <= float32(fps)*0.25 {
				speed = int(float32(fps) * 0.25)
			} else {
				speed = int(float32(speed) * 0.95)
			}
		}
		if rl.IsKeyDown(rl.KeyKpAdd) {
			if float32(speed) <= float32(fps)*0.25 {
				speed = int(float32(fps) * 0.25)
			} else {
				speed = int(float32(speed) * 0.95)
			}
		}
		if rl.IsKeyDown(rl.KeyMinus) {
			speed = int(math.Max(float64(speed)*1.02, float64(speed)+1))
		}
		if rl.IsKeyDown(rl.KeyKpSubtract) {
			speed = int(math.Max(float64(speed)*1.02, float64(speed)+1))
		}

		// Update the light shader with the camera view position
		rl.SetShaderValue(shader, shader.GetLocation(rl.ShaderLocVectorView),
			[]float32{camera.Position.X, camera.Position.Y, camera.Position.Z}, rl.ShaderUniformVec3)

		// Apply per-instance transformations
		for i := 0; i < MAX_INSTANCES; i++ {
			rotations[i] = rl.MatrixMultiply(rotations[i], rotationsInc[i])
			transforms[i] = rl.MatrixMultiply(rotations[i], translations[i])

			// Get the animation cycle's framesCounter for this instance
			loop = float32((framesCounter+int(float32(i%groups)/float32(groups)*float32(speed)))%speed) / float32(speed)

			// Calculate the y according to loop cycle
			y := float32(math.Sin(float64(loop)*rl.Pi*2)) * amp *
				((1 - variance) + (float32(variance) * float32(i%(groups*10)) / float32(groups*10)))

			// Clamp to floor
			if y < 0 {
				y = 0
			}

			transforms[i] = rl.MatrixMultiply(transforms[i], rl.MatrixTranslate(0.0, y, 0.0))
		}

		rl.UpdateCamera(&camera, rl.CameraOrbital) // Update camera with orbital camera mode
		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		rl.BeginDrawing()
		{
			rl.ClearBackground(rl.RayWhite)

			rl.BeginMode3D(camera)
			//rl.DrawMesh(cube, material, rl.MatrixIdentity())
			rl.DrawMeshInstanced(cube, material, transforms, MAX_INSTANCES)
			rl.EndMode3D()

			rl.DrawText("A CUBE OF DANCING CUBES!", 490, 10, 20, rl.Maroon)
			rl.DrawText("PRESS KEYS:", 10, textPositionY, 20, rl.Black)

			textPositionY += 25
			rl.DrawText("1 - 9", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": Number of groups", 50, textPositionY, 10, rl.Black)
			rl.DrawText(fmt.Sprintf(": %d", groups), 160, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("UP", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": increase amplitude", 50, textPositionY, 10, rl.Black)
			rl.DrawText(fmt.Sprintf(": %.2f", amp), 160, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("DOWN", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": decrease amplitude", 50, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("LEFT", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": decrease variance", 50, textPositionY, 10, rl.Black)
			rl.DrawText(fmt.Sprintf(": %.2f", variance), 160, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("RIGHT", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": increase variance", 50, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("+/=", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": increase speed", 50, textPositionY, 10, rl.Black)
			rl.DrawText(fmt.Sprintf(": %d = %f loops/sec", speed, float32(fps)/float32(speed)), 160, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("-", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": decrease speed", 50, textPositionY, 10, rl.Black)

			textPositionY += 15
			rl.DrawText("W", 10, textPositionY, 10, rl.Black)
			rl.DrawText(": Wild setup!", 50, textPositionY, 10, rl.Black)

			rl.DrawFPS(10, 10)
		}
		rl.EndDrawing()
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	//--------------------------------------------------------------------------------------
	rl.CloseWindow() // Close window and OpenGL context
	//--------------------------------------------------------------------------------------
}
