// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// SRPC server

package srpc

import (
	"bytes";
	"log";
	"os";
	"syscall";
)

// TODO(rsc): I'd prefer to make this
//	type Handler func(m *msg) Errno
// but NaCl can't use closures.
// The explicit interface is a way to attach state.

// A Handler is a handler for an SRPC method.
// It reads arguments from arg, checks size for array limits,
// writes return values to ret, and returns an Errno status code.
type Handler interface {
	Run(arg, ret []interface{}, size []int) Errno;
}

type method struct {
	name	string;
	fmt	string;
	handler	Handler;
}

var rpcMethod []method

// BUG(rsc): Add's format string should be replaced by analyzing the
// type of an arbitrary func passed in an interface{} using reflection.

// Add registers a handler for the named method.
// Fmt is a Native Client format string, a sequence of
// alphabetic characters representing the types of the parameter values,
// a colon, and then a sequence of alphabetic characters
// representing the types of the returned values.
// The format characters and corresponding dynamic types are:
//
//	b	bool
//	C	[]byte
//	d	float64
//	D	[]float64
//	h	int	// a file descriptor (aka handle)
//	i	int32
//	I	[]int32
//	s	string
//
func Add(name, fmt string, handler Handler) {
	n := len(rpcMethod);
	if n >= cap(rpcMethod) {
		a := make([]method, n, (n+4)*2);
		for i := range a {
			a[i] = rpcMethod[i];
		}
		rpcMethod = a;
	}
	rpcMethod = rpcMethod[0 : n+1];
	rpcMethod[n] = method{name, fmt, handler};
}

// Serve accepts new SRPC connections from the file descriptor fd
// and answers RPCs issued on those connections.
// It closes fd and returns an error if the imc_accept system call fails.
func Serve(fd int) os.Error {
	defer syscall.Close(fd);

	for {
		cfd, _, e := syscall.Syscall(syscall.SYS_IMC_ACCEPT, uintptr(fd), 0, 0);
		if e != 0 {
			return os.NewSyscallError("imc_accept", int(e));
		}
		go serveLoop(int(cfd));
	}
	panic("unreachable");
}

func serveLoop(fd int) {
	c := make(chan *msg);
	go sendLoop(fd, c);

	var r msgReceiver;
	r.fd = fd;
	for {
		m, err := r.recv();
		if err != nil {
			break;
		}
		m.unpackRequest();
		if !m.gotHeader {
			log.Stderrf("cannot unpack header: %s", m.status);
			continue;
		}
		// log.Stdoutf("<- %#v", m);
		m.isReq = false;	// set up for response
		go serveMsg(m, c);
	}
	close(c);
}

func sendLoop(fd int, c <-chan *msg) {
	var s msgSender;
	s.fd = fd;
	for m := range c {
		// log.Stdoutf("-> %#v", m);
		m.packResponse();
		s.send(m);
	}
	syscall.Close(fd);
}

func serveMsg(m *msg, c chan<- *msg) {
	if m.status != OK {
		c <- m;
		return;
	}
	if m.rpcNumber >= uint32(len(rpcMethod)) {
		m.status = ErrBadRPCNumber;
		c <- m;
		return;
	}

	meth := &rpcMethod[m.rpcNumber];
	if meth.fmt != m.fmt {
		switch {
		case len(m.fmt) < len(meth.fmt):
			m.status = ErrTooFewArgs;
		case len(m.fmt) > len(meth.fmt):
			m.status = ErrTooManyArgs;
		default:
			// There's a type mismatch.
			// It's an in-arg mismatch if the mismatch happens
			// before the colon; otherwise it's an out-arg mismatch.
			m.status = ErrInArgTypeMismatch;
			for i := 0; i < len(m.fmt) && m.fmt[i] == meth.fmt[i]; i++ {
				if m.fmt[i] == ':' {
					m.status = ErrOutArgTypeMismatch;
					break;
				}
			}
		}
		c <- m;
		return;
	}

	m.status = meth.handler.Run(m.Arg, m.Ret, m.Size);
	c <- m;
}

// ServeRuntime serves RPCs issued by the Native Client embedded runtime.
// This should be called by main once all methods have been registered using Add.
func ServeRuntime() os.Error {
	// Call getFd to check that we are running embedded.
	if _, err := getFd(); err != nil {
		return err;
	}

	// We are running embedded.
	// The fd returned by getFd is a red herring.
	// Accept connections on magic fd 3.
	return Serve(3);
}

// getFd runs the srpc_get_fd system call.
func getFd() (fd int, err os.Error) {
	r1, _, e := syscall.Syscall(syscall.SYS_SRPC_GET_FD, 0, 0, 0);
	return int(r1), os.NewSyscallError("srpc_get_fd", int(e));
}

// Enabled returns true if SRPC is enabled in the Native Client runtime.
func Enabled() bool {
	_, err := getFd();
	return err == nil;
}

// Service #0, service_discovery, returns a list of the other services
// and their argument formats.
type serviceDiscovery struct{}

func (serviceDiscovery) Run(arg, ret []interface{}, size []int) Errno {
	var b bytes.Buffer;
	for _, m := range rpcMethod {
		b.WriteString(m.name);
		b.WriteByte(':');
		b.WriteString(m.fmt);
		b.WriteByte('\n');
	}
	if b.Len() > size[0] {
		return ErrNoMemory;
	}
	ret[0] = b.Bytes();
	return OK;
}

func init() {
	Add("service_discovery", ":C", serviceDiscovery{});
}
