func mapKeys(m map[string]interface{}) (keys []string) {
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	return
}
mapKeys returns map keys, sorted ascending.
package logformat
import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
	"github.com/nqd/flat"
	"github.com/tj/go-logformat/internal/colors"
)
type FormatFunc func(string) string
type DateFormatFunc func(time.Time) string
type Formatters map[string]FormatFunc
var DefaultFormatters = Formatters{
	"debug":     colors.Gray,
	"info":      colors.Purple,
	"warn":      colors.Yellow,
	"warning":   colors.Yellow,
	"error":     colors.Red,
	"fatal":     colors.Red,
	"critical":  colors.Red,
	"emergency": colors.Red,
	"string": colors.None,
	"number": colors.None,
	"bool":   colors.None,
	"date":   colors.Gray,
	"object.key":       colors.Purple,
	"object.separator": colors.Gray,
	"object.value":     colors.None,
	"array.delimiter": colors.Gray,
	"array.separator": colors.Gray,
	"message": colors.None,
	"program": colors.Gray,
	"stage":   colors.Gray,
	"version": colors.Gray,
}
var NoColor = Formatters{
	"debug":     colors.None,
	"info":      colors.None,
	"warn":      colors.None,
	"warning":   colors.None,
	"error":     colors.None,
	"fatal":     colors.None,
	"critical":  colors.None,
	"emergency": colors.None,
	"string": colors.None,
	"number": colors.None,
	"bool":   colors.None,
	"date":   colors.None,
	"object.key":       colors.None,
	"object.separator": colors.None,
	"object.value":     colors.None,
	"array.delimiter": colors.None,
	"array.separator": colors.None,
	"message": colors.None,
	"program": colors.None,
	"stage":   colors.None,
	"version": colors.None,
}
type config struct {
	format     Formatters
	formatDate DateFormatFunc
	flatten    bool
	prefix     string
}
type Option func(*config)
func WithPrefix(s string) Option {
	return func(c *config) {
		c.prefix = s
	}
}
func WithFormatters(v Formatters) Option {
	return func(c *config) {
		c.format = v
	}
}
func WithDateFormatter(v DateFormatFunc) Option {
	return func(c *config) {
		c.formatDate = v
	}
}
func WithFlatten(v bool) Option {
	return func(c *config) {
		c.flatten = v
	}
}
func newConfig(options ...Option) *config {
	c := &config{
		format:     DefaultFormatters,
		formatDate: formatDate,
	}
	for _, o := range options {
		o(c)
	}
	return c
}
func Compact(m map[string]interface{}, options ...Option) string {
	return compact(m, newConfig(options...))
}
func compact(v interface{}, c *config) string {
	switch v := v.(type) {
	case map[string]interface{}:
		return compactMap(v, c)
	case []interface{}:
		return compactSlice(v, c)
	default:
		return primitive(v, c)
	}
}
func Prefix(m map[string]interface{}, options ...Option) string {
	s := ""
	c := newConfig(options...)
	if v, ok := m["timestamp"].(string); ok {
		t, err := time.Parse(time.RFC3339, v)
		if err == nil {
			s += primitive(t, c) + " "
			delete(m, "timestamp")
		}
	}
	if v, ok := m["level"].(string); ok {
		format := c.format[v]
		if format != nil {
			s += bold(format(strings.ToUpper(v[:4]))) + " "
			delete(m, "level")
		}
	}
	if v, ok := m["application"].(string); ok {
		s += c.format["application"](v) + " "
		delete(m, "application")
	}
	if v, ok := m["stage"].(string); ok {
		s += c.format["stage"](v) + " "
		delete(m, "stage")
	}
	if v, ok := m["version"]; ok {
		switch v := v.(type) {
		case string:
			s += c.format["version"](v) + " "
		case float64:
			s += c.format["version"](strconv.FormatFloat(v, 'f', -1, 64)) + " "
		}
		delete(m, "version")
	}
	if v, ok := m["message"].(string); ok {
		s += c.format["message"](v)
		delete(m, "message")
	}
	return s
}
func compactMap(m map[string]interface{}, c *config) string {
	m = maybeFlatten(m, c)
	s := ""
	keys := mapKeys(m)
	for i, k := range keys {
		v := m[k]
		s += c.format["object.key"](k)
		s += c.format["object.separator"]("=")
		if isComposite(v) {
			s += c.format["object.value"](compact(v, c))
		} else {
			s += compact(v, c)
		}
		if i < len(keys)-1 {
			s += " "
		}
	}
	return s
}
func compactSlice(v []interface{}, c *config) string {
	s := c.format["array.delimiter"]("[")
	for i, v := range v {
		if i > 0 {
			s += c.format["array.separator"](", ")
		}
		s += compact(v, c)
	}
	return s + c.format["array.delimiter"]("]")
}
func Expanded(m map[string]interface{}, options ...Option) string {
	c := newConfig(options...)
	return expanded(m, c.prefix, c)
}
func expanded(v interface{}, prefix string, c *config) string {
	switch v := v.(type) {
	case map[string]interface{}:
		return expandedMap(v, prefix, c)
	case []interface{}:
		return expandedSlice(v, prefix, c)
	default:
		return primitive(v, c)
	}
}
func expandedMap(m map[string]interface{}, prefix string, c *config) string {
	m = maybeFlatten(m, c)
	s := ""
	keys := mapKeys(m)
	for _, k := range keys {
		v := m[k]
		k = c.format["object.key"](k)
		d := c.format["object.separator"](":")
		if isComposite(v) {
			s += fmt.Sprintf("%s%s%s\n%s", prefix, k, d, expanded(v, prefix+"  ", c))
		} else {
			s += fmt.Sprintf("%s%s%s %s\n", prefix, k, d, expanded(v, prefix+"  ", c))
		}
	}
	return s
}
func expandedSlice(v []interface{}, prefix string, c *config) string {
	s := ""
	for _, v := range v {
		d := c.format["array.separator"]("-")
		if isComposite(v) {
			s += fmt.Sprintf("%s%s\n%s", prefix, d, expanded(v, prefix+"  ", c))
		} else {
			s += fmt.Sprintf("%s%s %v\n", prefix, d, primitive(v, c))
		}
	}
	return s
}
func primitive(v interface{}, c *config) string {
	switch v := v.(type) {
	case string:
		if strings.ContainsAny(v, " \n\t") || strings.TrimSpace(v) == "" {
			return c.format["string"](strconv.Quote(v))
		} else {
			return c.format["string"](v)
		}
	case time.Time:
		return c.format["date"](c.formatDate(v))
	case bool:
		return c.format["bool"](strconv.FormatBool(v))
	case float64:
		return c.format["number"](strconv.FormatFloat(v, 'f', -1, 64))
	default:
		return fmt.Sprintf("%v", v)
	}
}
func isComposite(v interface{}) bool {
	switch v.(type) {
	case map[string]interface{}:
		return true
	case []interface{}:
		return true
	default:
		return false
	}
}