package exec

import (
	"sync"
	"time"

	"github.com/docker/docker/daemon/execdriver"
	derr "github.com/docker/docker/errors"
	"github.com/docker/docker/pkg/stringid"
	"github.com/docker/docker/runconfig"
)

// Config holds the configurations for execs. The Daemon keeps
// track of both running and finished execs so that they can be
// examined both during and after completion.
type Config struct {
	sync.Mutex
	*runconfig.StreamConfig
	ID            string
	Running       bool
	ExitCode      *int
	ProcessConfig *execdriver.ProcessConfig
	OpenStdin     bool
	OpenStderr    bool
	OpenStdout    bool
	CanRemove     bool
	ContainerID   string
	DetachKeys    []byte

	// waitStart will be closed immediately after the exec is really started.
	waitStart chan struct{}
}

// NewConfig initializes the a new exec configuration
func NewConfig() *Config {
	return &Config{
		ID:           stringid.GenerateNonCryptoID(),
		StreamConfig: runconfig.NewStreamConfig(),
		waitStart:    make(chan struct{}),
	}
}

// Store keeps track of the exec configurations.
type Store struct {
	commands map[string]*Config
	sync.RWMutex
}

// NewStore initializes a new exec store.
func NewStore() *Store {
	return &Store{commands: make(map[string]*Config, 0)}
}

// Commands returns the exec configurations in the store.
func (e *Store) Commands() map[string]*Config {
	e.RLock()
	commands := make(map[string]*Config, len(e.commands))
	for id, config := range e.commands {
		commands[id] = config
	}
	e.RUnlock()
	return commands
}

// Add adds a new exec configuration to the store.
func (e *Store) Add(id string, Config *Config) {
	e.Lock()
	e.commands[id] = Config
	e.Unlock()
}

// Get returns an exec configuration by its id.
func (e *Store) Get(id string) *Config {
	e.RLock()
	res := e.commands[id]
	e.RUnlock()
	return res
}

// Delete removes an exec configuration from the store.
func (e *Store) Delete(id string) {
	e.Lock()
	delete(e.commands, id)
	e.Unlock()
}

// List returns the list of exec ids in the store.
func (e *Store) List() []string {
	var IDs []string
	e.RLock()
	for id := range e.commands {
		IDs = append(IDs, id)
	}
	e.RUnlock()
	return IDs
}

// Wait waits until the exec process finishes or there is an error in the error channel.
func (c *Config) Wait(cErr chan error) error {
	// Exec should not return until the process is actually running
	select {
	case <-c.waitStart:
	case err := <-cErr:
		return err
	}
	return nil
}

// Close closes the wait channel for the progress.
func (c *Config) Close() {
	close(c.waitStart)
}

// Resize changes the size of the terminal for the exec process.
func (c *Config) Resize(h, w int) error {
	select {
	case <-c.waitStart:
	case <-time.After(time.Second):
		return derr.ErrorCodeExecResize.WithArgs(c.ID)
	}
	return c.ProcessConfig.Terminal.Resize(h, w)
}