package main

import (
	"fmt"

	"github.com/gookit/color"
)

// MakeColors creates instance of AnsiColors to use in AnsiWriter according to
// environment and configuration.
func MakeColors(env environmentInfo, cfg *Config) AnsiColors {
	cacheDisabled := env.getenv("OMP_CACHE_DISABLED") == "1"
	return makeColors(cfg.Palette, !cacheDisabled)
}

func makeColors(palette Palette, cacheEnabled bool) (colors AnsiColors) {
	colors = &DefaultColors{}
	if palette != nil {
		colors = &PaletteColors{ansiColors: colors, palette: palette}
	}
	if cacheEnabled {
		colors = &CachedColors{ansiColors: colors}
	}
	return
}

// DefaultColors is the default AnsiColors implementation.
type DefaultColors struct{}

var (
	// Map for color names and their respective foreground [0] or background [1] color codes
	ansiColorCodes = map[string][2]AnsiColor{
		"black":        {"30", "40"},
		"red":          {"31", "41"},
		"green":        {"32", "42"},
		"yellow":       {"33", "43"},
		"blue":         {"34", "44"},
		"magenta":      {"35", "45"},
		"cyan":         {"36", "46"},
		"white":        {"37", "47"},
		"default":      {"39", "49"},
		"darkGray":     {"90", "100"},
		"lightRed":     {"91", "101"},
		"lightGreen":   {"92", "102"},
		"lightYellow":  {"93", "103"},
		"lightBlue":    {"94", "104"},
		"lightMagenta": {"95", "105"},
		"lightCyan":    {"96", "106"},
		"lightWhite":   {"97", "107"},
	}
)

const (
	foregroundIndex = 0
	backgroundIndex = 1
)

func (*DefaultColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
	if len(colorString) == 0 {
		return emptyAnsiColor
	}
	if colorString == Transparent {
		return transparentAnsiColor
	}
	colorFromName, err := getAnsiColorFromName(colorString, isBackground)
	if err == nil {
		return colorFromName
	}
	style := color.HEX(colorString, isBackground)
	if style.IsEmpty() {
		return emptyAnsiColor
	}
	return AnsiColor(style.String())
}

// getAnsiColorFromName returns the color code for a given color name if the name is
// known ANSI color name.
func getAnsiColorFromName(colorName string, isBackground bool) (AnsiColor, error) {
	if colorCodes, found := ansiColorCodes[colorName]; found {
		if isBackground {
			return colorCodes[backgroundIndex], nil
		}
		return colorCodes[foregroundIndex], nil
	}
	return "", fmt.Errorf("color name %s does not exist", colorName)
}

func IsAnsiColorName(colorString string) bool {
	_, ok := ansiColorCodes[colorString]
	return ok
}

// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color
// lookups before ANSI color code generation.
type PaletteColors struct {
	ansiColors AnsiColors
	palette    Palette
}

func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
	paletteColor, err := p.palette.ResolveColor(colorString)
	if err != nil {
		return emptyAnsiColor
	}
	ansiColor := p.ansiColors.AnsiColorFromString(paletteColor, isBackground)
	return ansiColor
}

// CachedColors is the AnsiColors Decorator that does simple color lookup caching.
// AnsiColorFromString calls are cheap, but not free, and having a simple cache in
// has measurable positive effect on performance.
type CachedColors struct {
	ansiColors AnsiColors
	colorCache map[cachedColorKey]AnsiColor
}

type cachedColorKey struct {
	colorString  string
	isBackground bool
}

func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
	if c.colorCache == nil {
		c.colorCache = make(map[cachedColorKey]AnsiColor)
	}
	key := cachedColorKey{colorString, isBackground}
	if ansiColor, hit := c.colorCache[key]; hit {
		return ansiColor
	}
	ansiColor := c.ansiColors.AnsiColorFromString(colorString, isBackground)
	c.colorCache[key] = ansiColor
	return ansiColor
}
