package main

import (
	"fmt"
	"os"
	"os/exec"
	"syscall"
)

type CommandStatement struct {
	BinaryArgs []Token
}

func (this *CommandStatement) Execute(state *ExecutionState) ExitState {

	if len(this.BinaryArgs) == 0 {
		return ExitState{ExitCode: 0} // no input isn't technically a problem
	}

	builtinFn, builtinFound := builtins[string(this.BinaryArgs[0])]
	if builtinFound {
		return builtinFn(this, state)
	}

	// exec.Command does NOT call the system shell.

	// TODO expand variable references

	// Handle command and arguments

	bin := string(this.BinaryArgs[0])
	args := make([]string, 0, len(this.BinaryArgs)-1)
	for _, arg := range this.BinaryArgs[1:] {
		args = append(args, string(arg))
	}

	cmd := exec.Command(bin, args...)

	// Handle descriptors

	cmd.Stdin = nil
	cmd.Stdout = nil
	cmd.Stderr = nil

	for i, d := range state.Descriptors {
		if i == DESCRIPTOR_STDIN {
			cmd.Stdin = d
		} else if i == DESCRIPTOR_STDOUT {
			cmd.Stdout = d
		} else if i == DESCRIPTOR_STDERR {
			cmd.Stderr = d
		} else if i >= 3 {

			// Ensure there's buffer space
			if len(cmd.ExtraFiles) < 3+i {
				tmp := make([]*os.File, i)
				copy(tmp, cmd.ExtraFiles)
				cmd.ExtraFiles = tmp
			}

			// High descriptor
			cmd.ExtraFiles[i-3] = d
		}
	}

	// Handle environment

	env := make([]string, 0, len(state.Environment))
	for k, v := range state.Environment {
		env = append(env, k+"="+v)
	}

	// Launch and block
	ret := ExitState{ExitCode: 0}
	err := cmd.Run()
	if err != nil {
		ret.ExitCode = 1 // preliminary, until we have detail

		switch err.(type) {
		case *exec.Error:
			ee := err.(*exec.Error)
			if ee != nil && ee.Err == exec.ErrNotFound {
				fmt.Fprintf(state.Descriptors[DESCRIPTOR_STDERR], "error: '%s' not found\n", bin)
			}

		case *exec.ExitError:
			exitErr := err.(*exec.ExitError)
			if ws, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				ret.ExitCode = int(ws.ExitCode)
			}

		default:
			// some other type of error?
		}

	}

	//fmt.Fprintf(state.Descriptors[DESCRIPTOR_STDERR], "exit code %d\n", ret.ExitCode)

	// Done

	return ret
}
