package errutil

import (
	"errors"

	"github.com/tsaikd/KDGoLib/runtimecaller"
)

// errors
var (
	ErrorWalkLoop = NewFactory("detect error component parents loop when walking")
)

// New return a new ErrorObject object
func New(text string, errs ...error) ErrorObject {
	if text != "" {
		errs = append([]error{errors.New(text)}, errs...)
	}
	return NewErrorsSkip(1, errs...)
}

// NewErrors return ErrorObject that contains all input errors
func NewErrors(errs ...error) ErrorObject {
	return NewErrorsSkip(1, errs...)
}

// NewErrorsSkip return ErrorObject, skip function call
func NewErrorsSkip(skip int, errs ...error) ErrorObject {
	var errcomp ErrorObject
	var errtmp ErrorObject
	for i, size := 0, len(errs); i < size; i++ {
		errtmp = castErrorObject(nil, skip+1, errs[i])
		if errtmp == nil {
			continue
		}

		if errcomp == nil {
			errcomp = errtmp
			continue
		}

		if err := AddParent(errcomp, errtmp); err != nil {
			panic(err)
		}
	}
	return errcomp
}

// ErrorObject is a rich error interface
type ErrorObject interface {
	Error() string
	Factory() ErrorFactory
	Parent() ErrorObject
	SetParent(parent ErrorObject) ErrorObject
	runtimecaller.CallInfo
}

type errorObject struct {
	errtext string
	factory ErrorFactory
	parent  ErrorObject
	runtimecaller.CallInfo
}

func castErrorObject(factory ErrorFactory, skip int, err error) ErrorObject {
	if err == nil {
		return nil
	}
	switch err.(type) {
	case errorObject:
		res := err.(errorObject)
		return &res
	case *errorObject:
		return err.(*errorObject)
	case ErrorObject:
		return err.(ErrorObject)
	default:
		callinfo, _ := RuntimeCaller(skip + 1)
		return &errorObject{
			errtext:  err.Error(),
			factory:  factory,
			CallInfo: callinfo,
		}
	}
}

func getErrorText(errin error) string {
	errobj, ok := errin.(*errorObject)
	if ok {
		return errobj.errtext
	}
	return errin.Error()
}

func (t errorObject) Error() string {
	errtext, _ := errorObjectFormatter.Format(&t)
	return errtext
}

func (t *errorObject) Factory() ErrorFactory {
	if t == nil {
		return nil
	}
	return t.factory
}

func (t *errorObject) Parent() ErrorObject {
	if t == nil {
		return nil
	}
	return t.parent
}

func (t *errorObject) SetParent(parent ErrorObject) ErrorObject {
	if t == nil {
		return nil
	}
	if t == parent {
		return t
	}
	t.parent = parent
	return t
}

func (t *errorObject) MarshalJSON() ([]byte, error) {
	return MarshalJSON(t)
}

var _ ErrorObject = (*errorObject)(nil)

var errorObjectFormatter = ErrorFormatter(&ConsoleFormatter{
	Seperator: "; ",
})