package errors

import (
	"errors"
	"fmt"
	"strings"
)

// New returns an error that formats as the given text.
func New(text string) error {
	return errors.New(text)
}

// wrapperError satisfies the error interface.
type wrapperError struct {
	msg    string
	detail []string
	data   map[string]interface{}
	stack  []uintptr
	root   error
}

// It satisfies the error interface.
func (e wrapperError) Error() string {
	return e.msg
}

// Root returns the original error that was wrapped by one or more
// calls to Wrap. If e does not wrap other errors, it will be returned
// as-is.
func Root(e error) error {
	if wErr, ok := e.(wrapperError); ok {
		return wErr.root
	}
	return e
}

// wrap adds a context message and stack trace to err and returns a new error
// containing the new context. This function is meant to be composed within
// other exported functions, such as Wrap and WithDetail.
// The argument stackSkip is the number of stack frames to ascend when
// generating stack straces, where 0 is the caller of wrap.
func wrap(err error, msg string, stackSkip int) error {
	if err == nil {
		return nil
	}

	werr, ok := err.(wrapperError)
	if !ok {
		werr.root = err
		werr.msg = err.Error()
		werr.stack = getStack(stackSkip+2, stackTraceSize)
	}
	if msg != "" {
		werr.msg = msg + ": " + werr.msg
	}

	return werr
}

// Wrap adds a context message and stack trace to err and returns a new error
// with the new context. Arguments are handled as in fmt.Print.
// Use Root to recover the original error wrapped by one or more calls to Wrap.
// Use Stack to recover the stack trace.
// Wrap returns nil if err is nil.
func Wrap(err error, a ...interface{}) error {
	if err == nil {
		return nil
	}
	return wrap(err, fmt.Sprint(a...), 1)
}

// Wrapf is like Wrap, but arguments are handled as in fmt.Printf.
func Wrapf(err error, format string, a ...interface{}) error {
	if err == nil {
		return nil
	}
	return wrap(err, fmt.Sprintf(format, a...), 1)
}

// WithDetail returns a new error that wraps
// err as a chain error messsage containing text
// as its additional context.
// Function Detail will return the given text
// when called on the new error value.
func WithDetail(err error, text string) error {
	if err == nil {
		return nil
	}
	if text == "" {
		return err
	}
	e1 := wrap(err, text, 1).(wrapperError)
	e1.detail = append(e1.detail, text)
	return e1
}

// WithDetailf is like WithDetail, except it formats
// the detail message as in fmt.Printf.
// Function Detail will return the formatted text
// when called on the new error value.
func WithDetailf(err error, format string, v ...interface{}) error {
	if err == nil {
		return nil
	}
	text := fmt.Sprintf(format, v...)
	e1 := wrap(err, text, 1).(wrapperError)
	e1.detail = append(e1.detail, text)
	return e1
}

// Detail returns the detail message contained in err, if any.
// An error has a detail message if it was made by WithDetail
// or WithDetailf.
func Detail(err error) string {
	wrapper, _ := err.(wrapperError)
	return strings.Join(wrapper.detail, "; ")
}

// withData returns a new error that wraps err
// as a chain error message containing v as
// an extra data item.
// Calling Data on the returned error yields v.
// Note that if err already has a data item,
// it will not be accessible via the returned error value.
func withData(err error, v map[string]interface{}) error {
	if err == nil {
		return nil
	}
	e1 := wrap(err, "", 1).(wrapperError)
	e1.data = v
	return e1
}

// WithData returns a new error that wraps err
// as a chain error message containing a value of type
// map[string]interface{} as an extra data item.
// The map contains the values in the map in err,
// if any, plus the items in keyval.
// Keyval takes the form
//   k1, v1, k2, v2, ...
// Values kN must be strings.
// Calling Data on the returned error yields the map.
// Note that if err already has a data item of any other type,
// it will not be accessible via the returned error value.
func WithData(err error, keyval ...interface{}) error {
	if err == nil {
		return nil
	}
	// TODO(kr): add vet check for odd-length keyval and non-string keys
	newkv := make(map[string]interface{})
	for k, v := range Data(err) {
		newkv[k] = v
	}
	for i := 0; i < len(keyval); i += 2 {
		newkv[keyval[i].(string)] = keyval[i+1]
	}
	return withData(err, newkv)
}

// Data returns the data item in err, if any.
func Data(err error) map[string]interface{} {
	wrapper, _ := err.(wrapperError)
	return wrapper.data
}

// Sub returns an error containing root as its root and
// taking all other metadata (stack trace, detail, message,
// and data items) from err.
//
// Sub returns nil when either root or err is nil.
//
// Use this when you need to substitute a new root error in place
// of an existing error that may already hold a stack trace
// or other metadata.
func Sub(root, err error) error {
	if wrapper, ok := err.(wrapperError); ok && root != nil {
		wrapper.root = Root(root)
		wrapper.msg = root.Error()
		root = wrapper
	}
	if err == nil {
		return nil
	}
	return Wrap(root, err.Error())
}
