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

package netchan

import (
	"encoding/gob"
	"errors"
	"io"
	"reflect"
	"sync"
	"time"
)

// The direction of a connection from the client's perspective.
type Dir int

const (
	Recv Dir = iota
	Send
)

func (dir Dir) String() string {
	switch dir {
	case Recv:
		return "Recv"
	case Send:
		return "Send"
	}
	return "???"
}

// Payload types
const (
	payRequest = iota // request structure follows
	payError          // error structure follows
	payData           // user payload follows
	payAck            // acknowledgement; no payload
	payClosed         // channel is now closed
	payAckSend        // payload has been delivered.
)

// A header is sent as a prefix to every transmission.  It will be followed by
// a request structure, an error structure, or an arbitrary user payload structure.
type header struct {
	Id          int
	PayloadType int
	SeqNum      int64
}

// Sent with a header once per channel from importer to exporter to report
// that it wants to bind to a channel with the specified direction for count
// messages, with space for size buffered values. If count is -1, it means unlimited.
type request struct {
	Name  string
	Count int64
	Size  int
	Dir   Dir
}

// Sent with a header to report an error.
type error_ struct {
	Error string
}

// Used to unify management of acknowledgements for import and export.
type unackedCounter interface {
	unackedCount() int64
	ack() int64
	seq() int64
}

// A channel and its direction.
type chanDir struct {
	ch  reflect.Value
	dir Dir
}

// clientSet contains the objects and methods needed for tracking
// clients of an exporter and draining outstanding messages.
type clientSet struct {
	mu      sync.Mutex // protects access to channel and client maps
	names   map[string]*chanDir
	clients map[unackedCounter]bool
}

// Mutex-protected encoder and decoder pair.
type encDec struct {
	decLock sync.Mutex
	dec     *gob.Decoder
	encLock sync.Mutex
	enc     *gob.Encoder
}

func newEncDec(conn io.ReadWriter) *encDec {
	return &encDec{
		dec: gob.NewDecoder(conn),
		enc: gob.NewEncoder(conn),
	}
}

// Decode an item from the connection.
func (ed *encDec) decode(value reflect.Value) error {
	ed.decLock.Lock()
	err := ed.dec.DecodeValue(value)
	if err != nil {
		// TODO: tear down connection?
	}
	ed.decLock.Unlock()
	return err
}

// Encode a header and payload onto the connection.
func (ed *encDec) encode(hdr *header, payloadType int, payload interface{}) error {
	ed.encLock.Lock()
	hdr.PayloadType = payloadType
	err := ed.enc.Encode(hdr)
	if err == nil {
		if payload != nil {
			err = ed.enc.Encode(payload)
		}
	}
	if err != nil {
		// TODO: tear down connection if there is an error?
	}
	ed.encLock.Unlock()
	return err
}

// See the comment for Exporter.Drain.
func (cs *clientSet) drain(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for {
		pending := false
		cs.mu.Lock()
		// Any messages waiting for a client?
		for _, chDir := range cs.names {
			if chDir.ch.Len() > 0 {
				pending = true
			}
		}
		// Any unacknowledged messages?
		for client := range cs.clients {
			n := client.unackedCount()
			if n > 0 { // Check for > rather than != just to be safe.
				pending = true
				break
			}
		}
		cs.mu.Unlock()
		if !pending {
			break
		}
		if timeout > 0 && time.Now().After(deadline) {
			return errors.New("timeout")
		}
		time.Sleep(100 * time.Millisecond)
	}
	return nil
}

// See the comment for Exporter.Sync.
func (cs *clientSet) sync(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	// seq remembers the clients and their seqNum at point of entry.
	seq := make(map[unackedCounter]int64)
	cs.mu.Lock()
	for client := range cs.clients {
		seq[client] = client.seq()
	}
	cs.mu.Unlock()
	for {
		pending := false
		cs.mu.Lock()
		// Any unacknowledged messages?  Look only at clients that existed
		// when we started and are still in this client set.
		for client := range seq {
			if _, ok := cs.clients[client]; ok {
				if client.ack() < seq[client] {
					pending = true
					break
				}
			}
		}
		cs.mu.Unlock()
		if !pending {
			break
		}
		if timeout > 0 && time.Now().After(deadline) {
			return errors.New("timeout")
		}
		time.Sleep(100 * time.Millisecond)
	}
	return nil
}

// A netChan represents a channel imported or exported
// on a single connection. Flow is controlled by the receiving
// side by sending payAckSend messages when values
// are delivered into the local channel.
type netChan struct {
	*chanDir
	name   string
	id     int
	size   int // buffer size of channel.
	closed bool

	// sender-specific state
	ackCh chan bool // buffered with space for all the acks we need
	space int       // available space.

	// receiver-specific state
	sendCh chan reflect.Value // buffered channel of values received from other end.
	ed     *encDec            // so that we can send acks.
	count  int64              // number of values still to receive.
}

// Create a new netChan with the given name (only used for
// messages), id, direction, buffer size, and count.
// The connection to the other side is represented by ed.
func newNetChan(name string, id int, ch *chanDir, ed *encDec, size int, count int64) *netChan {
	c := &netChan{chanDir: ch, name: name, id: id, size: size, ed: ed, count: count}
	if c.dir == Send {
		c.ackCh = make(chan bool, size)
		c.space = size
	}
	return c
}

// Close the channel.
func (nch *netChan) close() {
	if nch.closed {
		return
	}
	if nch.dir == Recv {
		if nch.sendCh != nil {
			// If the sender goroutine is active, close the channel to it.
			// It will close nch.ch when it can.
			close(nch.sendCh)
		} else {
			nch.ch.Close()
		}
	} else {
		nch.ch.Close()
		close(nch.ackCh)
	}
	nch.closed = true
}

// Send message from remote side to local receiver.
func (nch *netChan) send(val reflect.Value) {
	if nch.dir != Recv {
		panic("send on wrong direction of channel")
	}
	if nch.sendCh == nil {
		// If possible, do local send directly and ack immediately.
		if nch.ch.TrySend(val) {
			nch.sendAck()
			return
		}
		// Start sender goroutine to manage delayed delivery of values.
		nch.sendCh = make(chan reflect.Value, nch.size)
		go nch.sender()
	}
	select {
	case nch.sendCh <- val:
		// ok
	default:
		// TODO: should this be more resilient?
		panic("netchan: remote sender sent more values than allowed")
	}
}

// sendAck sends an acknowledgment that a message has left
// the channel's buffer. If the messages remaining to be sent
// will fit in the channel's buffer, then we don't
// need to send an ack.
func (nch *netChan) sendAck() {
	if nch.count < 0 || nch.count > int64(nch.size) {
		nch.ed.encode(&header{Id: nch.id}, payAckSend, nil)
	}
	if nch.count > 0 {
		nch.count--
	}
}

// The sender process forwards items from the sending queue
// to the destination channel, acknowledging each item.
func (nch *netChan) sender() {
	if nch.dir != Recv {
		panic("sender on wrong direction of channel")
	}
	// When Exporter.Hangup is called, the underlying channel is closed,
	// and so we may get a "too many operations on closed channel" error
	// if there are outstanding messages in sendCh.
	// Make sure that this doesn't panic the whole program.
	defer func() {
		if r := recover(); r != nil {
			// TODO check that r is "too many operations", otherwise re-panic.
		}
	}()
	for v := range nch.sendCh {
		nch.ch.Send(v)
		nch.sendAck()
	}
	nch.ch.Close()
}

// Receive value from local side for sending to remote side.
func (nch *netChan) recv() (val reflect.Value, ok bool) {
	if nch.dir != Send {
		panic("recv on wrong direction of channel")
	}

	if nch.space == 0 {
		// Wait for buffer space.
		<-nch.ackCh
		nch.space++
	}
	nch.space--
	return nch.ch.Recv()
}

// acked is called when the remote side indicates that
// a value has been delivered.
func (nch *netChan) acked() {
	if nch.dir != Send {
		panic("recv on wrong direction of channel")
	}
	select {
	case nch.ackCh <- true:
		// ok
	default:
		// TODO: should this be more resilient?
		panic("netchan: remote receiver sent too many acks")
	}
}
