package formjson

import (
	"encoding/json"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"golang.org/x/net/context"

	"github.com/rs/xhandler"
)

// Handler detects when a POST/PUT/PATCH content type is JSON, and transparently convert
// the JSON content into a standard PostForm. This does only support posting of a JSON dictionary
// containing string => string key value pairs.
func Handler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handleFormJSONRequest(r)
		h.ServeHTTP(w, r)
	})
}

// HandlerC detects when a POST/PUT/PATCH content type is JSON, and transparently convert
// the JSON content into a standard PostForm. This does only support posting of a JSON dictionary
// containing string => string key value pairs.
func HandlerC(h xhandler.HandlerC) xhandler.HandlerC {
	return xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		handleFormJSONRequest(r)
		h.ServeHTTPC(ctx, w, r)
	})
}

func handleFormJSONRequest(r *http.Request) {
	if strings.Index(r.Header.Get("Content-Type"), "application/json") == -1 {
		return
	}
	switch r.Method {
	case "POST":
	case "PUT":
	case "PATCH":
		// whitelisted methods
	default:
		return
	}

	// Try to decode body using a restrictive type
	decoder := json.NewDecoder(r.Body)
	var d map[string]interface{}
	if err := decoder.Decode(&d); err != nil {
		return
	}
	// Inject parsed data into PostForm
	r.PostForm = url.Values{}
	for k, v := range d {
		switch t := v.(type) {
		case string:
			r.PostForm.Set(k, t)
		case float64:
			r.PostForm.Set(k, strconv.FormatFloat(t, 'f', -1, 64))
		case bool:
			if t {
				r.PostForm.Set(k, "1")
			} else {
				r.PostForm.Set(k, "0")
			}
		case []interface{}:
			r.PostForm[k] = []string{}
			for _, sv := range t {
				switch st := sv.(type) {
				case string:
					r.PostForm.Add(k, st)
				case float64:
					r.PostForm.Add(k, strconv.FormatFloat(st, 'f', -1, 64))
				case bool:
					if st {
						r.PostForm.Add(k, "1")
					} else {
						r.PostForm.Add(k, "0")
					}
				default:
					// Do not translate array partially
					r.PostForm.Del(k)
					break
				}
			}
		}
	}
	// Build the Form property
	if len(r.PostForm) > 0 {
		r.Form = url.Values{}
		for k, vs := range r.PostForm {
			r.Form[k] = []string{}
			for _, v := range vs {
				r.Form.Add(k, v)
			}
		}
		if r.URL != nil {
			if newValues, err := url.ParseQuery(r.URL.RawQuery); err == nil {
				for k, v := range newValues {
					r.Form.Set(k, v[0])
				}
			}
		}
	}
}