package ctxcloser

import (
	"sync"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
)

// CloseFunc is a function used to close a ContextCloser
type CloseFunc func() error

// ContextCloser is an interface for services able to be opened and closed.
// It has a parent Context, and Children. But ContextCloser is not a proper
// "tree" like the Context tree. It is more like a Context-WaitGroup hybrid.
// It models a main object with a few children objects -- and, unlike the
// context -- concerns itself with the parent-child closing semantics:
//
// - Can define a CloseFunc (func() error) to be run at Close time.
// - Children call Children().Add(1) to be waited upon
// - Children can select on <-Closing() to know when they should shut down.
// - Close() will wait until all children call Children().Done()
// - <-Closed() signals when the service is completely closed.
//
// ContextCloser can be embedded into the main object itself. In that case,
// the closeFunc (if a member function) has to be set after the struct
// is intialized:
//
//  type service struct {
//  	ContextCloser
//  	net.Conn
//  }
//
//  func (s *service) close() error {
//  	return s.Conn.Close()
//  }
//
//  func newService(ctx context.Context, c net.Conn) *service {
//  	s := &service{c}
//  	s.ContextCloser = NewContextCloser(ctx, s.close)
//  	return s
//  }
//
type ContextCloser interface {

	// Context is the context of this ContextCloser. It is "sort of" a parent.
	Context() context.Context

	// Children is a sync.Waitgroup for all children goroutines that should
	// shut down completely before this service is said to be "closed".
	// Follows the semantics of WaitGroup:
	//  Children().Add(1) // add one more dependent child
	//  Children().Done() // child signals it is done
	Children() *sync.WaitGroup

	// Close is a method to call when you wish to stop this ContextCloser
	Close() error

	// Closing is a signal to wait upon, like Context.Done().
	// It fires when the object should be closing (but hasn't yet fully closed).
	// The primary use case is for child goroutines who need to know when
	// they should shut down. (equivalent to Context().Done())
	Closing() <-chan struct{}

	// Closed is a method to wait upon, like Context.Done().
	// It fires when the entire object is fully closed.
	// The primary use case is for external listeners who need to know when
	// this object is completly done, and all its children closed.
	Closed() <-chan struct{}
}

// contextCloser is an OpenCloser with a cancellable context
type contextCloser struct {
	ctx    context.Context
	cancel context.CancelFunc

	// called to run the close logic.
	closeFunc CloseFunc

	// closed is released once the close function is done.
	closed chan struct{}

	// wait group for child goroutines
	children sync.WaitGroup

	// sync primitive to ensure the close logic is only called once.
	closeOnce sync.Once

	// error to return to clients of Close().
	closeErr error
}

// NewContextCloser constructs and returns a ContextCloser. It will call
// cf CloseFunc before its Done() Wait signals fire.
func NewContextCloser(ctx context.Context, cf CloseFunc) ContextCloser {
	ctx, cancel := context.WithCancel(ctx)
	c := &contextCloser{
		ctx:       ctx,
		cancel:    cancel,
		closeFunc: cf,
		closed:    make(chan struct{}),
	}

	go c.closeOnContextDone()
	return c
}

func (c *contextCloser) Context() context.Context {
	return c.ctx
}

func (c *contextCloser) Children() *sync.WaitGroup {
	return &c.children
}

// Close is the external close function. it's a wrapper around internalClose
// that waits on Closed()
func (c *contextCloser) Close() error {
	c.internalClose()
	<-c.Closed() // wait until we're totally done.
	return c.closeErr
}

func (c *contextCloser) Closing() <-chan struct{} {
	return c.Context().Done()
}

func (c *contextCloser) Closed() <-chan struct{} {
	return c.closed
}

func (c *contextCloser) internalClose() {
	go c.closeOnce.Do(c.closeLogic)
}

// the _actual_ close process.
func (c *contextCloser) closeLogic() {
	// this function should only be called once (hence the sync.Once).
	// and it will panic at the bottom (on close(c.closed)) otherwise.

	c.cancel()                 // signal that we're shutting down (Closing)
	c.closeErr = c.closeFunc() // actually run the close logic
	c.children.Wait()          // wait till all children are done.
	close(c.closed)            // signal that we're shut down (Closed)
}

// if parent context is shut down before we call Close explicitly,
// we need to go through the Close motions anyway. Hence all the sync
// stuff all over the place...
func (c *contextCloser) closeOnContextDone() {
	c.Children().Add(1)  // we're a child goroutine, to be waited upon.
	<-c.Context().Done() // wait until parent (context) is done.
	c.internalClose()
	c.Children().Done()
}
