package geometry

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/go-gl/gl/v4.6-core/gl"
)

// attributes:
// position 						 3 * float
// normal   						 3 * float
// texture coordinates   2 * float

type Geometry struct {
	vao  uint32
	vbo  uint32
	ebo  uint32
	size int32
}

func (g Geometry) Draw() {
	gl.BindVertexArray(g.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, g.vbo)
	gl.DrawElementsWithOffset(gl.TRIANGLES, g.size, gl.UNSIGNED_INT, 0)
}

func (g Geometry) Delete() {
	gl.DeleteVertexArrays(1, &g.vao)
	gl.DeleteBuffers(1, &g.ebo)
	gl.DeleteBuffers(1, &g.vbo)
}

type vec struct {
	Values []float32
}

type point struct {
	Vertex int
	Normal int
	Uv     int
}

type face struct {
	Points [3]point
}

func new(vertices []vec, normals []vec, uvs []vec, faces []face) (Geometry, error) {
	fmt.Println(len(vertices), len(normals), len(uvs), len(faces))

	i := 0
	points := make(map[point]int)
	indicies := []uint{}
	for _, face := range faces {
		for _, point := range face.Points {
			if val, ok := points[point]; !ok {
				points[point] = i
				indicies = append(indicies, uint(i))
				i++
			} else {
				indicies = append(indicies, uint(val))
			}
		}
	}

	rPoints := make(map[int]point)
	for k, v := range points {
		rPoints[v] = k
	}

	buffer := make([]float32, len(rPoints)*8)
	for i := 0; i < len(rPoints); i++ {
		v := rPoints[i]
		buffer[i*8+0] = vertices[v.Vertex].Values[0]
		buffer[i*8+1] = vertices[v.Vertex].Values[1]
		buffer[i*8+2] = vertices[v.Vertex].Values[2]
		buffer[i*8+3] = normals[v.Normal].Values[0]
		buffer[i*8+4] = normals[v.Normal].Values[1]
		buffer[i*8+5] = normals[v.Normal].Values[2]
		buffer[i*8+6] = uvs[v.Uv].Values[0]
		buffer[i*8+7] = uvs[v.Uv].Values[1]
	}

	var geometry Geometry

	// vao
	gl.GenVertexArrays(1, &geometry.vao)
	gl.BindVertexArray(geometry.vao)

	// vbo
	gl.GenBuffers(1, &geometry.vbo)
	gl.BindBuffer(gl.ARRAY_BUFFER, geometry.vbo)
	gl.BufferData(gl.ARRAY_BUFFER, len(buffer)*4, gl.Ptr(buffer), gl.STATIC_DRAW)

	// attributes
	gl.VertexAttribPointerWithOffset(0, 3, gl.FLOAT, false, 8*4, 0)
	gl.EnableVertexAttribArray(0)

	gl.VertexAttribPointerWithOffset(1, 3, gl.FLOAT, false, 8*4, 3*4)
	gl.EnableVertexAttribArray(1)

	gl.VertexAttribPointerWithOffset(2, 2, gl.FLOAT, false, 8*4, 6*4)
	gl.EnableVertexAttribArray(2)

	// ebo
	gl.GenBuffers(1, &geometry.ebo)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.ebo)
	gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(indicies)*4, gl.Ptr(indicies), gl.STATIC_DRAW)
	geometry.size = int32(len(indicies))

	return geometry, nil
}

func LoadOBJ(path string) (geometries []Geometry, err error) {
	file, err := os.Open(path)

	if err != nil {
		return nil, err
	}

	var vertices []vec
	var normals []vec
	var uvs []vec
	var faces []face

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		switch scanner.Text()[0:2] {
		case "# ":
			continue

		case "mt":
			fmt.Println("material ignored")
			continue

		case "o ":
			if len(vertices) != 0 {
				geometry, err := new(vertices, normals, uvs, faces)
				if err != nil {
					return nil, err
				}
				geometries = append(geometries, geometry)
			}

		case "v ":
			parts := strings.Split(scanner.Text(), " ")
			vertex := vec{}
			for i := 1; i < 4; i++ {
				value, err := strconv.ParseFloat(parts[i], 32)
				if err != nil {
					return nil, err
				}
				vertex.Values = append(vertex.Values, float32(value))
			}
			vertices = append(vertices, vertex)

		case "vn":
			parts := strings.Split(scanner.Text(), " ")
			normal := vec{}
			for i := 1; i < 4; i++ {
				value, err := strconv.ParseFloat(parts[i], 32)
				if err != nil {
					return nil, err
				}
				normal.Values = append(normal.Values, float32(value))
			}
			normals = append(normals, normal)

		case "vt":
			parts := strings.Split(scanner.Text(), " ")
			uv := vec{}
			for i := 1; i < 3; i++ {
				value, err := strconv.ParseFloat(parts[i], 32)
				if err != nil {
					return nil, err
				}
				uv.Values = append(uv.Values, float32(value))
			}
			uvs = append(uvs, uv)

		case "f ":
			parts := strings.Split(scanner.Text(), " ")
			face := face{}
			for i, part := range parts[1:] {
				bits := strings.Split(part, "/")
				v, err := strconv.Atoi(bits[0])
				if err != nil {
					return nil, err
				}

				t, err := strconv.Atoi(bits[1])
				if err != nil {
					return nil, err
				}

				n, err := strconv.Atoi(bits[2])
				if err != nil {
					return nil, err
				}

				face.Points[i].Vertex = v - 1
				face.Points[i].Normal = n - 1
				face.Points[i].Uv = t - 1
			}
			faces = append(faces, face)
		}
	}

	geometry, err := new(vertices, normals, uvs, faces)
	if err != nil {
		return nil, err
	}
	geometries = append(geometries, geometry)

	return geometries, err
}
