// Package colour ([docs][1]) provides [Quake-style colour formatting][2] for Unix terminals.
//
// It is a drop-in replacement for the fmt package.
//
// The package level functions can be used to write to stdout (or strings or
// other files). If stdout is not a terminal, colour formatting will be
// stripped.
//
// eg.
//
//     colour.Printf("^0black ^1red ^2green ^3yellow ^4blue ^5magenta ^6cyan ^7white^R\n")
//
//
// For more control a Printer object can be created with various helper
// functions. This can be used to do useful things such as strip formatting,
// write to strings, and so on.
//
// The following sequences are converted to their equivalent ANSI colours:
//
//     ^0 = Black
//     ^1 = Red
//     ^2 = Green
//     ^3 = Yellow
//     ^4 = Blue
//     ^5 = Cyan (light blue)
//     ^6 = Magenta (purple)
//     ^7 = White
//     ^8 = Black Background
//     ^9 = Red Background
//     ^a = Green Background
//     ^b = Yellow Background
//     ^c = Blue Background
//     ^d = Cyan (light blue) Background
//     ^e = Magenta (purple) Background
//     ^f = White Background
//     ^R = Reset
//     ^U = Underline
//     ^B = Bold
//
// [1]: http://godoc.org/github.com/alecthomas/colour
// [2]: http://www.holysh1t.net/quake-live-colors-nickname/
package colour

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"

	"github.com/mattn/go-isatty"
)

var (
	extract = regexp.MustCompile(`(\^[0-9a-fRDUB])|(\^\^)|([^^]+)`)
	colours = map[byte]string{
		'0': "\033[30m",
		'1': "\033[31m",
		'2': "\033[32m",
		'3': "\033[33m",
		'4': "\033[34m",
		'5': "\033[35m",
		'6': "\033[36m",
		'7': "\033[37m",

		'8': "\033[40m",
		'9': "\033[41m",
		'a': "\033[42m",
		'b': "\033[43m",
		'c': "\033[44m",
		'd': "\033[45m",
		'e': "\033[46m",
		'f': "\033[47m",

		'R': "\033[0m", // reset
		'B': "\033[1m", // bold
		'D': "\033[2m", // dim
		'U': "\033[4m",
	}

	// Stdout is an conditional colour writer for os.Stdout.
	Stdout = TTY(os.Stdout)
	// Stderr is an conditional colour writer for os.Stderr.
	Stderr = TTY(os.Stderr)
)

func Println(args ...interface{}) (n int, err error) {
	return Stdout.Println(args...)
}

func Sprintln(args ...interface{}) string {
	s := String()
	_, _ = s.Println(args...)
	return s.String()
}

func Fprintln(w io.Writer, args ...interface{}) (n int, err error) {
	return TTY(w).Println(args...)
}

func Print(args ...interface{}) (n int, err error) {
	return Stdout.Print(args...)
}

func Sprint(args ...interface{}) string {
	s := String()
	_, _ = s.Print(args...)
	return s.String()
}

func Fprint(w io.Writer, args ...interface{}) (n int, err error) {
	return TTY(w).Print(args...)
}

func Printf(format string, args ...interface{}) (n int, err error) {
	return Stdout.Printf(format, args...)
}

func Sprintf(format string, args ...interface{}) string {
	s := String()
	_, _ = s.Printf(format, args...)
	return s.String()
}

func Fprintf(w io.Writer, format string, args ...interface{}) (n int, err error) {
	return TTY(w).Printf(format, args...)
}

// A Printer implements functions that accept Quake-style colour formatting
// and print coloured text.
type Printer interface {
	Println(args ...interface{}) (n int, err error)
	Print(args ...interface{}) (n int, err error)
	Printf(format string, args ...interface{}) (n int, err error)
}

// TTY creates a Printer that colourises output if w is a terminal, or strips
// formatting if it is not.
func TTY(w io.Writer) Printer {
	if f, ok := w.(*os.File); ok && isatty.IsTerminal(f.Fd()) {
		return &colourPrinter{w}
	}
	return &stripPrinter{w}
}

// Colour creats a new ANSI colour Printer on w.
func Colour(w io.Writer) Printer {
	return &colourPrinter{w}
}

// Strip returns a Printer that strips all colour codes from printed strings before writing to w.
func Strip(w io.Writer) Printer {
	return &stripPrinter{w}
}

type StringPrinter struct {
	Printer
	w *bytes.Buffer
}

// String creates a new Printer that writes ANSI coloured text to a buffer.
// Use the String() method to return the printed string.
func String() *StringPrinter {
	w := &bytes.Buffer{}
	f := Colour(w)
	return &StringPrinter{f, w}
}

// StringStripper writes text stripped of colour formatting codes to a string.
// Use the String() method to return the printed string.
func StringStripper() *StringPrinter {
	w := &bytes.Buffer{}
	f := Strip(w)
	return &StringPrinter{f, w}
}

func (s *StringPrinter) String() string {
	return s.w.String()
}

type colourPrinter struct {
	w io.Writer
}

func (c *colourPrinter) Println(args ...interface{}) (n int, err error) {
	for i, arg := range args {
		if s, ok := arg.(string); ok {
			args[i] = FormatString(s)
		}
	}
	return fmt.Fprintln(c.w, args...)
}

func (c *colourPrinter) Print(args ...interface{}) (n int, err error) {
	for i, arg := range args {
		if s, ok := arg.(string); ok {
			args[i] = FormatString(s)
		}
	}
	return fmt.Fprint(c.w, args...)
}

func (c *colourPrinter) Printf(format string, args ...interface{}) (n int, err error) {
	return fmt.Fprintf(c.w, FormatString(format), args...)
}

type stripPrinter struct {
	w io.Writer
}

func (p *stripPrinter) Println(args ...interface{}) (n int, err error) {
	return fmt.Fprintln(p.w, stripArgs(args...)...)
}

func (p *stripPrinter) Print(args ...interface{}) (n int, err error) {
	return fmt.Fprint(p.w, stripArgs(args...)...)
}

func (p *stripPrinter) Printf(format string, args ...interface{}) (n int, err error) {
	return fmt.Fprintf(p.w, StripFormatting(format), args...)
}

func FormatString(s string) string {
	out := &bytes.Buffer{}
	for _, match := range extract.FindAllStringSubmatch(s, -1) {
		if match[1] != "" {
			n := match[1][1]
			out.WriteString(colours[n])
		} else if match[2] != "" {
			out.WriteString("^")
		} else {
			out.WriteString(match[3])
		}
	}
	return out.String()
}

func StripFormatting(s string) string {
	out := &bytes.Buffer{}
	for _, match := range extract.FindAllStringSubmatch(s, -1) {
		if match[2] != "" {
			out.WriteString("^")
		} else if match[1] == "" {
			out.WriteString(match[3])
		}
	}
	return out.String()
}

func stripArgs(args ...interface{}) []interface{} {
	for i, arg := range args {
		if s, ok := arg.(string); ok {
			args[i] = StripFormatting(s)
		}
	}
	return args
}
