// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package internal

import (
	"fmt"
)

// WrappedError represents an error that contains another error.
type WrappedError interface {
	// Message gets the basic message of the error.
	Message() string
	// Inner gets the inner error if one exists.
	Inner() error
}

// RolledUpErrorMessage gets a flattened error message.
func RolledUpErrorMessage(err error) string {
	if wrappedErr, ok := err.(WrappedError); ok {
		inner := wrappedErr.Inner()
		if inner != nil {
			return fmt.Sprintf("%s: %s", wrappedErr.Message(), RolledUpErrorMessage(inner))
		}

		return wrappedErr.Message()
	}

	return err.Error()
}

// UnwrapError attempts to unwrap the error down to its root cause.
func UnwrapError(err error) error {

	switch tErr := err.(type) {
	case WrappedError:
		return UnwrapError(tErr.Inner())
	case *multiError:
		return UnwrapError(tErr.errors[0])
	}

	return err
}

// WrapError wraps an error with a message.
func WrapError(inner error, message string) error {
	return &wrappedError{message, inner}
}

// WrapErrorf wraps an error with a message.
func WrapErrorf(inner error, format string, args ...interface{}) error {
	return &wrappedError{fmt.Sprintf(format, args...), inner}
}

// MultiError combines multiple errors into a single error. If there are no errors,
// nil is returned. If there is 1 error, it is returned. Otherwise, they are combined.
func MultiError(errors ...error) error {

	// remove nils from the error list
	var nonNils []error
	for _, e := range errors {
		if e != nil {
			nonNils = append(nonNils, e)
		}
	}

	switch len(nonNils) {
	case 0:
		return nil
	case 1:
		return nonNils[0]
	default:
		return &multiError{
			message: "multiple errors encountered",
			errors:  nonNils,
		}
	}
}

type multiError struct {
	message string
	errors  []error
}

func (e *multiError) Message() string {
	return e.message
}

func (e *multiError) Error() string {
	result := e.message
	for _, e := range e.errors {
		result += fmt.Sprintf("\n  %s", e)
	}
	return result
}

func (e *multiError) Errors() []error {
	return e.errors
}

type wrappedError struct {
	message string
	inner   error
}

func (e *wrappedError) Message() string {
	return e.message
}

func (e *wrappedError) Error() string {
	return RolledUpErrorMessage(e)
}

func (e *wrappedError) Inner() error {
	return e.inner
}
