package winrm

import (
	
	
	
	
	
	
)

type commandWriter struct {
	*Command
	mutex sync.Mutex
	eof   bool
}

type commandReader struct {
	*Command
	write  *io.PipeWriter
	read   *io.PipeReader
	stream string
}

// Command represents a given command running on a Shell. This structure allows to get access
// to the various stdout, stderr and stdin pipes.
type Command struct {
	client   *Client
	shell    *Shell
	id       string
	exitCode int
	err      error

	Stdin  *commandWriter
	Stdout *commandReader
	Stderr *commandReader

	done   chan struct{}
	cancel chan struct{}
}

func newCommand( context.Context,  *Shell,  string) *Command {
	 := &Command{
		shell:    ,
		client:   .client,
		id:       ,
		exitCode: 0,
		err:      nil,
		done:     make(chan struct{}),
		cancel:   make(chan struct{}),
	}

	.Stdout = newCommandReader("stdout", )
	.Stdin = &commandWriter{
		Command: ,
		eof:     false,
	}
	.Stderr = newCommandReader("stderr", )

	go fetchOutput(, )

	return 
}

func newCommandReader( string,  *Command) *commandReader {
	,  := io.Pipe()
	return &commandReader{
		Command: ,
		stream:  ,
		write:   ,
		read:    ,
	}
}

func fetchOutput( context.Context,  *Command) {
	 := .Done()
	for {
		select {
		case <-.cancel:
			_, _ = .slurpAllOutput()
			 := errors.New("canceled")
			.Stderr.write.CloseWithError()
			.Stdout.write.CloseWithError()
			close(.done)
			return
		case <-:
			.err = .Err()
			 = nil
			.Close()
		default:
			,  := .slurpAllOutput()
			if  {
				.err = 
				close(.done)
				return
			}
		}
	}
}

func ( *Command) () error {
	if .id == "" {
		return errors.New("Command has already been closed")
	}
	if .shell == nil {
		return errors.New("Command has no associated shell")
	}
	if .client == nil {
		return errors.New("Command has no associated client")
	}
	return nil
}

// Close will terminate the running command
func ( *Command) () error {
	if  := .check();  != nil {
		return 
	}

	select { // close cancel channel if it's still open
	case <-.cancel:
	default:
		close(.cancel)
	}

	 := NewSignalRequest(.client.url, .shell.id, .id, &.client.Parameters)
	defer .Free()

	,  := .client.sendRequest()
	return 
}

func ( *Command) () (bool, error) {
	if  := .check();  != nil {
		.Stderr.write.CloseWithError()
		.Stdout.write.CloseWithError()
		return true, 
	}

	 := NewGetOutputRequest(.client.url, .shell.id, .id, "stdout stderr", &.client.Parameters)
	defer .Free()

	,  := .client.sendRequest()
	if  != nil {
		if strings.Contains(.Error(), "OperationTimeout") {
			// Operation timeout because there was no command output
			return false, 
		}
		if strings.Contains(.Error(), "EOF") {
			.exitCode = 16001
		}

		.Stderr.write.CloseWithError()
		.Stdout.write.CloseWithError()
		return true, 
	}

	var  int
	var ,  bytes.Buffer
	, ,  := ParseSlurpOutputErrResponse(, &, &)
	if  != nil {
		.Stderr.write.CloseWithError()
		.Stdout.write.CloseWithError()
		return true, 
	}
	if .Len() > 0 {
		_, _ = .Stdout.write.Write(.Bytes())
	}
	if .Len() > 0 {
		_, _ = .Stderr.write.Write(.Bytes())
	}
	if  {
		.exitCode = 
		_ = .Stderr.write.Close()
		_ = .Stdout.write.Close()
	}

	return , nil
}

func ( *Command) ( []byte,  bool) error {
	if  := .check();  != nil {
		return 
	}

	 := NewSendInputRequest(.client.url, .shell.id, .id, , , &.client.Parameters)
	defer .Free()

	,  := .client.sendRequest()
	return 
}

// ExitCode returns command exit code when it is finished. Before that the result is always 0.
func ( *Command) () int {
	return .exitCode
}

// Wait function will block the current goroutine until the remote command terminates.
func ( *Command) () {
	// block until finished
	<-.done
}

// Write data to this Pipe
// commandWriter implements io.Writer and io.Closer interface
func ( *commandWriter) ( []byte) (int, error) {
	.mutex.Lock()
	defer .mutex.Unlock()

	if .eof {
		return 0, io.ErrClosedPipe
	}

	var (
		 int
		     error
	)
	 := len()
	for len() > 0 {
		// never send more data than our EnvelopeSize.
		 := min(.client.Parameters.EnvelopeSize-1000, len())
		if  := .sendInput([:], false);  != nil {
			break
		}
		 = [:]
		 += 
	}

	// signal that we couldn't write all data
	if  == nil &&  <  {
		 = io.ErrShortWrite
	}

	return , 
}

// Write data to this Pipe and mark EOF
func ( *commandWriter) ( []byte) (int, error) {
	.eof = true
	return .Write()
}

func min( int,  int) int {
	if  <  {
		return 
	}
	return 
}

// Close method wrapper
// commandWriter implements io.Closer interface
func ( *commandWriter) () error {
	.mutex.Lock()
	defer .mutex.Unlock()

	if .eof {
		return io.ErrClosedPipe
	}
	.eof = true
	return .sendInput(nil, .eof)
}

// Read data from this Pipe
func ( *commandReader) ( []byte) (int, error) {
	,  := .read.Read()
	if  != nil && errors.Is(, io.EOF) {
		return 0, 
	}
	return , 
}