package commands

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

// ErrorType signfies a category of errors
type ErrorType uint

// ErrorTypes convey what category of error ocurred
const (
	ErrNormal ErrorType = iota // general errors
	ErrClient                  // error was caused by the client, (e.g. invalid CLI usage)
	// TODO: add more types of errors for better error-specific handling
)

// Error is a struct for marshalling errors
type Error struct {
	Message string
	Code    ErrorType
}

func (e Error) Error() string {
	return fmt.Sprintf("%d error: %s", e.Code, e.Message)
}

// EncodingType defines a supported encoding
type EncodingType string

// Supported EncodingType constants.
const (
	JSON = "json"
	XML  = "xml"
	// TODO: support more encoding types
)

// Marshaller is a function used by coding types.
// TODO this should just be a `coding.Codec`
type Marshaller func(v interface{}) ([]byte, error)

var marshallers = map[EncodingType]Marshaller{
	JSON: json.Marshal,
	XML:  xml.Marshal,
}

// Response is the result of a command request. Handlers write to the response,
// setting Error or Value. Response is returned to the client.
type Response interface {
	Request() Request

	// Set/Return the response Error
	SetError(err error, code ErrorType)
	Error() error

	// Sets/Returns the response value
	SetValue(interface{})
	Value() interface{}

	// Marshal marshals out the response into a buffer. It uses the EncodingType
	// on the Request to chose a Marshaller (Codec).
	Marshal() ([]byte, error)
}

type response struct {
	req   Request
	err   *Error
	value interface{}
	out   io.Writer
}

func (r *response) Request() Request {
	return r.req
}

func (r *response) Value() interface{} {
	return r.value
}

func (r *response) SetValue(v interface{}) {
	r.value = v
}

func (r *response) Stream() io.Writer {
	return r.out
}

func (r *response) Error() error {
	if r.err == nil {
		return nil
	}
	return r.err
}

func (r *response) SetError(err error, code ErrorType) {
	r.err = &Error{Message: err.Error(), Code: code}
}

func (r *response) Marshal() ([]byte, error) {
	if r.err == nil && r.value == nil {
		return nil, fmt.Errorf("No error or value set, there is nothing to marshal")
	}

	enc, ok := r.req.Option(EncShort)
	if !ok || enc.(string) == "" {
		return nil, fmt.Errorf("No encoding type was specified")
	}
	encType := EncodingType(strings.ToLower(enc.(string)))

	marshaller, ok := marshallers[encType]
	if !ok {
		return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
	}

	if r.err != nil {
		return marshaller(r.err)
	}
	return marshaller(r.value)
}

// NewResponse returns a response to match given Request
func NewResponse(req Request) Response {
	return &response{req: req}
}
