package main

import (
	"log"
	"net"
	"time"

	"github.com/g3n/engine/core"
	"github.com/g3n/engine/geometry"
	"github.com/g3n/engine/graphic"
	"github.com/g3n/engine/material"
	"github.com/g3n/engine/math32"
)

var dials []Dial

type Dial struct {
	Connection net.Conn           // the real deal underlying TCP connection
	Geo        *geometry.Geometry // the 3D geometry
	Mat        *material.Standard // the material / color
	Mesh       *graphic.Mesh      // the combined geometry and material
	Node       *core.Node         // pointer to node in scene, required for removing the object if connection goes down

}

// Keeps the pool full, replaces stale connections, and at the same time creates the objects in the 3D scenes representing physical connections
// should be ran in its own goroutine
func connPoolWatchdog(serverAddress string, maxPoolSize int, scene *core.Node) {

	dials = make([]Dial, 0)

	tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress)
	if err != nil {
		log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err)
	}

	for {

		// Check status of existing open connections in pool
		i := 0 // output index
		for _, dial := range dials {
			if !isConnUp(dial.Connection) { // TBD: this should be moved to check before first write to the socket file descriptor instead of polling here
				log.Printf("closing bad idle connection and removing from pool - %s", dial.Connection.LocalAddr().String())
				dial.Connection.Close()
				scene.Remove(dial.Mesh)
				continue
			}
			dials[i] = dial
			i++
		}
		dials = dials[:i]

		// fill any empty slots in the pool with fresh connections
		for poolSize := len(dials); poolSize < maxPoolSize; poolSize++ {
			log.Printf("Current pool size is '%d', desired pool size is '%d' - opening new connection...", poolSize, maxPoolSize)
			conn, err := net.DialTCP("tcp", nil, tcpAddr)
			if err != nil {
				log.Printf("failed to dial TCP connection: %v", err)
				break
			}

			conn.SetKeepAlive(true)
			conn.SetKeepAlivePeriod(time.Second * 5)

			dial := Dial{Connection: conn, Geo: geometry.NewBox(1, 1, 1), Mat: material.NewStandard(math32.NewColor("LimeGreen"))}
			dial.Mesh = graphic.NewMesh(dial.Geo, dial.Mat)
			dial.Node = scene.Add(dial.Mesh)
			dials = append(dials, dial)
		}

		// Update the position of the connections on the scene
		padding := float32(200 / (len(dials) + 1)) // space between the connection objects in 3D space
		currX := float32(len(dials)/2) * -padding  // variable gets updated, this is the initial starting position (left most connection in 3D space)
		if len(dials)%2 == 0 {
			currX += padding / 2
		}
		//currX := padding - 100
		for _, dial := range dials {
			dial.Mesh.SetPositionX(currX)
			currX += padding
		}

		time.Sleep(time.Second * 5) // random sleep, AKA evidence this shouldn't be a watchdog,
		// this whole function should be event based instead of polling
	}
}

// Checks with OS to ensure that a connection is still active
// returns err if connection is not active
func isConnUp(conn net.Conn) bool {
	conn.SetReadDeadline(time.Now().Add(time.Millisecond * 300))

	buf := make([]byte, 128)
	_, err := conn.Read(buf)
	if err != nil {
		log.Printf("connection error detected: %v", err)
		return false
	}

	var zero time.Time
	conn.SetReadDeadline(zero)

	return true
}
