package jsonrpc2

import (
	"encoding/json"
	"fmt"
	"strings"
)

var (
	// Actual returned error may have different message.
	errParse       = NewError(-32700, "parse error")
	errRequest     = NewError(-32600, "invalid request")
	errMethod      = NewError(-32601, "method not found")
	errParams      = NewError(-32602, "invalid params")
	errInternal    = NewError(-32603, "internal error")
	errServer      = NewError(-32000, "server error")
	errServerError = NewError(-32001, "jsonrpc2.Error: json.Marshal failed")
)

// Error represent JSON-RPC 2.0 "Error object".
type Error struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// NewError returns an Error with given code and message.
func NewError(code int, message string) *Error {
	return &Error{Code: code, Message: message}
}

// newError returns an Error with auto-detected code for given message.
func newError(message string) *Error {
	switch {
	case strings.HasPrefix(message, "rpc: service/method request ill-formed"):
		return NewError(errMethod.Code, message)
	case strings.HasPrefix(message, "rpc: can't find service"):
		return NewError(errMethod.Code, message)
	case strings.HasPrefix(message, "rpc: can't find method"):
		return NewError(errMethod.Code, message)
	default:
		return NewError(errServer.Code, message)
	}
}

// ServerError convert errors returned by Client.Call() into Error.
// User should check for rpc.ErrShutdown and io.ErrUnexpectedEOF before
// calling ServerError.
func ServerError(rpcerr error) *Error {
	if rpcerr == nil {
		return nil
	}
	if err, ok := rpcerr.(*Error); ok {
		if err.Code == errInternal.Code && err.Data != nil {
			if err2, ok := err.Data.(*Error); ok {
				// Use alternate error when ReadResponseBody fail on other call.
				return err2
			}
		}
		return err
	}
	keepData := true
	errmsg := rpcerr.Error()
	if s := strings.Index(errmsg, "{"); strings.HasPrefix(errmsg, "reading ") && s != -1 && strings.HasSuffix(errmsg, "}") {
		// ReadResponseBody fail on this call.
		errmsg = errmsg[s:]
		keepData = false
	}
	e := &Error{}
	err := json.Unmarshal([]byte(errmsg), e)
	if err != nil {
		panic(fmt.Sprintf("not a jsonrpc2 error: %s (%#q)", err, rpcerr))
	}
	if e.Code == errInternal.Code && e.Data != nil && !keepData {
		// ReadResponseBody fail on this call.
		e.Data = nil
	}
	return e
}

// Error returns JSON representation of Error.
func (e *Error) Error() string {
	buf, err := json.Marshal(e)
	if err != nil {
		msg, err := json.Marshal(err.Error())
		if err != nil {
			msg = []byte(`"` + errServerError.Message + `"`)
		}
		return fmt.Sprintf(`{"code":%d,"message":%s}`, errServerError.Code, string(msg))
	}
	return string(buf)
}
