package osc

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
	"net"
	"strings"
	"sync"
)

type Value struct {
	Type  byte
	Float float32
	Int   int32
	Bool  bool
	Str   string
}

type Handler func(address string, args []Value) error

type Server struct {
	addr     string
	conn     *net.UDPConn
	handlers map[string]Handler
	mu       sync.RWMutex
}

func NewServer(host string, port int) *Server {
	return &Server{
		addr:     fmt.Sprintf("%s:%d", host, port),
		handlers: map[string]Handler{},
	}
}

func (s *Server) Map(param string, h Handler) { s.handlers["/avatar/parameters/"+param] = h }
func (s *Server) Close() error {
	if s.conn != nil {
		return s.conn.Close()
	}
	return nil
}

func (s *Server) Serve() error {
	addr, err := net.ResolveUDPAddr("udp", s.addr)
	if err != nil {
		return err
	}
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		return err
	}
	s.conn = conn
	buf := make([]byte, 2048)
	for {
		n, _, err := conn.ReadFromUDP(buf)
		if err != nil {
			return err
		}
		a, v, err := parseMessage(buf[:n])
		if err != nil {
			continue
		}
		s.mu.RLock()
		h := s.handlers[a]
		s.mu.RUnlock()
		if h != nil {
			_ = h(a, v)
		}
	}
}

func parseMessage(b []byte) (string, []Value, error) {
	address, offset, err := parsePaddedString(b, 0)
	if err != nil {
		return "", nil, err
	}
	if !strings.HasPrefix(address, "/") {
		return "", nil, fmt.Errorf("invalid address")
	}
	if offset >= len(b) {
		return address, nil, nil
	}
	typetags, offset, err := parsePaddedString(b, offset)
	if err != nil {
		return "", nil, err
	}
	if !strings.HasPrefix(typetags, ",") {
		return address, nil, nil
	}
	var values []Value
	for _, tag := range typetags[1:] {
		switch tag {
		case 'i':
			if offset+4 > len(b) {
				return "", nil, fmt.Errorf("invalid int")
			}
			values = append(values, Value{Type: 'i', Int: int32(binary.BigEndian.Uint32(b[offset : offset+4]))})
			offset += 4
		case 'f':
			if offset+4 > len(b) {
				return "", nil, fmt.Errorf("invalid float")
			}
			bits := binary.BigEndian.Uint32(b[offset : offset+4])
			values = append(values, Value{Type: 'f', Float: math.Float32frombits(bits)})
			offset += 4
		case 's':
			s, next, err := parsePaddedString(b, offset)
			if err != nil {
				return "", nil, err
			}
			values = append(values, Value{Type: 's', Str: s})
			offset = next
		case 'T':
			values = append(values, Value{Type: 'T', Bool: true})
		case 'F':
			values = append(values, Value{Type: 'F', Bool: false})
		}
	}
	return address, values, nil
}

func parsePaddedString(b []byte, offset int) (string, int, error) {
	if offset >= len(b) {
		return "", offset, fmt.Errorf("invalid osc string")
	}
	end := bytes.IndexByte(b[offset:], 0)
	if end < 0 {
		return "", offset, fmt.Errorf("invalid osc string")
	}
	s := string(b[offset : offset+end])
	next := offset + end + 1
	for next%4 != 0 {
		next++
	}
	if next > len(b) {
		next = len(b)
	}
	return s, next, nil
}
