package logger

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/fatih/color"
)

var nextFormatID = 1

type IFormatter interface {
	Format(Entry) []byte
	NextColor() IFormatter
	Color() string
}

type formatterJSON struct{}

func (f formatterJSON) Format(entry Entry) []byte {
	jsonEntry, err := json.Marshal(entry)
	if err != nil {
		return []byte(fmt.Sprintf("failed to marshal entry: %v: %+v\n", err, entry))
	}
	return append(jsonEntry, []byte("\n")...)
}

func (f formatterJSON) NextColor() IFormatter {
	return f //do not select colors for JSON (only used in console)
}

func (f formatterJSON) Color() string {
	return "default"
}

func NewConsole() IFormatter {
	nextFormatID++
	return formatterConsole{
		id: nextFormatID,
		fg: 1, //color.FgWhite,
		bg: 0, //color.BgBlack,
	}
}

type formatterConsole struct {
	id int
	fg int //color.Attribute
	bg int //color.Attribute
}

func (f formatterConsole) Format(entry Entry) []byte {
	source := fmt.Sprintf("%s/%s:%d", entry.Caller.Package, entry.Caller.File, entry.Caller.Line)
	if len(source) > 40 {
		source = source[len(source)-40:]
	}

	buffer := bytes.NewBuffer(nil)

	red := color.New(color.FgRed).FprintfFunc()
	magenta := color.New(color.FgMagenta).FprintfFunc()
	yellow := color.New(color.FgYellow).FprintfFunc()
	green := color.New(color.FgGreen).FprintfFunc()
	cyan := color.New(color.FgCyan).FprintfFunc()

	cyan(buffer, entry.Timestamp.Format("2006-01-02 15:04:05"))

	levelString := fmt.Sprintf(" %5.5s", entry.Level)
	switch entry.Level {
	case LevelFatal:
		red(buffer, levelString)
	case LevelError:
		red(buffer, levelString)
	case LevelWarn:
		magenta(buffer, levelString)
	case LevelInfo:
		yellow(buffer, levelString)
	case LevelDebug:
		green(buffer, levelString)
	}
	cyan(buffer, fmt.Sprintf(" %-40.40s| ", source))

	base := color.New(fgColors[colorNames[f.fg]], bgColors[colorNames[f.bg]]).FprintfFunc()
	base(buffer, entry.Message)

	if len(entry.Data) > 0 {
		jsonData, _ := json.Marshal(entry.Data)
		green(buffer, " "+string(jsonData))
	}
	buffer.WriteString("\n")
	return buffer.Bytes()
}

func (f formatterConsole) WithForeground(fg color.Attribute) IFormatter {
	//	f.fg = fg
	return f
}

func (f formatterConsole) WithBackground(bg color.Attribute) IFormatter {
	//	f.bg = bg
	return f
}

func (f formatterConsole) Color() string {
	return colorNames[f.fg] + " on " + colorNames[f.bg]
}

var (
	colorNames = []string{"black", "white", "red", "green", "yellow", "blue", "magenta", "cyan"}
	fgColors   = map[string]color.Attribute{
		"black":   color.FgBlack,
		"white":   color.FgWhite,
		"red":     color.FgRed,
		"green":   color.FgGreen,
		"yellow":  color.FgYellow,
		"blue":    color.FgBlue,
		"magenta": color.FgMagenta,
		"cyan":    color.FgCyan,
	}
	bgColors = map[string]color.Attribute{
		"black":   color.BgBlack,
		"white":   color.BgWhite,
		"red":     color.BgRed,
		"green":   color.BgGreen,
		"yellow":  color.BgYellow,
		"blue":    color.BgBlue,
		"magenta": color.BgMagenta,
		"cyan":    color.BgCyan,
	}
	nextFg = 1
	nextBg = 0
)

func (f formatterConsole) NextColor() IFormatter {
	for {
		nextFg++
		if nextFg >= len(fgColors) {
			nextFg = 0
		}
		if nextFg != nextBg {
			break
		}
	}
	nextFormatID++
	f = formatterConsole{
		id: nextFormatID,
		fg: nextFg,
		bg: nextBg,
	}
	return f
}