package socketio

import (
	"fmt"
	"reflect"
	"sync"

	"github.com/googollee/go-socket.io/parser"
)

// Namespace describes a communication channel that allows you to split the logic of your application
// over a single shared connection.
type Namespace interface {
	// Context of this connection. You can save one context for one
	// connection, and share it between all handlers. The handlers
	// are called in one goroutine, so no need to lock context if it
	// only accessed in one connection.
	Context() interface{}
	SetContext(ctx interface{})

	Namespace() string
	Emit(eventName string, v ...interface{})

	Join(room string)
	Leave(room string)
	LeaveAll()
	Rooms() []string
}

type namespaceConn struct {
	*conn
	broadcast Broadcast

	namespace string
	context   interface{}

	ack sync.Map
}

func newNamespaceConn(conn *conn, namespace string, broadcast Broadcast) *namespaceConn {
	return &namespaceConn{
		conn:      conn,
		namespace: namespace,
		broadcast: broadcast,
	}
}

func (nc *namespaceConn) SetContext(ctx interface{}) {
	nc.context = ctx
}

func (nc *namespaceConn) Context() interface{} {
	return nc.context
}

func (nc *namespaceConn) Namespace() string {
	return nc.namespace
}

func (nc *namespaceConn) Emit(eventName string, v ...interface{}) {
	header := parser.Header{
		Type: parser.Event,
	}

	if nc.namespace != aliasRootNamespace {
		header.Namespace = nc.namespace
	}

	if l := len(v); l > 0 {
		last := v[l-1]
		lastV := reflect.TypeOf(last)

		if lastV.Kind() == reflect.Func {
			f := newAckFunc(last)

			header.ID = nc.conn.nextID()
			header.NeedAck = true

			nc.ack.Store(header.ID, f)
			v = v[:l-1]
		}
	}

	args := make([]reflect.Value, len(v)+1)
	args[0] = reflect.ValueOf(eventName)

	for i := 1; i < len(args); i++ {
		args[i] = reflect.ValueOf(v[i-1])
	}

	nc.conn.write(header, args...)
}

func (nc *namespaceConn) Join(room string) {
	nc.broadcast.Join(room, nc)
}

func (nc *namespaceConn) Leave(room string) {
	nc.broadcast.Leave(room, nc)
}

func (nc *namespaceConn) LeaveAll() {
	nc.broadcast.LeaveAll(nc)
}

func (nc *namespaceConn) Rooms() []string {
	return nc.broadcast.Rooms(nc)
}

func (nc *namespaceConn) dispatch(header parser.Header) {
	if header.Type != parser.Ack {
		return
	}

	rawFunc, ok := nc.ack.Load(header.ID)
	if ok {
		f, ok := rawFunc.(*funcHandler)
		if !ok {
			nc.conn.onError(nc.namespace, fmt.Errorf("incorrect data stored for header %d", header.ID))
			return
		}

		nc.ack.Delete(header.ID)

		args, err := nc.conn.parseArgs(f.argTypes)
		if err != nil {
			nc.conn.onError(nc.namespace, err)
			return
		}
		if _, err := f.Call(args); err != nil {
			nc.conn.onError(nc.namespace, err)
			return
		}
	}
}
