package negotiator

import (
	"errors"
	"mime"
	"reflect"
)

var (
	// ErrNoContentType is the error returned if an accept header cannot be matched
	// in the current registry
	ErrNoContentType = errors.New("No Acceptable Content Type")
)

// ContentTypeParams is a type alias for a map of string to strings,
// representing any parameters passed to the Content-Type header
type ContentTypeParams map[string]string

// Registry is a content type registry used for managing a mapping of media
// ranges to the interfaces that represent those resources
type Registry map[string]interface{}

// NewRegistry returns an empty Registry
func NewRegistry() *Registry {
	return &Registry{}
}

// Register registers the default struct value for a content type in the
// registry. when requested, a copy of the default value will be provided as
// the result of a call to Negotiate
func (r Registry) Register(contentType string, defaultValue interface{}) {
	if reflect.TypeOf(defaultValue).Kind() == reflect.Ptr {
		r[contentType] = reflect.ValueOf(defaultValue).Elem().Interface()
		return
	}
	r[contentType] = defaultValue
}

// Negotiate attempts to negotiate the proper interface for the provided accept
// header. Negotiate returns a copy of the default interface that best matches
// the provided accept header, if a match is found
func (r Registry) Negotiate(header string) (interface{}, *Accept, error) {
	acceptHeader, err := ParseHeader(header)
	if err != nil {
		return nil, nil, err
	}

	for _, hdr := range acceptHeader {
		if val, ok := r[string(hdr.MediaRange)]; ok {
			return reflect.ValueOf(val).Interface(), hdr, nil
		}
	}
	return nil, nil, ErrNoContentType
}

// ContentType parses the provided Content-Type header and attempts to find an
// interface which implements the specified content type
func (r Registry) ContentType(header string) (interface{}, ContentTypeParams, error) {
	mediaType, params, err := mime.ParseMediaType(header)
	if err != nil {
		return nil, nil, err
	}

	if val, ok := r[mediaType]; ok {
		return reflect.ValueOf(val).Interface(), params, nil
	}
	return nil, nil, ErrNoContentType
}