package assert

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"runtime"
	"strings"
)

// Result contains a single assertion failure as an error.
// You should not create a Result directly, use So instead.
// Once created, a Result is read-only and only allows
// queries using the provided methods.
type Result struct {
	invocation string
	err        error

	stdout io.Writer
	logger *logger
}

// So is a convenience function (as opposed to an inconvenience function?)
// for running assertions on arbitrary arguments in any context. It allows you to perform
// assertion-like behavior and decide what happens in the event of a failure.
// It is a variant of assertions.So in every respect except its return value.
// In this case, the return value is a *Result which possesses several of its
// own convenience methods:
//
//    fmt.Println(assert.So(1, should.Equal, 1)) // Calls String() and prints the representation of the assertion.
//    assert.So(1, should.Equal, 1).Println()    // Calls fmt.Print with the failure message and file:line header.
//    assert.So(1, should.Equal, 1).Log()        // Calls log.Print with the failure message and file:line header.
//    assert.So(1, should.Equal, 1).Panic()      // Calls log.Panic with the failure message and file:line header.
//    assert.So(1, should.Equal, 1).Fatal()      // Calls log.Fatal with the failure message and file:line header.
//    if err := assert.So(1, should.Equal, 1).Error(); err != nil {
//        // Allows custom handling of the error, which will include the failure message and file:line header.
//    }
func So(actual interface{}, assert assertion, expected ...interface{}) *Result {
	result := new(Result)
	result.stdout = os.Stdout
	result.invocation = fmt.Sprintf("So(actual: %v, %v, expected: %v)", actual, assertionName(assert), expected)
	if failure := assert(actual, expected...); len(failure) > 0 {
		_, file, line, _ := runtime.Caller(1)
		result.err = fmt.Errorf("Assertion failure at %s:%d\n%s", file, line, failure)
	}
	return result
}
func assertionName(i interface{}) string {
	functionAddress := runtime.FuncForPC(reflect.ValueOf(i).Pointer())
	fullNameStartingWithPackage := functionAddress.Name()
	parts := strings.Split(fullNameStartingWithPackage, "/")
	baseName := parts[len(parts)-1]
	return strings.Replace(baseName, "assertions.Should", "should.", 1)
}

// Failed returns true if the assertion failed, false if it passed.
func (this *Result) Failed() bool {
	return !this.Passed()
}

// Passed returns true if the assertion passed, false if it failed.
func (this *Result) Passed() bool {
	return this.err == nil
}

// Error returns the error representing an assertion failure, which is nil in the case of a passed assertion.
func (this *Result) Error() error {
	return this.err
}

// String implements fmt.Stringer.
// It returns the error as a string in the case of an assertion failure.
// Unlike other methods defined herein, if returns a non-empty
// representation of the assertion as confirmation of success.
func (this *Result) String() string {
	if this.Passed() {
		return fmt.Sprintf("✔ %s", this.invocation)
	} else {
		return fmt.Sprintf("✘ %s\n%v", this.invocation, this.Error())
	}
}

// Println calls fmt.Println in the case of an assertion failure.
func (this *Result) Println() *Result {
	if this.Failed() {
		fmt.Fprintln(this.stdout, this)
	}
	return this
}

// Log calls log.Print in the case of an assertion failure.
func (this *Result) Log() *Result {
	if this.Failed() {
		this.logger.Print(this)
	}
	return this
}

// Panic calls log.Panic in the case of an assertion failure.
func (this *Result) Panic() *Result {
	if this.Failed() {
		this.logger.Panic(this)
	}
	return this
}

// Fatal calls log.Fatal in the case of an assertion failure.
func (this *Result) Fatal() *Result {
	if this.Failed() {
		this.logger.Fatal(this)
	}
	return this
}

// assertion is a copy of github.com/smartystreets/assertions.assertion.
type assertion func(actual interface{}, expected ...interface{}) string
