package ansi

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
)

const (
	black = iota
	red
	green
	yellow
	blue
	magenta
	cyan
	white
	defaultt = 9

	normalIntensityFG = 30
	highIntensityFG   = 90
	normalIntensityBG = 40
	highIntensityBG   = 100

	start         = "\033["
	normal        = "0;"
	bold          = "1;"
	dim           = "2;"
	underline     = "4;"
	blink         = "5;"
	inverse       = "7;"
	strikethrough = "9;"

	// Reset is the ANSI reset escape sequence
	Reset = "\033[0m"
	// DefaultBG is the default background
	DefaultBG = "\033[49m"
	// DefaultFG is the default foreground
	DefaultFG = "\033[39m"
)

// Black FG
var Black string

// Red FG
var Red string

// Green FG
var Green string

// Yellow FG
var Yellow string

// Blue FG
var Blue string

// Magenta FG
var Magenta string

// Cyan FG
var Cyan string

// White FG
var White string

// LightBlack FG
var LightBlack string

// LightRed FG
var LightRed string

// LightGreen FG
var LightGreen string

// LightYellow FG
var LightYellow string

// LightBlue FG
var LightBlue string

// LightMagenta FG
var LightMagenta string

// LightCyan FG
var LightCyan string

// LightWhite FG
var LightWhite string

var (
	plain = false
	// Colors maps common color names to their ANSI color code.
	Colors = map[string]int{
		"black":   black,
		"red":     red,
		"green":   green,
		"yellow":  yellow,
		"blue":    blue,
		"magenta": magenta,
		"cyan":    cyan,
		"white":   white,
		"default": defaultt,
	}
)

func init() {
	for i := 0; i < 256; i++ {
		Colors[strconv.Itoa(i)] = i
	}

	Black = ColorCode("black")
	Red = ColorCode("red")
	Green = ColorCode("green")
	Yellow = ColorCode("yellow")
	Blue = ColorCode("blue")
	Magenta = ColorCode("magenta")
	Cyan = ColorCode("cyan")
	White = ColorCode("white")
	LightBlack = ColorCode("black+h")
	LightRed = ColorCode("red+h")
	LightGreen = ColorCode("green+h")
	LightYellow = ColorCode("yellow+h")
	LightBlue = ColorCode("blue+h")
	LightMagenta = ColorCode("magenta+h")
	LightCyan = ColorCode("cyan+h")
	LightWhite = ColorCode("white+h")
}

// ColorCode returns the ANSI color color code for style.
func ColorCode(style string) string {
	return colorCode(style).String()
}

// Gets the ANSI color code for a style.
func colorCode(style string) *bytes.Buffer {
	buf := bytes.NewBufferString("")
	if plain || style == "" {
		return buf
	}
	if style == "reset" {
		buf.WriteString(Reset)
		return buf
	} else if style == "off" {
		return buf
	}

	foregroundBackground := strings.Split(style, ":")
	foreground := strings.Split(foregroundBackground[0], "+")
	fgKey := foreground[0]
	fg := Colors[fgKey]
	fgStyle := ""
	if len(foreground) > 1 {
		fgStyle = foreground[1]
	}

	bg, bgStyle := "", ""

	if len(foregroundBackground) > 1 {
		background := strings.Split(foregroundBackground[1], "+")
		bg = background[0]
		if len(background) > 1 {
			bgStyle = background[1]
		}
	}

	buf.WriteString(start)
	base := normalIntensityFG
	buf.WriteString(normal) // reset any previous style
	if len(fgStyle) > 0 {
		if strings.Contains(fgStyle, "b") {
			buf.WriteString(bold)
		}
		if strings.Contains(fgStyle, "d") {
			buf.WriteString(dim)
		}
		if strings.Contains(fgStyle, "B") {
			buf.WriteString(blink)
		}
		if strings.Contains(fgStyle, "u") {
			buf.WriteString(underline)
		}
		if strings.Contains(fgStyle, "i") {
			buf.WriteString(inverse)
		}
		if strings.Contains(fgStyle, "s") {
			buf.WriteString(strikethrough)
		}
		if strings.Contains(fgStyle, "h") {
			base = highIntensityFG
		}
	}

	// if 256-color
	n, err := strconv.Atoi(fgKey)
	if err == nil {
		fmt.Fprintf(buf, "38;5;%d;", n)
	} else {
		fmt.Fprintf(buf, "%d;", base+fg)
	}

	base = normalIntensityBG
	if len(bg) > 0 {
		if strings.Contains(bgStyle, "h") {
			base = highIntensityBG
		}
		// if 256-color
		n, err := strconv.Atoi(bg)
		if err == nil {
			fmt.Fprintf(buf, "48;5;%d;", n)
		} else {
			fmt.Fprintf(buf, "%d;", base+Colors[bg])
		}
	}

	// remove last ";"
	buf.Truncate(buf.Len() - 1)
	buf.WriteRune('m')
	return buf
}

// Color colors a string based on the ANSI color code for style.
func Color(s, style string) string {
	if plain || len(style) < 1 {
		return s
	}
	buf := colorCode(style)
	buf.WriteString(s)
	buf.WriteString(Reset)
	return buf.String()
}

// ColorFunc creates a closure to avoid computation ANSI color code.
func ColorFunc(style string) func(string) string {
	if style == "" {
		return func(s string) string {
			return s
		}
	}
	color := ColorCode(style)
	return func(s string) string {
		if plain || s == "" {
			return s
		}
		buf := bytes.NewBufferString(color)
		buf.WriteString(s)
		buf.WriteString(Reset)
		result := buf.String()
		return result
	}
}

// DisableColors disables ANSI color codes. The default is false (colors are on).
func DisableColors(disable bool) {
	plain = disable
	if plain {
		Black = ""
		Red = ""
		Green = ""
		Yellow = ""
		Blue = ""
		Magenta = ""
		Cyan = ""
		White = ""
		LightBlack = ""
		LightRed = ""
		LightGreen = ""
		LightYellow = ""
		LightBlue = ""
		LightMagenta = ""
		LightCyan = ""
		LightWhite = ""
	} else {
		Black = ColorCode("black")
		Red = ColorCode("red")
		Green = ColorCode("green")
		Yellow = ColorCode("yellow")
		Blue = ColorCode("blue")
		Magenta = ColorCode("magenta")
		Cyan = ColorCode("cyan")
		White = ColorCode("white")
		LightBlack = ColorCode("black+h")
		LightRed = ColorCode("red+h")
		LightGreen = ColorCode("green+h")
		LightYellow = ColorCode("yellow+h")
		LightBlue = ColorCode("blue+h")
		LightMagenta = ColorCode("magenta+h")
		LightCyan = ColorCode("cyan+h")
		LightWhite = ColorCode("white+h")
	}
}
