package logr

import (
	"bytes"
	"fmt"
	"io"
	"runtime"
	"sort"
)

// Formatter turns a LogRec into a formatted string.
type Formatter interface {
	// Format converts a log record to bytes. If buf is not nil then it will be
	// be filled with the formatted results, otherwise a new buffer will be allocated.
	Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error)
}

const (
	// DefTimestampFormat is the default time stamp format used by
	// Plain formatter and others.
	DefTimestampFormat = "2006-01-02 15:04:05.000 Z07:00"
)

// DefaultFormatter is the default formatter, outputting only text with
// no colors and a space delimiter. Use `format.Plain` instead.
type DefaultFormatter struct {
}

// Format converts a log record to bytes.
func (p *DefaultFormatter) Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error) {
	if buf == nil {
		buf = &bytes.Buffer{}
	}
	delim := " "
	timestampFmt := DefTimestampFormat

	fmt.Fprintf(buf, "%s%s", rec.Time().Format(timestampFmt), delim)
	fmt.Fprintf(buf, "%v%s", rec.Level(), delim)
	fmt.Fprint(buf, rec.Msg(), delim)

	ctx := rec.Fields()
	if len(ctx) > 0 {
		WriteFields(buf, ctx, " ")
	}

	if stacktrace {
		frames := rec.StackFrames()
		if len(frames) > 0 {
			buf.WriteString("\n")
			WriteStacktrace(buf, rec.StackFrames())
		}
	}
	buf.WriteString("\n")

	return buf, nil
}

// WriteFields writes zero or more name value pairs to the io.Writer.
// The pairs are sorted by key name and output in key=value format
// with optional separator between fields.
func WriteFields(w io.Writer, flds Fields, separator string) {
	keys := make([]string, 0, len(flds))
	for k := range flds {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	sep := ""
	for _, key := range keys {
		writeField(w, key, flds[key], sep)
		sep = separator
	}
}

func writeField(w io.Writer, key string, val interface{}, sep string) {
	var template string
	switch v := val.(type) {
	case error:
		val := v.Error()
		if shouldQuote(val) {
			template = "%s%s=%q"
		} else {
			template = "%s%s=%s"
		}
	case string:
		if shouldQuote(v) {
			template = "%s%s=%q"
		} else {
			template = "%s%s=%s"
		}
	default:
		template = "%s%s=%v"
	}
	fmt.Fprintf(w, template, sep, key, val)
}

// shouldQuote returns true if val contains any characters that might be unsafe
// when injecting log output into an aggregator, viewer or report.
func shouldQuote(val string) bool {
	for _, c := range val {
		if !((c >= '0' && c <= '9') ||
			(c >= 'a' && c <= 'z') ||
			(c >= 'A' && c <= 'Z')) {
			return true
		}
	}
	return false
}

// WriteStacktrace formats and outputs a stack trace to an io.Writer.
func WriteStacktrace(w io.Writer, frames []runtime.Frame) {
	for _, frame := range frames {
		if frame.Function != "" {
			fmt.Fprintf(w, "  %s\n", frame.Function)
		}
		if frame.File != "" {
			fmt.Fprintf(w, "      %s:%d\n", frame.File, frame.Line)
		}
	}
}