// Package emoji terminal output.
package emoji

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"regexp"
	"unicode"
)

//go:generate generateEmojiCodeMap -pkg emoji -o emoji_codemap.go

// Replace Padding character for emoji.
var (
	ReplacePadding = " "
)

// CodeMap gets the underlying map of emoji.
func CodeMap() map[string]string {
	return emojiCode()
}

// RevCodeMap gets the underlying map of emoji.
func RevCodeMap() map[string][]string {
	return emojiRevCode()
}

func AliasList(shortCode string) []string {
	return emojiRevCode()[emojiCode()[shortCode]]
}

// HasAlias flags if the given `shortCode` has multiple aliases with other
// codes.
func HasAlias(shortCode string) bool {
	return len(AliasList(shortCode)) > 1
}

// NormalizeShortCode normalizes a given `shortCode` to a deterministic alias.
func NormalizeShortCode(shortCode string) string {
	shortLists := AliasList(shortCode)
	if len(shortLists) == 0 {
		return shortCode
	}
	return shortLists[0]
}

// regular expression that matches :flag-[countrycode]:
var flagRegexp = regexp.MustCompile(":flag-([a-z]{2}):")

// Emojize Converts the string passed as an argument to a emoji. For unsupported emoji, the string passed as an argument is returned as is.
func Emojize(x string) string {
	str, ok := emojiCode()[x]
	if ok {
		return str + ReplacePadding
	}
	if match := flagRegexp.FindStringSubmatch(x); len(match) == 2 {
		return regionalIndicator(match[1][0]) + regionalIndicator(match[1][1])
	}
	return x
}

// regionalIndicator maps a lowercase letter to a unicode regional indicator
func regionalIndicator(i byte) string {
	return string('\U0001F1E6' + rune(i) - 'a')
}

func replaceEmoji(input *bytes.Buffer) string {
	emoji := bytes.NewBufferString(":")
	for {
		i, _, err := input.ReadRune()
		if err != nil {
			// not replace
			return emoji.String()
		}

		if i == ':' && emoji.Len() == 1 {
			return emoji.String() + replaceEmoji(input)
		}

		emoji.WriteRune(i)
		switch {
		case unicode.IsSpace(i):
			return emoji.String()
		case i == ':':
			return Emojize(emoji.String())
		}
	}
}

func compile(x string) string {
	if x == "" {
		return ""
	}

	input := bytes.NewBufferString(x)
	output := bytes.NewBufferString("")

	for {
		i, _, err := input.ReadRune()
		if err != nil {
			break
		}
		switch i {
		default:
			output.WriteRune(i)
		case ':':
			output.WriteString(replaceEmoji(input))
		}
	}
	return output.String()
}

// Print is fmt.Print which supports emoji
func Print(a ...interface{}) (int, error) {
	return fmt.Print(compile(fmt.Sprint(a...)))
}

// Println is fmt.Println which supports emoji
func Println(a ...interface{}) (int, error) {
	return fmt.Println(compile(fmt.Sprint(a...)))
}

// Printf is fmt.Printf which supports emoji
func Printf(format string, a ...interface{}) (int, error) {
	return fmt.Print(compile(fmt.Sprintf(format, a...)))
}

// Fprint is fmt.Fprint which supports emoji
func Fprint(w io.Writer, a ...interface{}) (int, error) {
	return fmt.Fprint(w, compile(fmt.Sprint(a...)))
}

// Fprintln is fmt.Fprintln which supports emoji
func Fprintln(w io.Writer, a ...interface{}) (int, error) {
	return fmt.Fprintln(w, compile(fmt.Sprint(a...)))
}

// Fprintf is fmt.Fprintf which supports emoji
func Fprintf(w io.Writer, format string, a ...interface{}) (int, error) {
	return fmt.Fprint(w, compile(fmt.Sprintf(format, a...)))
}

// Sprint is fmt.Sprint which supports emoji
func Sprint(a ...interface{}) string {
	return compile(fmt.Sprint(a...))
}

// Sprintf is fmt.Sprintf which supports emoji
func Sprintf(format string, a ...interface{}) string {
	return compile(fmt.Sprintf(format, a...))
}

// Errorf is fmt.Errorf which supports emoji
func Errorf(format string, a ...interface{}) error {
	return errors.New(compile(Sprintf(format, a...)))
}
