package cursor

import (
    "arimelody-web/model"
    "fmt"
    "math/rand"
    "net/http"
    "strconv"
    "strings"
    "sync"
    "time"

    "github.com/gorilla/websocket"
)

type CursorClient struct {
    ID int32
    Conn *websocket.Conn
    Route string
    X float32
    Y float32
    Click bool
    Disconnected bool
}

type CursorMessage struct {
    Data []byte
    Route string
    Exclude []*CursorClient
}

func (client *CursorClient) Send(data []byte) {
    err := client.Conn.WriteMessage(websocket.TextMessage, data)
    if err != nil {
        client.Disconnect()
    }
}

func (client *CursorClient) Disconnect() {
    client.Disconnected = true
    broadcast <- CursorMessage{
        []byte(fmt.Sprintf("leave:%d", client.ID)),
        client.Route,
        []*CursorClient{},
    }
}

var clients = make(map[int32]*CursorClient)
var broadcast = make(chan CursorMessage)
var mutex = &sync.Mutex{}

func StartCursor(app *model.AppState) {
    var includes = func (clients []*CursorClient, client *CursorClient) bool {
        for _, c := range clients {
            if c.ID == client.ID { return true }
        }
        return false
    }
    
    log("Cursor message handler ready!")

    for {
        message := <-broadcast
        mutex.Lock()
        for _, client := range clients {
            if client.Route != message.Route { continue }
            if includes(message.Exclude, client) { continue }
            client.Send(message.Data)
        }
        mutex.Unlock()
    }
}

func handleClient(client *CursorClient) {
    msgType, message, err := client.Conn.ReadMessage()
    if err != nil {
        client.Disconnect()
        return
    }
    if msgType != websocket.TextMessage { return }

    args := strings.Split(string(message), ":")
    if len(args) == 0 { return }
    switch args[0] {
    case "loc":
        if len(args) < 2 { return }
        
        client.Route = args[1]

        mutex.Lock()
        for otherClientID, otherClient := range clients {
            if otherClientID == client.ID || otherClient.Route != client.Route { continue }
            client.Send([]byte(fmt.Sprintf("join:%d", otherClientID)))
            client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClientID, otherClient.X, otherClient.Y)))
        }
        mutex.Unlock()
        
        broadcast <- CursorMessage{
            []byte(fmt.Sprintf("join:%d", client.ID)),
            client.Route,
            []*CursorClient{ client },
        }
    case "char":
        if len(args) < 2 { return }
        // haha, turns out using ':' as a separator means you can't type ':'s
        // i should really be writing byte packets, not this nonsense
        msg := byte(':')
        if len(args[1]) > 0 {
            msg = args[1][0]
        }
        broadcast <- CursorMessage{
            []byte(fmt.Sprintf("char:%d:%c", client.ID, msg)),
            client.Route,
            []*CursorClient{ client },
        }
    case "nochar":
        broadcast <- CursorMessage{
            []byte(fmt.Sprintf("nochar:%d", client.ID)),
            client.Route,
            []*CursorClient{ client },
        }
    case "click":
        if len(args) < 2 { return }
        click := 0
        if args[1][0] == '1' {
            click = 1
        }
        broadcast <- CursorMessage{
            []byte(fmt.Sprintf("click:%d:%d", client.ID, click)),
            client.Route,
            []*CursorClient{ client },
        }
    case "pos":
        if len(args) < 3 { return }
        x, err := strconv.ParseFloat(args[1], 32)
        y, err := strconv.ParseFloat(args[2], 32)
        if err != nil { return }
        client.X = float32(x)
        client.Y = float32(y)
        broadcast <- CursorMessage{
            []byte(fmt.Sprintf("pos:%d:%f:%f", client.ID, client.X, client.Y)),
            client.Route,
            []*CursorClient{ client },
        }
    }
}

func Handler(app *model.AppState) http.HandlerFunc {
    var upgrader = websocket.Upgrader{
        CheckOrigin: func (r *http.Request) bool {
            origin := r.Header.Get("Origin")
            return origin == app.Config.BaseUrl
        },
    }

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        conn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            log("Failed to upgrade to WebSocket connection: %v\n", err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
        }
        defer conn.Close()

        client := CursorClient{
            ID: rand.Int31(),
            Conn: conn,
            X: 0.0,
            Y: 0.0,
            Disconnected: false,
        }

        err = client.Conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("id:%d", client.ID)))
        if err != nil {
            client.Conn.Close()
            return
        }

        mutex.Lock()
        clients[client.ID] = &client
        mutex.Unlock()

        // log("Client connected: %s (%s)", fmt.Sprintf("0x%08x", client.ID), client.Conn.RemoteAddr().String())

        for {
            if client.Disconnected {
                mutex.Lock()
                delete(clients, client.ID)
                client.Conn.Close()
                mutex.Unlock()
                return
            }
            handleClient(&client)
        }
    })
}

func log(format string, args ...any) {
    logString := fmt.Sprintf(format, args...)
    fmt.Printf("[%s] [CURSOR] %s\n", time.Now().Format(time.UnixDate), logString)
}
