// 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.

// Native Client audio/video

// Package av implements audio and video access for Native Client
// binaries running standalone or embedded in a web browser window.
//
// The C version of the API is documented at
// http://nativeclient.googlecode.com/svn/data/docs_tarball/nacl/googleclient/native_client/scons-out/doc/html/group__audio__video.html
package av

import (
	"bytes";
	"exp/draw";
	"exp/nacl/srpc";
	"log";
	"os";
	"syscall";
	"unsafe";
)

var srpcEnabled = srpc.Enabled()

// native_client/src/trusted/service_runtime/include/sys/audio_video.h

// Subsystem values for Init.
const (
	SubsystemVideo	= 1<<iota;
	SubsystemAudio;
	SubsystemEmbed;
)
//	SubsystemRawEvents;

// Audio formats.
const (
	AudioFormatStereo44K	= iota;
	AudioFormatStereo48K;
)

// A Window represents a connection to the Native Client window.
// It implements draw.Context.
type Window struct {
	Embedded	bool;	// running as part of a web page?
	*Image;			// screen image

	mousec	chan draw.Mouse;
	kbdc	chan int;
	quitc	chan bool;
	resizec	chan bool;
}

// *Window implements draw.Context
var _ draw.Context = (*Window)(nil)

func (w *Window) KeyboardChan() <-chan int {
	return w.kbdc;
}

func (w *Window) MouseChan() <-chan draw.Mouse {
	return w.mousec;
}

func (w *Window) QuitChan() <-chan bool {
	return w.quitc;
}

func (w *Window) ResizeChan() <-chan bool {
	return w.resizec;
}

func (w *Window) Screen() draw.Image {
	return w.Image;
}

// Init initializes the Native Client subsystems specified by subsys.
// Init must be called before using any of the other functions
// in this package, and it must be called only once.
//
// If the SubsystemVideo flag is set, Init requests a window of size dx×dy.
// When embedded in a web page, the web page's window specification
// overrides the parameters to Init, so the returned Window may have
// a different size than requested.
//
// If the SubsystemAudio flag is set, Init requests a connection to the
// audio device carrying 44 kHz 16-bit stereo PCM audio samples.
func Init(subsys int, dx, dy int) (*Window, os.Error) {
	xsubsys := subsys;
	if srpcEnabled {
		waitBridge();
		xsubsys &^= SubsystemVideo | SubsystemEmbed;
	}

	if xsubsys & SubsystemEmbed != 0 {
		return nil, os.NewError("not embedded");
	}

	w := new(Window);
	err := multimediaInit(xsubsys);
	if err != nil {
		return nil, err;
	}

	if subsys & SubsystemVideo != 0 {
		if dx, dy, err = videoInit(dx, dy); err != nil {
			return nil, err;
		}
		w.Image = newImage(dx, dy, bridge.pixel);
		w.resizec = make(chan bool, 64);
		w.kbdc = make(chan int, 64);
		w.mousec = make(chan draw.Mouse, 64);
		w.quitc = make(chan bool);
	}

	if subsys & SubsystemAudio != 0 {
		var n int;
		if n, err = audioInit(AudioFormatStereo44K, 2048); err != nil {
			return nil, err;
		}
		println("audio", n);
	}

	if subsys & SubsystemVideo != 0 {
		go w.readEvents();
	}

	return w, nil;
}

func (w *Window) FlushImage() {
	if w.Image == nil {
		return;
	}
	videoUpdate(w.Image.Linear);
}

func multimediaInit(subsys int) (err os.Error) {
	return os.NewSyscallError("multimedia_init", syscall.MultimediaInit(subsys));
}

func videoInit(dx, dy int) (ndx, ndy int, err os.Error) {
	if srpcEnabled {
		bridge.share.ready = 1;
		return int(bridge.share.width), int(bridge.share.height), nil;
	}
	if e := syscall.VideoInit(dx, dy); e != 0 {
		return 0, 0, os.NewSyscallError("video_init", int(e));
	}
	return dx, dy, nil;
}

func videoUpdate(data []Color) (err os.Error) {
	if srpcEnabled {
		bridge.flushRPC.Call("upcall", nil);
		return;
	}
	return os.NewSyscallError("video_update", syscall.VideoUpdate((*uint32)(&data[0])));
}

var noEvents = os.NewError("no events")

func videoPollEvent(ev []byte) (err os.Error) {
	if srpcEnabled {
		r := bridge.share.eq.ri;
		if r == bridge.share.eq.wi {
			return noEvents;
		}
		bytes.Copy(ev, &bridge.share.eq.event[r]);
		bridge.share.eq.ri = (r+1)%eqsize;
		return nil;
	}
	return os.NewSyscallError("video_poll_event", syscall.VideoPollEvent(&ev[0]));
}

func audioInit(fmt int, want int) (got int, err os.Error) {
	var x int;
	e := syscall.AudioInit(fmt, want, &x);
	if e == 0 {
		return x, nil;
	}
	return 0, os.NewSyscallError("audio_init", e);
}

var audioSize uintptr

// AudioStream provides access to the audio device.
// Each call to AudioStream writes the given data,
// which should be a slice of 16-bit stereo PCM audio samples,
// and returns the number of samples required by the next
// call to AudioStream.
//
// To find out the initial number of samples to write, call AudioStream(nil).
//
func AudioStream(data []uint16) (nextSize int, err os.Error) {
	if audioSize == 0 {
		e := os.NewSyscallError("audio_stream", syscall.AudioStream(nil, &audioSize));
		return int(audioSize), e;
	}
	if data == nil {
		return int(audioSize), nil;
	}
	if uintptr(len(data))*2 != audioSize {
		log.Stdoutf("invalid audio size want %d got %d", audioSize, len(data));
	}
	e := os.NewSyscallError("audio_stream", syscall.AudioStream(&data[0], &audioSize));
	return int(audioSize), e;
}

// Synchronization structure to wait for bridge to become ready.
var bridge struct {
	c		chan bool;
	displayFd	int;
	rpcFd		int;
	share		*videoShare;
	pixel		[]Color;
	client		*srpc.Client;
	flushRPC	*srpc.RPC;
}

// Wait for bridge to become ready.
// When chan is first created, there is nothing in it,
// so this blocks.  Once the bridge is ready, multimediaBridge.Run
// will drop a value into the channel.  Then any calls
// to waitBridge will finish, taking the value out and immediately putting it back.
func waitBridge() {
	bridge.c <- <-bridge.c;
}

const eqsize = 64

// Data structure shared with host via mmap.
type videoShare struct {
	revision	int32;	// definition below is rev 100 unless noted
	mapSize		int32;

	// event queue
	eq	struct {
		ri	uint32;	// read index [0,eqsize)
		wi	uint32;	// write index [0,eqsize)
		eof	int32;
		event	[eqsize][64]byte;
	};

	// now unused
	_, _, _, _	int32;

	// video backing store information
	width, height, _, size	int32;
	ready			int32;	// rev 0x101
}

// The frame buffer data is videoShareSize bytes after
// the videoShare begins.
const videoShareSize = 16*1024

type multimediaBridge struct{}

// If using SRPC, the runtime will call this method to pass in two file descriptors,
// one to mmap to get the display memory, and another to use for SRPCs back
// to the main process.
func (multimediaBridge) Run(arg, ret []interface{}, size []int) srpc.Errno {
	bridge.displayFd = arg[0].(int);
	bridge.rpcFd = arg[1].(int);

	var st syscall.Stat_t;
	if errno := syscall.Fstat(bridge.displayFd, &st); errno != 0 {
		log.Exitf("mmbridge stat display: %s", os.Errno(errno));
	}

	addr, _, errno := syscall.Syscall6(syscall.SYS_MMAP,
		0,
		uintptr(st.Size),
		syscall.PROT_READ | syscall.PROT_WRITE,
		syscall.MAP_SHARED,
		uintptr(bridge.displayFd),
		0);
	if errno != 0 {
		log.Exitf("mmap display: %s", os.Errno(errno));
	}

	bridge.share = (*videoShare)(unsafe.Pointer(addr));

	// Overestimate frame buffer size
	// (must use a compile-time constant)
	// and then reslice.  256 megapixels (1 GB) should be enough.
	fb := (*[256*1024*1024]Color)(unsafe.Pointer(addr + videoShareSize));
	bridge.pixel = fb[0 : (st.Size - videoShareSize)/4];

	// Configure RPC connection back to client.
	var err os.Error;
	bridge.client, err = srpc.NewClient(bridge.rpcFd);
	if err != nil {
		log.Exitf("NewClient: %s", err);
	}
	bridge.flushRPC = bridge.client.NewRPC(nil);

	// Notify waiters that the bridge is ready.
	println("bridged", bridge.share.revision);
	bridge.c <- true;

	return srpc.OK;
}

func init() {
	bridge.c = make(chan bool, 1);
	if srpcEnabled {
		srpc.Add("nacl_multimedia_bridge", "hh:", multimediaBridge{});
	}
}
