package twin

import "fmt"

// Create using NewColor16(), NewColor256 or NewColor24Bit(), or use
// ColorDefault.
type Color uint32
type ColorType uint8

const (
	// Default foreground / background color
	colorTypeDefault ColorType = iota

	// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
	colorType16

	// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
	colorType256

	// RGB: https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
	colorType24bit
)

// Reset to default foreground / background color
var ColorDefault = newColor(colorTypeDefault, 0)

// From: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
var colorNames16 = map[int]string{
	0:  "0 black",
	1:  "1 red",
	2:  "2 green",
	3:  "3 yellow (orange)",
	4:  "4 blue",
	5:  "5 magenta",
	6:  "6 cyan",
	7:  "7 white (light gray)",
	8:  "8 bright black (dark gray)",
	9:  "9 bright red",
	10: "10 bright green",
	11: "11 bright yellow",
	12: "12 bright blue",
	13: "13 bright magenta",
	14: "14 bright cyan",
	15: "15 bright white",
}

func newColor(colorType ColorType, value uint32) Color {
	return Color(value | (uint32(colorType) << 24))
}

// Four bit colors as defined here:
// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
func NewColor16(colorNumber0to15 int) Color {
	return newColor(colorType16, uint32(colorNumber0to15))
}

func NewColor256(colorNumber uint8) Color {
	return newColor(colorType256, uint32(colorNumber))
}

func NewColor24Bit(red uint8, green uint8, blue uint8) Color {
	return newColor(colorType24bit, (uint32(red)<<16)+(uint32(green)<<8)+(uint32(blue)<<0))
}

func NewColorHex(rgb uint32) Color {
	return newColor(colorType24bit, rgb)
}

func (color Color) colorType() ColorType {
	return ColorType(color >> 24)
}

func (color Color) colorValue() uint32 {
	return uint32(color & 0xff_ff_ff)
}

// Render color into an ANSI string.
//
// Ref: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func (color Color) ansiString(foreground bool) string {
	value := color.colorValue()

	fgBgMarker := "3"
	if !foreground {
		fgBgMarker = "4"
	}

	if color.colorType() == colorType16 {
		if value < 8 {
			return fmt.Sprint("\x1b[", fgBgMarker, value, "m")
		} else if value <= 15 {
			fgBgMarker := "9"
			if !foreground {
				fgBgMarker = "10"
			}
			return fmt.Sprint("\x1b[", fgBgMarker, value-8, "m")
		}
	}

	if color.colorType() == colorType256 {
		if value <= 255 {
			return fmt.Sprint("\x1b[", fgBgMarker, "8;5;", value, "m")
		}
	}

	if color.colorType() == colorType24bit {
		red := (value & 0xff0000) >> 16
		green := (value & 0xff00) >> 8
		blue := value & 0xff

		return fmt.Sprint("\x1b[", fgBgMarker, "8;2;", red, ";", green, ";", blue, "m")
	}

	if color.colorType() == colorTypeDefault {
		return fmt.Sprint("\x1b[", fgBgMarker, "9m")
	}

	panic(fmt.Errorf("unhandled color type=%d value=%#x", color.colorType(), value))
}

func (color Color) ForegroundAnsiString() string {
	// FIXME: Test this function with all different color types.
	return color.ansiString(true)
}

func (color Color) BackgroundAnsiString() string {
	// FIXME: Test this function with all different color types.
	return color.ansiString(false)
}

func (color Color) String() string {
	switch color.colorType() {
	case colorTypeDefault:
		return "Default color"

	case colorType16:
		return colorNames16[int(color.colorValue())]

	case colorType256:
		if color.colorValue() < 16 {
			return colorNames16[int(color.colorValue())]
		}
		return fmt.Sprintf("#%02x", color.colorValue())

	case colorType24bit:
		return fmt.Sprintf("#%06x", color.colorValue())
	}

	panic(fmt.Errorf("unhandled color type %d", color.colorType()))
}
