package _tests_test

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/onsi/gomega/types"
	"os/exec"
	"strings"
)

// cmdOut is the output of a command. it's used by cmdMatcher to match on
type cmdOut struct {
	args []string
	str  string
	err  error
}

// execute executes the command generated by fmt.Sprintf(cmdLine, args...) and returns its output as a cmdOut structure.
// this structure can then be matched upon using the SucceedWithOutput matcher below
func execute(cmdLine string, args ...interface{}) *cmdOut {
	var stdout, stderr bytes.Buffer
	var cmd *exec.Cmd
	cmd = exec.Command("/bin/sh", "-c", fmt.Sprintf(cmdLine, args...))
	cmd.Stdout, cmd.Stderr = &stdout, &stderr
	ret := &cmdOut{}
	ret.args = cmd.Args
	ret.str = stdout.String()
	if err := cmd.Run(); err != nil {
		ret.err = err
		return ret
	}
	return ret
}

var (
	errExpectedCmdOut = errors.New("cmdMatcher expects a *cmdOut")
	errFailedCmd      = errors.New("command failed")
)

// Command
type successfulCmdMatcher struct {
	// will be filled in when Match is called
	cmdo     *cmdOut
	matchers []types.GomegaMatcher
}

// SucceedWithOutput returns a matcher that will match on a *cmdOut, ensuring that the command returned no error
// and its output matches all the given matchers
func SucceedWithOutput(matchers ...types.GomegaMatcher) types.GomegaMatcher {
	return &successfulCmdMatcher{matchers: matchers}
}

// Match is the interface implementation of github.com/onsi/gomega/types.GomegaMatcher
func (c *successfulCmdMatcher) Match(actual interface{}) (bool, error) {
	cmdo, ok := actual.(*cmdOut)
	if !ok {
		return false, errExpectedCmdOut
	}
	c.cmdo = cmdo
	if cmdo.err != nil {
		return false, errFailedCmd
	}
	return SatisfyAll(c.matchers...)
}

// FailureMessage is the interface implementation of github.com/onsi/gomega/types.GomegaMatcher
func (c *successfulCmdMatcher) FailureMessage(actual interface{}) string {
	if c.cmdo == nil {
		return "command failed, but wasn't recorded"
	}
	return fmt.Sprintf("command %s errored.\noutput:\n%s\nerror:%s\n",
		strings.Join(c.cmdo.args, " "),
		c.cmdo.str,
		c.cmdo.err,
	)
}

// NegatedFailureMessage is the interface implementation of github.com/onsi/gomega/types.GomegaMatcher
func (c *successfulCmdMatcher) NegatedFailureMessage(actual interface{}) string {
	if c.cmdo == nil {
		return "command failed, but wasn't recorded"
	}

	return fmt.Sprintf("command %s errored.\noutput:\n%s\nerror:%s\n",
		strings.Join(c.cmdo.args, " "),
		c.cmdo.str,
		c.cmdo.err,
	)
}
