package message

import "fmt"

const (
	// Reset resets the color
	Reset = "\033[0m"

	// Bold makes the following text bold
	Bold = "\033[1m"

	// Dim dims the following text
	Dim = "\033[2m"

	// Italic makes the following text italic
	Italic = "\033[3m"

	// Underline underlines the following text
	Underline = "\033[4m"

	// Blink blinks the following text
	Blink = "\033[5m"

	// Invert inverts the following text
	Invert = "\033[7m"

	// Newline
	Newline = "\r\n"

	// BEL
	Bel = "\007"
)

// Interface for Styles
type Style interface {
	String() string
	Format(string) string
}

// General hardcoded style, mostly used as a crutch until we flesh out the
// framework to support backgrounds etc.
type style string

func (c style) String() string {
	return string(c)
}

func (c style) Format(s string) string {
	return c.String() + s + Reset
}

// 256 color type, for terminals who support it
type Color256 uint8

// String version of this color
func (c Color256) String() string {
	return fmt.Sprintf("38;05;%d", c)
}

// Return formatted string with this color
func (c Color256) Format(s string) string {
	return "\033[" + c.String() + "m" + s + Reset
}

func Color256Palette(colors ...uint8) *Palette {
	size := len(colors)
	p := make([]Style, 0, size)
	for _, color := range colors {
		p = append(p, Color256(color))
	}
	return &Palette{
		colors: p,
		size:   size,
	}
}

// No color, used for mono theme
type Color0 struct{}

// No-op for Color0
func (c Color0) String() string {
	return ""
}

// No-op for Color0
func (c Color0) Format(s string) string {
	return s
}

// Container for a collection of colors
type Palette struct {
	colors []Style
	size   int
}

// Get a color by index, overflows are looped around.
func (p Palette) Get(i int) Style {
	if p.size == 1 {
		return p.colors[0]
	}
	return p.colors[i%(p.size-1)]
}

func (p Palette) Len() int {
	return p.size
}

func (p Palette) String() string {
	r := ""
	for _, c := range p.colors {
		r += c.Format("X")
	}
	return r
}

// Collection of settings for chat
type Theme struct {
	id        string
	sys       Style
	pm        Style
	highlight Style
	names     *Palette
}

func (t Theme) ID() string {
	return t.id
}

// Colorize name string given some index
func (t Theme) ColorName(u *User) string {
	if t.names == nil {
		return u.Name()
	}

	return t.names.Get(u.colorIdx).Format(u.Name())
}

// Colorize the PM string
func (t Theme) ColorPM(s string) string {
	if t.pm == nil {
		return s
	}

	return t.pm.Format(s)
}

// Colorize the Sys message
func (t Theme) ColorSys(s string) string {
	if t.sys == nil {
		return s
	}

	return t.sys.Format(s)
}

// Highlight a matched string, usually name
func (t Theme) Highlight(s string) string {
	if t.highlight == nil {
		return s
	}
	return t.highlight.Format(s)
}

// List of initialzied themes
var Themes []Theme

// Default theme to use
var DefaultTheme *Theme

func allColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func readableColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		if i == 0 || i == 7 || i == 8 || i == 15 || i == 16 || i == 17 || i > 230 {
			// Skip 31 Shades of Grey, and one hyperintelligent shade of blue.
			continue
		}
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func init() {
	Themes = []Theme{
		{
			id:        "colors",
			names:     readableColors256(),
			sys:       Color256(245),                              // Grey
			pm:        Color256(7),                                // White
			highlight: style(Bold + "\033[48;5;11m\033[38;5;16m"), // Yellow highlight
		},
		{
			id:        "solarized",
			names:     Color256Palette(1, 2, 3, 4, 5, 6, 7, 9, 13),
			sys:       Color256(11),                              // Yellow
			pm:        Color256(15),                              // White
			highlight: style(Bold + "\033[48;5;3m\033[38;5;94m"), // Orange highlight
		},
		{
			id:        "hacker",
			names:     Color256Palette(82),                        // Green
			sys:       Color256(22),                               // Another green
			pm:        Color256(28),                               // More green, slightly lighter
			highlight: style(Bold + "\033[48;5;22m\033[38;5;46m"), // Green on dark green
		},
		{
			id: "mono",
		},
	}

	DefaultTheme = &Themes[0]

	/* Some debug helpers for your convenience:

	// Debug for palettes
	printPalette(allColors256())

	// Debug for themes
	for _, t := range Themes {
		printTheme(t)
	}

	*/
}

func printTheme(t Theme) {
	fmt.Println("Printing theme:", t.ID())
	if t.names != nil {
		for i, color := range t.names.colors {
			fmt.Printf("%s ", color.Format(fmt.Sprintf("name%d", i)))
		}
		fmt.Println("")
	}
	fmt.Println(t.ColorSys("SystemMsg"))
	fmt.Println(t.ColorPM("PrivateMsg"))
	fmt.Println(t.Highlight("Highlight"))
	fmt.Println("")
}

func printPalette(p *Palette) {
	for i, color := range p.colors {
		fmt.Printf("%d\t%s\n", i, color.Format(color.String()+" "))
	}
}