package tarantool

import (
	"bytes"
	"fmt"
)

const errorExtID = 3

const (
	keyErrorStack   = 0x00
	keyErrorType    = 0x00
	keyErrorFile    = 0x01
	keyErrorLine    = 0x02
	keyErrorMessage = 0x03
	keyErrorErrno   = 0x04
	keyErrorErrcode = 0x05
	keyErrorFields  = 0x06
)

// BoxError is a type representing Tarantool `box.error` object: a single
// MP_ERROR_STACK object with a link to the previous stack error.
// See https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_error/error/
//
// Since 1.10.0
type BoxError struct {
	// Type is error type that implies its source (for example, "ClientError").
	Type string
	// File is a source code file where the error was caught.
	File string
	// Line is a number of line in the source code file where the error was caught.
	Line uint64
	// Msg is the text of reason.
	Msg string
	// Errno is the ordinal number of the error.
	Errno uint64
	// Code is the number of the error as defined in `errcode.h`.
	Code uint64
	// Fields are additional fields depending on error type. For example, if
	// type is "AccessDeniedError", then it will include "object_type",
	// "object_name", "access_type".
	Fields map[string]interface{}
	// Prev is the previous error in stack.
	Prev *BoxError
}

// Error converts a BoxError to a string.
func (e *BoxError) Error() string {
	s := fmt.Sprintf("%s (%s, code 0x%x), see %s line %d",
		e.Msg, e.Type, e.Code, e.File, e.Line)

	if e.Prev != nil {
		return fmt.Sprintf("%s: %s", s, e.Prev)
	}

	return s
}

// Depth computes the count of errors in stack, including the current one.
func (e *BoxError) Depth() int {
	depth := int(0)

	cur := e
	for cur != nil {
		cur = cur.Prev
		depth++
	}

	return depth
}

func decodeBoxError(d *decoder) (*BoxError, error) {
	var l, larr, l1, l2 int
	var errorStack []BoxError
	var err error

	if l, err = d.DecodeMapLen(); err != nil {
		return nil, err
	}

	for ; l > 0; l-- {
		var cd int
		if cd, err = d.DecodeInt(); err != nil {
			return nil, err
		}
		switch cd {
		case keyErrorStack:
			if larr, err = d.DecodeArrayLen(); err != nil {
				return nil, err
			}

			errorStack = make([]BoxError, larr)

			for i := 0; i < larr; i++ {
				if l1, err = d.DecodeMapLen(); err != nil {
					return nil, err
				}

				for ; l1 > 0; l1-- {
					var cd1 int
					if cd1, err = d.DecodeInt(); err != nil {
						return nil, err
					}
					switch cd1 {
					case keyErrorType:
						if errorStack[i].Type, err = d.DecodeString(); err != nil {
							return nil, err
						}
					case keyErrorFile:
						if errorStack[i].File, err = d.DecodeString(); err != nil {
							return nil, err
						}
					case keyErrorLine:
						if errorStack[i].Line, err = d.DecodeUint64(); err != nil {
							return nil, err
						}
					case keyErrorMessage:
						if errorStack[i].Msg, err = d.DecodeString(); err != nil {
							return nil, err
						}
					case keyErrorErrno:
						if errorStack[i].Errno, err = d.DecodeUint64(); err != nil {
							return nil, err
						}
					case keyErrorErrcode:
						if errorStack[i].Code, err = d.DecodeUint64(); err != nil {
							return nil, err
						}
					case keyErrorFields:
						var mapk string
						var mapv interface{}

						errorStack[i].Fields = make(map[string]interface{})

						if l2, err = d.DecodeMapLen(); err != nil {
							return nil, err
						}
						for ; l2 > 0; l2-- {
							if mapk, err = d.DecodeString(); err != nil {
								return nil, err
							}
							if mapv, err = d.DecodeInterface(); err != nil {
								return nil, err
							}
							errorStack[i].Fields[mapk] = mapv
						}
					default:
						if err = d.Skip(); err != nil {
							return nil, err
						}
					}
				}

				if i > 0 {
					errorStack[i-1].Prev = &errorStack[i]
				}
			}
		default:
			if err = d.Skip(); err != nil {
				return nil, err
			}
		}
	}

	if len(errorStack) == 0 {
		return nil, fmt.Errorf("msgpack: unexpected empty BoxError stack on decode")
	}

	return &errorStack[0], nil
}

func encodeBoxError(enc *encoder, boxError *BoxError) error {
	if boxError == nil {
		return fmt.Errorf("msgpack: unexpected nil BoxError on encode")
	}

	if err := enc.EncodeMapLen(1); err != nil {
		return err
	}
	if err := encodeUint(enc, keyErrorStack); err != nil {
		return err
	}

	var stackDepth = boxError.Depth()
	if err := enc.EncodeArrayLen(stackDepth); err != nil {
		return err
	}

	for ; stackDepth > 0; stackDepth-- {
		fieldsLen := len(boxError.Fields)

		if fieldsLen > 0 {
			if err := enc.EncodeMapLen(7); err != nil {
				return err
			}
		} else {
			if err := enc.EncodeMapLen(6); err != nil {
				return err
			}
		}

		if err := encodeUint(enc, keyErrorType); err != nil {
			return err
		}
		if err := enc.EncodeString(boxError.Type); err != nil {
			return err
		}

		if err := encodeUint(enc, keyErrorFile); err != nil {
			return err
		}
		if err := enc.EncodeString(boxError.File); err != nil {
			return err
		}

		if err := encodeUint(enc, keyErrorLine); err != nil {
			return err
		}
		if err := enc.EncodeUint64(boxError.Line); err != nil {
			return err
		}

		if err := encodeUint(enc, keyErrorMessage); err != nil {
			return err
		}
		if err := enc.EncodeString(boxError.Msg); err != nil {
			return err
		}

		if err := encodeUint(enc, keyErrorErrno); err != nil {
			return err
		}
		if err := enc.EncodeUint64(boxError.Errno); err != nil {
			return err
		}

		if err := encodeUint(enc, keyErrorErrcode); err != nil {
			return err
		}
		if err := enc.EncodeUint64(boxError.Code); err != nil {
			return err
		}

		if fieldsLen > 0 {
			if err := encodeUint(enc, keyErrorFields); err != nil {
				return err
			}

			if err := enc.EncodeMapLen(fieldsLen); err != nil {
				return err
			}

			for k, v := range boxError.Fields {
				if err := enc.EncodeString(k); err != nil {
					return err
				}
				if err := enc.Encode(v); err != nil {
					return err
				}
			}
		}

		if stackDepth > 1 {
			boxError = boxError.Prev
		}
	}

	return nil
}

// UnmarshalMsgpack deserializes a BoxError value from a MessagePack
// representation.
func (e *BoxError) UnmarshalMsgpack(b []byte) error {
	if e == nil {
		panic("cannot unmarshal to a nil pointer")
	}

	buf := bytes.NewBuffer(b)
	dec := newDecoder(buf)

	if val, err := decodeBoxError(dec); err != nil {
		return err
	} else {
		*e = *val
		return nil
	}
}

// MarshalMsgpack serializes the BoxError into a MessagePack representation.
func (e *BoxError) MarshalMsgpack() ([]byte, error) {
	var buf bytes.Buffer

	enc := newEncoder(&buf)
	if err := encodeBoxError(enc, e); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}