// Package main stl.go - GOOS=js GOARCH=wasm go build -ldflags="-X main.stlFileName=STLFILE.stl" stl.go
package main

import (
	"bytes"
	"log"
	"encoding/base64"
	"encoding/binary"
	m "math"
	"crypto/rand"
	r "reflect"
	"runtime"
	"strconv"
	"syscall/js"
	u "unsafe"

	"github.com/go-gl/mathgl/mgl32"
	"gitlab.com/russoj88/stl/stl"
)


var (
	running = true
	done                 chan struct{}
	stlFileName, originalHTML         string
	rr               Renderer
	existingFooter,	body,	footer,	sXV, sYV,	sZV, sZoomV, cEl, gl js.Value
	currentZoom          float32 = 3
)

const gebi = "getElementById"
const ael = "addEventListener"
const ih = "innerHTML"

func main() {
	ready := make(chan struct{})

	// Check if the DOM is already loaded
	document := js.Global().Get("document")
	readyState := document.Get("readyState").String()
	if readyState == "interactive" || readyState == "complete" {
		log.Println("WASM: DOM already fully loaded")
		close(ready) // Signal the channel immediately
	} else {
		// Define the callback function for DOMContentLoaded
		cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			log.Println("WASM: DOM fully loaded and parsed")
			close(ready) // Signal the channel to continue
			return nil
		})
		defer cb.Release() // Release the callback when it's no longer needed

		// Add an event listener for DOMContentLoaded
		document.Call(ael, "DOMContentLoaded", cb)
		log.Println("WASM: waiting for DOM to load")
	}

	// Wait until the DOM is ready
	<-ready

	mgl32.DisableMemoryPooling()
	preElement := js.Global().Get("document").Call("getElementById", "package-type")
	if !preElement.IsUndefined() && !preElement.IsNull() {
		packageText := preElement.Get("textContent").String()
		packageType := packageText[len("Package Type: "):]
		stlFileName = packageType + ".stl"
	}
//stlFileName = "TO-247.stl"
//log.Println("WASM: test")
	tdata := struct {
		XRange,  ZMin, ZMax string
		XStep, ZoomStep     string
		}{
			XRange: "1", ZMin: "0", ZMax: "50",
			XStep:  "0.01", ZoomStep: "0.1",
		}
		if stlFileName != ".stl" && stlFileName != "" {
			tdata.ZMin = "10"
			tdata.ZMax = "1000"
		}

	var controlsHTML = `
	<datalist id="speeds">
	<option>-`+tdata.XRange+`</option><option>0</option><option>`+tdata.XRange+`</option></datalist>
	<table class="🌐">
	<tr><td><h2>3D Model</h2></td><td><p>X<input id="X" type="range" min="-`+tdata.XRange+`" max="`+tdata.XRange+`" step="`+tdata.XStep+`" list="speeds"><text id="XV">00.00</text></p></td>
	<td><p>Y<input id="Y" type="range" min="-`+tdata.XRange+`" max="`+tdata.XRange+`" step="`+tdata.XStep+`" list="speeds"><text id="YV">00.00</text></p></td>
	<td><p>Z<input id="Z" type="range" min="-`+tdata.XRange+`" max="`+tdata.XRange+`" step="`+tdata.XStep+`" list="speeds"><text id="ZV">00.00</text></p></td>
	<td><p>Zoom<input id="Zoom" type="range" min="`+tdata.ZMin+`" max="`+tdata.ZMax+`" step="`+tdata.ZoomStep+`" list="speeds"><text id="ZoomV">0000.00</text></p></td>
	<td><p><button type="button" id="stop">Stop Rendering</button></p></td></tr>
	</table>
	`
		doc := js.Global().Get("document")
	body = doc.Get("body")
	existingFooter = doc.Call("getElementsByTagName", "footer").Index(0)
	if existingFooter.Truthy() {
		originalHTML = existingFooter.Get(ih).String()
		footer = doc.Call("createElement", "footer")
		footer.Set(ih, originalHTML+controlsHTML)
		body.Call("replaceChild", footer, existingFooter)
	} else {
		footer = doc.Call("createElement", "footer")
		footer.Set(ih, controlsHTML)
		body.Call("appendChild", footer)
	}

	cEl = doc.Call(gebi, "gocanvas")
	width := doc.Get("body").Get("clientWidth").Int()
	height := doc.Get("body").Get("clientHeight").Int()

	cEl.Set("width", width)
	cEl.Set("height", height)
	sXc := js.FuncOf(sCX)
	sX := doc.Call(gebi, "X")
	sX.Call(ael, "input", sXc)
	sXV = doc.Call(gebi, "XV")

	sYc := js.FuncOf(sCY)
	sY := doc.Call(gebi, "Y")
	sY.Call(ael, "input", sYc)
	sYV = doc.Call(gebi, "YV")

	sZc := js.FuncOf(sCZ)
	sZ := doc.Call(gebi, "Z")
	sZ.Call(ael, "input", sZc)
	sZV = doc.Call(gebi, "ZV")

	sZoomc := js.FuncOf(sCZoom)
	sZoom := doc.Call(gebi, "Zoom")
	sZoom.Call(ael, "input", sZoomc)
	sZoomV = doc.Call(gebi, "ZoomV")


	sBc := js.FuncOf(stopApplication)
	sB := doc.Call(gebi, "stop")
	sB.Call(ael, "click", sBc)
	defer sBc.Release()

		if stlFileName != ".stl" && stlFileName != "" {

	response := js.Global().Call("fetch", "/stl/base64/"+stlFileName)
	promise := response.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
		if p[0].Get("ok").Bool() {
			return p[0].Call("text")
		}
		return "Error fetching stereolithograph"
	}))
	promise.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
		result := p[0].String()
		uploadedFile, _ := parseBase64File(result) // nolint
		stlSolid, _ := NewSTL(uploadedFile) // nolint
		vert, colors, indices := stlSolid.GetModel()
		modelSize := getMaxScalar(vert)
		currentZoom := modelSize * 3
		rr.SetZoom(currentZoom)
		rr.SetModel(colors, vert, indices)
		return nil
	}))
}
	gl = cEl.Call("getContext", "webgl")
	if gl.IsUndefined() {
		gl = cEl.Call("getContext", "experimental-webgl")
	}
	if gl.IsUndefined() {
		js.Global().Call("alert", "WASM:  browser might not support webgl")
		return
	}


	config := InitialConfig{
		W:              width,
		H:             height,
		X:            0,
		Y:            0,
		Z:            0,
		Vertices:           verticesNative,
		Indices:            indicesNative,
		Colors:             colorsNative,
		FSC: fragShaderCode,
		VSC:   vertShaderCode,
	}

config.X = cryptoRandFloat32() / 20
config.Y = cryptoRandFloat32() / 20
config.Z = cryptoRandFloat32() / 20
		config.Vertices, config.Indices = generateSphereVertices(float32(1.0),30,30)
	if stlFileName == ".stl" || stlFileName == "" {
		config.FSC, config.VSC  = fragShaderCode1, vertShaderCode1
	}
var jsErr js.Value
rr, jsErr = NewRenderer(gl, config)
if !jsErr.IsNull() {
    js.Global().Call("alert", "WASM: Cannot load webgl ")
    return
}
rr.SetZoom(currentZoom)
defer rr.Release()

x, y, z := rr.GetSpeed()
sX.Set("value", f32(x, 'f', -1, 64))
if x > 0 {sXV.Set(ih, "+"+f32(x,  'f', 2, 64))}
if x == 0 {sXV.Set(ih, " "+f32(x,  'f', 2, 64))}
if x < 0 {sXV.Set(ih, f32(x,  'f', 2, 64))}
sY.Set("value", f32(y,  'f', -1, 64))
if y > 0 {sYV.Set(ih, "+"+f32(y,  'f', 2, 64))}
if y == 0 {sYV.Set(ih, "0"+f32(y,  'f', 2, 64))}
if y < 0 {sYV.Set(ih, f32(y,  'f', 2, 64))}
sZ.Set("value", f32(z,  'f', -1, 64))
if z > 0 {sZV.Set(ih, "+"+f32(z,  'f', 2, 64))}
if z == 0 {sZV.Set(ih, "0"+f32(z,  'f', 2, 64))}
if z < 0 {sZV.Set(ih, f32(z,  'f', 2, 64))}

	var renderFrame js.Func
	renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			rr.Render(this, args)
		js.Global().Call("requestAnimationFrame", renderFrame)
		return nil
	})
	js.Global().Call("requestAnimationFrame", renderFrame)

	done = make(chan struct{})

	<-done
}


func parseBase64File(input string) (output []byte, err js.Value) {
    searchString := "base64,"
    searchLength := len(searchString)
    var index = -1
    for i := 0; i <= len(input)-searchLength; i++ {
        if input[i:i+searchLength] == searchString {
            index = i
            break
        }
    }
    if index < 0 {
        err = js.Global().Get("Error").New("Error opening file")
        return
    }
    sBuffer := input[index+searchLength:]
    output, decodeErr := base64.StdEncoding.DecodeString(sBuffer)
    if decodeErr != nil {
        err = js.Global().Get("Error").New(decodeErr.Error())
        return
    }
    return output, js.Null()
}


func getMaxScalar(vertices []float32) float32 {
	var max float32
	for baseIndex := 0; baseIndex < len(vertices); baseIndex += 3 {
		testScale := scalar(vertices[baseIndex], vertices[baseIndex], vertices[baseIndex])
		if testScale > max {
			max = testScale
		}
	}
	return max
}

func scalar(x float32, y float32, z float32) float32 {
	xy := m.Sqrt(float64(x*x + y*y))
	return float32(m.Sqrt(xy*xy + float64(z*z)))
}

func cryptoRandFloat32() float32 {
	b := make([]byte, 4)
	_, err := rand.Read(b)
	if err != nil {
		panic("crypto/rand read failed: " + err.Error())
	}
	u := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
	return float32(u) / float32(m.MaxUint32)
}


func stopApplication(_ js.Value, _ []js.Value) interface{} {
	running = false
	sZoomV.Set(ih, float32(10000))
	currentZoom = float32(10000)
	rr.SetZoom(float32(10000))
	footer.Set(ih, originalHTML)
	js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
		close(done)
//		done <- struct{}{}
		return nil
	}), 5000)
	return nil
}

func sCX(this js.Value, _ []js.Value) interface{} {
    sSpeed := this.Get("value").String()
    s, _ := strconv.ParseFloat(sSpeed, 64)
    rr.SetX(float32(s))
    if s > 0 {        sXV.Set(ih, "+"+f64(s, 'f', 2, 32))    }
    if s == 0 {        sXV.Set(ih, "0"+f64(s, 'f', 2, 32))    }
    if s < 0 {        sXV.Set(ih, f64(s, 'f', 2, 32))    }
    return nil
}

func sCY(this js.Value, _ []js.Value) interface{} {
    sS := this.Get("value").String()
    s, _ := strconv.ParseFloat(sS, 64)
    rr.SetY(float32(s))
    if s > 0 {        sYV.Set(ih, "+"+f64(s, 'f', 2, 32))    }
    if s == 0 {        sYV.Set(ih, "0"+f64(s, 'f', 2, 32))    }
    if s < 0 {        sYV.Set(ih, f64(s, 'f', 2, 32))    }
    return nil
}

func sCZ(this js.Value, _ []js.Value) interface{} {
    sS := this.Get("value").String()
    s, _ := strconv.ParseFloat(sS, 64)
    rr.SetZ(float32(s))
    if s > 0 {        sZV.Set(ih, "+"+f64(s, 'f', 2, 32))    }
    if s == 0 {        sZV.Set(ih, "0"+f64(s, 'f', 2, 32))    }
    if s < 0 {        sZV.Set(ih, f64(s, 'f', 2, 32))    }
    return nil
}

func sCZoom(this js.Value, _ []js.Value) interface{} {
    sS := this.Get("value").String()
    s, _ := strconv.ParseFloat(sS, 64)
    if s < 10 {
			sZoomV.Set(ih, "000"+f64(s, 'f', 2, 32))
			} else if s < 100 {
				sZoomV.Set(ih, "00"+f64(s, 'f', 2, 32))
				} else if s < 1000 {
					sZoomV.Set(ih, "0"+f64(s, 'f', 2, 32))
					} else {
						sZoomV.Set(ih, f64(s, 'f', 2, 32))
					}
    currentZoom = float32(s)
    rr.SetZoom(currentZoom)
    return nil
}

// Model is an interface for a model
type Model interface {
	GetModel() ([]float32, []float32, []uint16)
}

func cryptoRandIntn(max int) (int, js.Value) {
    if max <= 0 {        return 0, js.Global().Get("Error").New("max must be a positive integer")    }
    numBytes := (max + 7) / 8
    maxBytes := 1 << (numBytes * 8)
    randBytes := make([]byte, numBytes)
    randNum := 0
    for {
        _, err := rand.Read(randBytes)
        if err != nil {            return 0, js.Global().Get("Error").New("error generating random number")        }
        for _, b := range randBytes {            randNum = (randNum << 8) | int(b)        }
        if randNum < maxBytes-maxBytes%max {            break        }
    }
    return randNum % max, js.Null()
}


// NewSTL returns a new STL & error
func NewSTL(buffer []byte) (o STL, err error) {
	bufferReader := bytes.NewReader(buffer)
	solid, err := stl.From(bufferReader)
	if err != nil {
		return
	}

	// Generate random rotation matrix
	rotationMatrix := randomRotationMatrix()

	// Generate colors
	numColors, _ := cryptoRandIntn(5)
	numColors += 2 // Random number between 2 and 6
	colors := GenerateGradient(numColors, int(solid.TriangleCount))

	var index uint32
	for i, triangle := range solid.Triangles {
		colorR := colors[i].Red
		colorG := colors[i].Green
		colorB := colors[i].Blue

		// Convert each triangle's vertices to custom Vertex type and apply rotation
		v0 := Vertex{X: float32(triangle.Vertices[0].X), Y: float32(triangle.Vertices[0].Y), Z: float32(triangle.Vertices[0].Z)}
		v1 := Vertex{X: float32(triangle.Vertices[1].X), Y: float32(triangle.Vertices[1].Y), Z: float32(triangle.Vertices[1].Z)}
		v2 := Vertex{X: float32(triangle.Vertices[2].X), Y: float32(triangle.Vertices[2].Y), Z: float32(triangle.Vertices[2].Z)}

		// Rotate and add vertices
		o.addRotatedVertex(&index, v0, rotationMatrix, colorR, colorG, colorB)
		o.addRotatedVertex(&index, v1, rotationMatrix, colorR, colorG, colorB)
		o.addRotatedVertex(&index, v2, rotationMatrix, colorR, colorG, colorB)
	}

	return o, err
}

// Add a rotated vertex to the STL structure
func (s *STL) addRotatedVertex(index *uint32, vertex Vertex, rotation mgl32.Mat4, r, g, b float32) {
	// Apply rotation
	rotatedVertex := rotation.Mul4x1(mgl32.Vec3{vertex.X, vertex.Y, vertex.Z}.Vec4(1.0))

	// Add rotated vertex to the STL struct
	s.v = append(s.v, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
	s.i = append(s.i, *index)
	s.c = append(s.c, r, g, b)
	(*index)++
}

// STL is a stereolithograph
type STL struct {
	v []float32
	c []float32
	i []uint32
}


// Define a custom Vertex type for storing the vertex data.
type Vertex struct {
	X, Y, Z float32
}


// GetModel gets the model
func (s STL) GetModel() ([]float32, []float32, []uint32) {
	return s.v, s.c, s.i
}

// InitialConfig is the initial config
type InitialConfig struct {
	W              int
	H             int
	X             float32
	Y             float32
	Z             float32
	Colors             []float32
	Vertices           []float32
	Indices            []uint32
	FSC string
	VSC   string
}

// Renderer is the renderer
type Renderer struct {
	glContext      js.Value
	glTypes        GLTypes
	colors         js.Value
	v       js.Value
	i        js.Value
	colorBuffer    js.Value
	vertexBuffer   js.Value
	indexBuffer    js.Value
	numIndices     int
	numVertices     int
	fragShader     js.Value
	vertShader     js.Value
	shaderProgram  js.Value
	tmark          float32
	rX      float32	//rotation X
	rY      float32
	rZ      float32
	movMatrix      mgl32.Mat4
	PositionMatrix js.Value
	ViewMatrix     js.Value
	ModelMatrix    js.Value
	height         int
	width          int
	sX         float32
	sY         float32
	sZ         float32
}

// NewRenderer returns a new renderer & error
func NewRenderer(gl js.Value, config InitialConfig) (r Renderer, err js.Value) {
	// Get some WebGL bindings
	r.glContext = gl
	err = r.glTypes.New(r.glContext)
	r.numIndices = len(config.Indices)
	r.numVertices = len(config.Vertices)
	r.movMatrix = mgl32.Ident4()
	r.width = config.W
	r.height = config.H

	r.sX = config.X
	r.sY = config.Y
	r.sZ = config.Z

	// Convert buffers to JS TypedArrays
	r.UpdateColorBuffer(config.Colors)
	r.UpdateVerticesBuffer(config.Vertices)
	r.UpdateIndicesBuffer(config.Indices)

	r.UpdateFragmentShader(config.FSC)
	r.UpdateVertexShader(config.VSC)
	r.updateShaderProgram()
	r.attachShaderProgram()

	r.setContextFlags()

	r.createMatrixes()
	r.EnableObject()
	return
}


// SetModel sets a new model
func (r *Renderer) SetModel(Colors []float32, Vertices []float32, Indices []uint32) {
	r.numIndices = len(Indices)
	r.UpdateColorBuffer(Colors)
	r.UpdateVerticesBuffer(Vertices)
	r.UpdateIndicesBuffer(Indices)
	r.EnableObject()
}

// Release releases the renderer
func (r *Renderer) Release() {
	return
}

// EnableObject enables the object
func (r *Renderer) EnableObject() {
	r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
}

// SetX set rotation x axis speed
func (r *Renderer) SetX(x float32) {
	r.sX = x
}

// SetY set rotation y axis speed
func (r *Renderer) SetY(y float32) {
	r.sY = y
}

// SetZ set rotation z axis speed
func (r *Renderer) SetZ(z float32) {
	r.sZ = z
}

// GetSpeed returns the rotation speeds
func (r *Renderer) GetSpeed() (x, y, z float32) {
	return r.sX, r.sY, r.sZ
}

// SetSize sets the size of the rendering
func (r *Renderer) SetSize(height, width int) {
	r.height = height
	r.width = width
}

func (r *Renderer) createMatrixes() {
	ratio := float32(r.width) / float32(r.height)
//	fmt.Println("Renderer.createMatrixes")
	projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), ratio, 1, 100000.0)
	projMatrixBuffer := (*[16]float32)(u.Pointer(&projMatrix)) // nolint
	typedProjMatrixBuffer := S2TA([]float32((*projMatrixBuffer)[:]))
	r.glContext.Call("uniformMatrix4fv", r.PositionMatrix, false, typedProjMatrixBuffer)

	viewMatrix := mgl32.LookAtV(mgl32.Vec3{3.0, 3.0, 3.0}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
	viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
	typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
	r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
}

func (r *Renderer) setContextFlags() {
	r.glContext.Call("clearColor", 0.0, 0.0, 0.0, 0.0) // Color the screen is cleared to
	r.glContext.Call("viewport", 0, 0, r.width, r.height) // Viewport size
	r.glContext.Call("depthFunc", r.glTypes.LEqual)
}

// UpdateFragmentShader Updates the Fragment Shader
func (r *Renderer) UpdateFragmentShader(shaderCode string) {
	r.fragShader = r.glContext.Call("createShader", r.glTypes.FragmentShader)
	r.glContext.Call("shaderSource", r.fragShader, shaderCode)
	r.glContext.Call("compileShader", r.fragShader)
}

// UpdateVertexShader updates the vertex shader
func (r *Renderer) UpdateVertexShader(shaderCode string) {
	r.vertShader = r.glContext.Call("createShader", r.glTypes.VertexShader)
	r.glContext.Call("shaderSource", r.vertShader, shaderCode)
	r.glContext.Call("compileShader", r.vertShader)
}

func (r *Renderer) updateShaderProgram() {
	if r.fragShader.IsUndefined() || r.vertShader.IsUndefined() {
		return
	}
	r.shaderProgram = r.glContext.Call("createProgram")
	r.glContext.Call("attachShader", r.shaderProgram, r.vertShader)
	r.glContext.Call("attachShader", r.shaderProgram, r.fragShader)
	r.glContext.Call("linkProgram", r.shaderProgram)
}

const gul = "getUniformLocation"
func (r *Renderer) attachShaderProgram() {
	r.PositionMatrix = r.glContext.Call(gul, r.shaderProgram, "Pmatrix")
	r.ViewMatrix = r.glContext.Call(gul, r.shaderProgram, "Vmatrix")
	r.ModelMatrix = r.glContext.Call(gul, r.shaderProgram, "Mmatrix")

	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
	position := r.glContext.Call("getAttribLocation", r.shaderProgram, "position")
	r.glContext.Call("vertexAttribPointer", position, 3, r.glTypes.Float, false, 0, 0)
	r.glContext.Call("enableVertexAttribArray", position)

	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
	color := r.glContext.Call("getAttribLocation", r.shaderProgram, "color")
	r.glContext.Call("vertexAttribPointer", color, 3, r.glTypes.Float, false, 0, 0)
	r.glContext.Call("enableVertexAttribArray", color)

	r.glContext.Call("useProgram", r.shaderProgram)
	if stlFileName == ".stl" || stlFileName == "" {

	uBaseColor := r.glContext.Call(gul, r.shaderProgram, "uBaseColor")
	uTopColor := r.glContext.Call(gul, r.shaderProgram, "uTopColor")
	uColor := r.glContext.Call(gul, r.shaderProgram, "uColor")
	r.glContext.Call("uniform3f", uBaseColor, 1.0, 0.0, 0.0)
	r.glContext.Call("uniform3f", uTopColor, 0.0, 0.0, 1.0)
	r.glContext.Call("uniform3f", uColor, 1.0, 1.0, 1.0)
}
}

// UpdateColorBuffer Updates the ColorBuffer
func (r *Renderer) UpdateColorBuffer(buffer []float32) {
	r.colors = S2TA(buffer)
	if r.colorBuffer.IsUndefined() {
		r.colorBuffer = r.glContext.Call("createBuffer")
	}
	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
	r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.colors, r.glTypes.StaticDraw)
}

// UpdateVerticesBuffer Updates the VerticesBuffer
func (r *Renderer) UpdateVerticesBuffer(buffer []float32) {
	r.v = S2TA(buffer)
	if r.vertexBuffer.IsUndefined() {
		r.vertexBuffer = r.glContext.Call("createBuffer")
	}
	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
	r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.v, r.glTypes.StaticDraw)
}

// UpdateIndicesBuffer Updates the IndicesBuffer
func (r *Renderer) UpdateIndicesBuffer(buffer []uint32) {
	r.i = S2TA(buffer)
	if r.indexBuffer.IsUndefined() {
		r.indexBuffer = r.glContext.Call("createBuffer")
	}
	r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
	r.glContext.Call("bufferData", r.glTypes.ElementArrayBuffer, r.i, r.glTypes.StaticDraw)
}

// Render renders
func (r *Renderer) Render(_ js.Value, args []js.Value) interface{} { // nolint
	now := float32(args[0].Float())
	tdiff := now - r.tmark
	r.tmark = now
	r.rX = r.rX + r.sX*float32(tdiff)/500
	r.rY = r.rY + r.sY*float32(tdiff)/500
	r.rZ = r.rZ + r.sZ*float32(tdiff)/500

	r.movMatrix = mgl32.HomogRotate3DX(r.rX)
	r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DY(r.rY))
	r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DZ(r.rZ))

	modelMatrixBuffer := (*[16]float32)(u.Pointer(&r.movMatrix)) // nolint
	typedModelMatrixBuffer := S2TA([]float32((*modelMatrixBuffer)[:]))

	r.glContext.Call("uniformMatrix4fv", r.ModelMatrix, false, typedModelMatrixBuffer)

	r.glContext.Call("enable", r.glTypes.DepthTest)
	r.glContext.Call("clear", r.glTypes.ColorBufferBit)
	r.glContext.Call("clear", r.glTypes.DepthBufferBit)
	usegltype := r.glTypes.Triangles
	if stlFileName == ".stl" || stlFileName == ""  {
			usegltype = r.glTypes.Line
			r.glContext.Call("drawArrays", r.glTypes.LineLoop, 0, r.numVertices/3)
	}
r.glContext.Call("drawElements", usegltype, r.numIndices, r.glTypes.UnsignedInt, 0)

	return nil
}

// SetZoom Sets the Zoom
func (r *Renderer) SetZoom(currentZoom float32) {
	viewMatrix := mgl32.LookAtV(mgl32.Vec3{currentZoom, currentZoom, currentZoom}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
	viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
	typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
	r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
}

// NewColorInterpolation generates color interpolation
func NewColorInterpolation(a Color, b Color) ColorInterpolation {
	return ColorInterpolation{
		a,
		b,
		a.Subtract(b),
	}
}

// ColorInterpolation is interpolated color
type ColorInterpolation struct {
	startColor Color
	endColor   Color
	deltaColor Color
}

// Interpolate interpolates
func (c ColorInterpolation) Interpolate(percent float32) Color {
	scaled := c.deltaColor.MultiplyFloat(percent)
	return c.startColor.Add(scaled)
}

// Color represents a color
type Color struct {
	Red   float32
	Green float32
	Blue  float32
}

// NewRandomColor returns a New RandomColor
func NewRandomColor() Color {
	const maxRGB = 255
	var r, g, b float64
	buf := make([]byte, 3)
	rand.Read(buf)
	r = float64(buf[0]) / 256
	g = float64(buf[1]) / 256
	b = float64(buf[2]) / 256
	r = r * maxRGB
	g = g * maxRGB
	b = b * maxRGB
	return Color{float32(r), float32(g), float32(b)}
}

// Subtract Subtracts color
func (c Color) Subtract(d Color) Color {
	return Color{
		c.Red - d.Red,
		c.Green - d.Green,
		c.Blue - d.Blue,
	}
}

// Add Adds color
func (c Color) Add(d Color) Color {
	return Color{
		c.Red + d.Red,
		c.Green + d.Green,
		c.Blue + d.Blue,
	}
}

// MultiplyFloat Multiplies Float
func (c Color) MultiplyFloat(x float32) Color {
	return Color{
		c.Red * x,
		c.Green * x,
		c.Blue * x,
	}
}

// GenerateGradient Generates Gradient
func GenerateGradient(numColors int, steps int) []Color {
	distribution := distributeColors(numColors, steps)
	colors := make([]Color, numColors)
	for i := 0; i < numColors; i++ {
		colors[i] = NewRandomColor()
	}
	outputBuffer := make([]Color, 0, steps)
	for index := 0; index < numColors; index++ {
		if index >= numColors-1 {
			size := steps - distribution[index]
			interpolation := NewColorInterpolation(colors[index-1], colors[index])
			buffer := generateSingleGradient(interpolation, size)
			outputBuffer = append(outputBuffer, buffer...)
			break
		}
		currentStep := distribution[index]
		nextStep := distribution[index+1]
		size := nextStep - currentStep
		interpolation := NewColorInterpolation(colors[index], colors[index+1])
		buffer := generateSingleGradient(interpolation, size)
		outputBuffer = append(outputBuffer, buffer...)
	}
	return outputBuffer
}

func distributeColors(numColors int, steps int) []int {
	diff := int(m.Ceil(float64(steps) / float64(numColors)))
	output := make([]int, numColors)
	for i := 0; i < numColors; i++ {
		output[i] = diff * i
	}
	return output
}

func generateSingleGradient(c ColorInterpolation, numSteps int) []Color {
	output := make([]Color, numSteps)
	for i := 0; i < numSteps; i++ {
		percent := float32(i) / float32(numSteps)
		output[i] = c.Interpolate(percent)
	}
	return output
}

func f32(f float32, g byte, prec, bitSize int) string {
	return strconv.FormatFloat(float64(f), g, prec, bitSize)
}
func f64(f float64, g byte, prec, bitSize int) string {
	return strconv.FormatFloat(f, g, prec, bitSize)
}


func generateSphereVertices(radius float32, stacks, slices int) ([]float32, []uint32) {
	var vertices []float32
	var indices []uint32

	// Generate random initial rotation
	rotationMatrix := randomRotationMatrix()

	// Generate sphere vertices
	for i := 0; i <= stacks; i++ {
		phi := float32(i) * float32(m.Pi) / float32(stacks)
		for j := 0; j <= slices; j++ {
			theta := float32(j) * 2.0 * float32(m.Pi) / float32(slices)
			x := radius * float32(m.Sin(float64(phi))) * float32(m.Cos(float64(theta)))
			y := radius * float32(m.Sin(float64(phi))) * float32(m.Sin(float64(theta)))
			z := radius * float32(m.Cos(float64(phi)))

			// Apply rotation to the vertex
			vertex := mgl32.Vec3{z, y, x}
			rotatedVertex := rotationMatrix.Mul4x1(vertex.Vec4(1.0))

			// Append rotated vertex
			vertices = append(vertices, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
		}
	}

	// Generate sphere indices
	for i := 0; i < stacks; i++ {
		for j := 0; j <= slices; j++ {
			indices = append(indices, uint32(i*(slices+1)+j), uint32((i+1)*(slices+1)+j))
		}
	}

	return vertices, indices
}

func randomRotationMatrix() mgl32.Mat4 {
	// Generate random rotation angles (in radians) using crypto/rand
	rotX := randomFloat32() * 2 * float32(m.Pi)
	rotY := randomFloat32() * 2 * float32(m.Pi)
	rotZ := randomFloat32() * 2 * float32(m.Pi)

	// Create rotation matrices for each axis
	rotMatrixX := mgl32.HomogRotate3DX(rotX)
	rotMatrixY := mgl32.HomogRotate3DY(rotY)
	rotMatrixZ := mgl32.HomogRotate3DZ(rotZ)

	// Combine rotations (Z * Y * X)
	return rotMatrixZ.Mul4(rotMatrixY).Mul4(rotMatrixX)
}

func randomFloat32() float32 {
	var randomValue uint32
	err := binary.Read(rand.Reader, binary.BigEndian, &randomValue)
	if err != nil {
		log.Fatalf("Failed to read random value: %v", err)
	}
	return float32(randomValue) / float32(0xFFFFFFFF)
}


var verticesNative = []float32{
	-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
	-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
	-1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1,
	1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1,
	-1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1,
	-1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1,
}
var colorsNative = []float32{
	5, 3, 7, 5, 3, 7, 5, 3, 7, 5, 3, 7,
	1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3,
	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
	1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
}

var indicesNative = []uint32{
	0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
	8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
	16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
}

const vertShaderCode = `
attribute vec3 position;
uniform mat4 Pmatrix;
uniform mat4 Vmatrix;
uniform mat4 Mmatrix;
attribute vec3 color;
varying vec3 vColor;

void main(void) {
	gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);
	vColor = color;
}
`
const fragShaderCode = `
precision mediump float;
varying vec3 vColor;
void main(void) {
	gl_FragColor = vec4(vColor, 1.);
}
`
const fragShaderCode1 = `
precision mediump float;
uniform vec3 uBaseColor; // Color value at the base
uniform vec3 uTopColor;  // Color value at the top
varying vec3 vPosition;  // Interpolated vertex position
void main(void) {
	float t = (vPosition.y + 1.0) * 0.5; // Normalize the y-coordinate to [0, 1]
	vec3 rainbowColor = mix(uBaseColor, uTopColor, t);
	gl_FragColor = vec4(rainbowColor, 1.0);
}
`
const 	vertShaderCode1 = `
	attribute vec3 position;
	uniform mat4 Pmatrix;
	uniform mat4 Vmatrix;
	uniform mat4 Mmatrix;
	varying vec3 vPosition;  // Pass vertex position to fragment shader
	void main(void) {
		gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4(position, 1.0);
		vPosition = position;  // Pass vertex position to fragment shader
	}
	`

// GLTypes provides WebGL bindings.
type GLTypes struct {
	StaticDraw         js.Value
	ArrayBuffer        js.Value
	ElementArrayBuffer js.Value
	VertexShader       js.Value
	FragmentShader     js.Value
	Float              js.Value
	DepthTest          js.Value
	ColorBufferBit     js.Value
	DepthBufferBit     js.Value
	Triangles          js.Value
	UnsignedShort      js.Value
	UnsignedInt        js.Value
	LEqual             js.Value
	LineLoop           js.Value
	Line               js.Value

}

// New grabs the WebGL bindings from a GL context.
func (types *GLTypes) New(gl js.Value) js.Value {
	types.StaticDraw = gl.Get("STATIC_DRAW")
	types.ArrayBuffer = gl.Get("ARRAY_BUFFER")
	types.ElementArrayBuffer = gl.Get("ELEMENT_ARRAY_BUFFER")
	types.VertexShader = gl.Get("VERTEX_SHADER")
	types.FragmentShader = gl.Get("FRAGMENT_SHADER")
	types.Float = gl.Get("FLOAT")
	types.DepthTest = gl.Get("DEPTH_TEST")
	types.ColorBufferBit = gl.Get("COLOR_BUFFER_BIT")
	types.Triangles = gl.Get("TRIANGLES")
	types.UnsignedShort = gl.Get("UNSIGNED_SHORT")
	types.LEqual = gl.Get("LEQUAL")
	types.DepthBufferBit = gl.Get("DEPTH_BUFFER_BIT")
	types.LineLoop = gl.Get("LINE_LOOP")
	types.Line = gl.Get("LINES")
	enabled := gl.Call("getExtension", "OES_element_index_uint")
	if !enabled.Truthy() {
		return js.Global().Get("Error").New("missing extension: OES_element_index_uint")
	}
	types.UnsignedInt = gl.Get("UNSIGNED_INT")
	return js.Null()
}

func sliceToByteSlice(s interface{}) []byte {
	switch s := s.(type) {
	case []int8:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		return *(*[]byte)(u.Pointer(h))            // nolint
	case []int16:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 2
		h.Cap *= 2
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []int32:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 4
		h.Cap *= 4
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []int64:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 8
		h.Cap *= 8
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []uint8:
		return s
	case []uint16:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 2
		h.Cap *= 2
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []uint32:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 4
		h.Cap *= 4
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []uint64:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 8
		h.Cap *= 8
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []float32:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 4
		h.Cap *= 4
		return *(*[]byte)(u.Pointer(h)) // nolint
	case []float64:
		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
		h.Len *= 8
		h.Cap *= 8
		return *(*[]byte)(u.Pointer(h)) // nolint
	default:
//		panic("jsutil: unexpected value at sliceToBytesSlice: " + r.TypeOf(s).String())
		panic("jsutil: unexpected value at sliceToBytesSlice: ")
	}
}

const bo = "byteOffset"
const bl = "byteLength"
const b = "buffer"
const u8a = "Uint8Array"
// S2TA converts Slice To TypedArray
func S2TA(s interface{}) js.Value {
	switch s := s.(type) {
	case []int8:
		a := js.Global().Get(u8a).New(len(s))
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Int8Array").New(buf, a.Get(bo), a.Get(bl))
	case []int16:
		a := js.Global().Get(u8a).New(len(s) * 2)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Int16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
	case []int32:
		a := js.Global().Get(u8a).New(len(s) * 4)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Int32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
	case []uint8:
		a := js.Global().Get(u8a).New(len(s))
		js.CopyBytesToJS(a, s)
		runtime.KeepAlive(s)
		return a
	case []uint16:
		a := js.Global().Get(u8a).New(len(s) * 2)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Uint16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
	case []uint32:
		a := js.Global().Get(u8a).New(len(s) * 4)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Uint32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
	case []float32:
		a := js.Global().Get(u8a).New(len(s) * 4)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Float32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
	case []float64:
		a := js.Global().Get(u8a).New(len(s) * 8)
		js.CopyBytesToJS(a, sliceToByteSlice(s))
		runtime.KeepAlive(s)
		buf := a.Get(b)
		return js.Global().Get("Float64Array").New(buf, a.Get(bo), a.Get(bl).Int()/8)
	default:
//		panic("jsutil: unexpected value at S2TA: " + r.TypeOf(s).String())
		panic("jsutil: unexpected value at S2TA: ")
	}
}