package render

import (
	"time"
	"unsafe"

	"github.com/Edgaru089/gl01/internal/asset"
	"github.com/Edgaru089/gl01/internal/util/itype"
	"github.com/Edgaru089/gl01/internal/world"
	"github.com/go-gl/gl/all-core/gl"
	"github.com/go-gl/mathgl/mgl32"
)

// WorldRenderer holds texture/shader resource and viewport
// information for world rendering.
type WorldRenderer struct {
	shader  *Shader
	texture *Texture

	vao, vbo uint32
	vbolen   int
	vertex   []world.Vertex

	// for testing!
	initTime time.Time
}

// The default WorldRenderer.
var DefaultWorldRenderer WorldRenderer

// Init initializes the WorldRenderer.
func (r *WorldRenderer) Init(w *world.World) (err error) {

	r.shader, err = NewShader(asset.WorldShaderVert, asset.WorldShaderFrag)
	if err != nil {
		return err
	}

	asset.InitWorldTextureAtlas()
	r.texture = NewTextureRGBA(asset.WorldTextureAtlas.Image)
	r.shader.SetUniformTexture("tex", r.texture)

	r.shader.SetUniformMat4("model", mgl32.Ident4())
	// and view and projection uniforms not yet set
	gl.BindFragDataLocation(r.shader.Handle(), 0, gl.Str("outputColor\x00"))

	gl.GenVertexArrays(1, &r.vao)
	gl.BindVertexArray(r.vao)
	gl.GenBuffers(1, &r.vbo)
	gl.BindBuffer(gl.ARRAY_BUFFER, r.vbo)

	gensync := make(chan []world.Vertex, len(w.Chunks))
	for _, c := range w.Chunks {
		chunk := c
		go func() {
			arr := chunk.AppendVertex([]world.Vertex{})
			gensync <- arr
		}()
	}
	for range w.Chunks {
		r.vertex = append(r.vertex, (<-gensync)...)
	}
	close(gensync)

	gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(world.Vertex{}))*len(r.vertex), gl.Ptr(r.vertex), gl.DYNAMIC_DRAW)

	vertAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("vert\x00")))
	gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.World))))

	normalAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("normal\x00")))
	gl.VertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Normal))))

	texCoordAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("vertTexCoord\x00")))
	gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Texture))))

	lightAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("light\x00")))
	gl.VertexAttribPointer(lightAttrib, 1, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Light))))

	gl.EnableVertexAttribArray(vertAttrib)
	gl.EnableVertexAttribArray(normalAttrib)
	gl.EnableVertexAttribArray(texCoordAttrib)
	gl.EnableVertexAttribArray(lightAttrib)

	w.EnableVertexArrayAttrib(vertAttrib)
	w.EnableVertexArrayAttrib(normalAttrib)
	w.EnableVertexArrayAttrib(texCoordAttrib)
	w.EnableVertexArrayAttrib(lightAttrib)

	gl.Enable(gl.CULL_FACE)
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LESS)

	r.initTime = time.Now()

	return nil
}

func (r *WorldRenderer) Render(world *world.World, view *View) {

	r.shader.UseProgram()
	r.shader.BindTextures()

	r.shader.SetUniformMat4("view", view.view)
	r.shader.SetUniformMat4("projection", mgl32.Perspective(view.fovy, view.aspect, 0.1, 200))
	r.shader.SetUniformVec3f("sun", itype.Vec3f{-0.2, 1, 0.8}.Normalize())

	// for testing!
	//model := mgl32.HomogRotate3D(float32(time.Since(r.initTime).Seconds()), mgl32.Vec3{0, 1, 0})
	//r.shader.SetUniformMat4("model", model)

	//r.shader.SetUniformMat4("view", r.view)

	gl.BindVertexArray(r.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, r.vbo)
	gl.DrawArrays(gl.TRIANGLES, 0, int32(len(r.vertex)))

	world.Render()
	//world.Chunk(0, 0).Render()
}
