func IsDefinedTag(name string) bool {
	_, ok := colorTags[name]
	return ok
}
IsDefinedTag is defined tag name
package color
import (
	"fmt"
	"regexp"
	"strings"
)
const (
	MatchExpr = `<([0-9a-zA-Z_=,;]+)>(?s:(.*?))<\/>`
	AttrExpr = `(fg|bg|op)[\s]*=[\s]*([0-9a-zA-Z,]+);?`
	StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>`
)
var (
	attrRegex  = regexp.MustCompile(AttrExpr)
	matchRegex = regexp.MustCompile(MatchExpr)
	stripRegex = regexp.MustCompile(StripExpr)
)
var colorTags = map[string]string{
	"red":      "0;31",
	"red1":     "1;31",  
	"redB":     "1;31",
	"red_b":    "1;31",
	"blue":     "0;34",
	"blue1":    "1;34",  
	"blueB":    "1;34",
	"blue_b":   "1;34",
	"cyan":     "0;36",
	"cyan1":    "1;36",  
	"cyanB":    "1;36",
	"cyan_b":   "1;36",
	"green":    "0;32",
	"green1":   "1;32",  
	"greenB":   "1;32",
	"green_b":  "1;32",
	"black":    "0;30",
	"white":    "1;37",
	"default":  "0;39",  
	"normal":   "0;39",  
	"brown":    "0;33",  
	"yellow":   "0;33",
	"ylw0":     "0;33",
	"yellowB":  "1;33",  
	"ylw1":     "1;33",
	"ylwB":     "1;33",
	"magenta":  "0;35",
	"mga":      "0;35",  
	"magentaB": "1;35",  
	"mgb":      "1;35",
	"mgaB":     "1;35",
	"gray":          "0;90",
	"darkGray":      "0;90",
	"dark_gray":     "0;90",
	"lightYellow":   "0;93",
	"light_yellow":  "0;93",
	"hiYellow":      "0;93",
	"hi_yellow":     "0;93",
	"hiYellowB":     "1;93",  
	"hi_yellow_b":   "1;93",
	"lightMagenta":  "0;95",
	"light_magenta": "0;95",
	"hiMagenta":     "0;95",
	"hi_magenta":    "0;95",
	"lightMagentaB": "1;95",  
	"hiMagentaB":    "1;95",  
	"hi_magenta_b":  "1;95",
	"lightRed":      "0;91",
	"light_red":     "0;91",
	"hiRed":         "0;91",
	"hi_red":        "0;91",
	"lightRedB":     "1;91",  
	"light_red_b":   "1;91",
	"hi_red_b":      "1;91",
	"lightGreen":    "0;92",
	"light_green":   "0;92",
	"hiGreen":       "0;92",
	"hi_green":      "0;92",
	"lightGreenB":   "1;92",
	"light_green_b": "1;92",
	"hi_green_b":    "1;92",
	"lightBlue":     "0;94",
	"light_blue":    "0;94",
	"hiBlue":        "0;94",
	"hi_blue":       "0;94",
	"lightBlueB":    "1;94",
	"light_blue_b":  "1;94",
	"hi_blue_b":     "1;94",
	"lightCyan":     "0;96",
	"light_cyan":    "0;96",
	"hiCyan":        "0;96",
	"hi_cyan":       "0;96",
	"lightCyanB":    "1;96",
	"light_cyan_b":  "1;96",
	"hi_cyan_b":     "1;96",
	"lightWhite":    "0;97;40",
	"light_white":   "0;97;40",
	"bold":       "1",
	"b":          "1",
	"underscore": "4",
	"us":         "4",  
	"reverse":    "7",
	"suc":     "1;32",  
	"success": "1;32",
	"info":    "0;32",  
	"comment": "0;33",  
	"note":    "36;1",
	"notice":  "36;4",
	"warn":    "0;1;33",
	"warning": "0;30;43",
	"primary": "0;34",
	"danger":  "1;31",  
	"err":     "97;41",
	"error":   "97;41",  
}
var (
	tagParser = TagParser{}
	rxNumStr  = regexp.MustCompile("^[0-9]{1,3}$")
	rxHexCode = regexp.MustCompile("^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
)
type TagParser struct {
	disable bool
}
func NewTagParser() *TagParser {
	return &TagParser{}
}
func (tp *TagParser) ParseByEnv(str string) string {
	if !RenderTag {
		return str
	}
	if !Enable || !SupportColor() {
		return ClearTag(str)
	}
	return tp.Parse(str)
}
func (tp *TagParser) Parse(str string) string {
	if !strings.Contains(str, "</>") {
		return str
	}
	matched := matchRegex.FindAllStringSubmatch(str, -1)
	for _, item := range matched {
		full, tag, content := item[0], item[1], item[2]
		if !strings.ContainsRune(tag, '=') {
			code := colorTags[tag]
			if len(code) > 0 {
				now := RenderString(code, content)
				str = strings.Replace(str, full, now, 1)
			}
			continue
		}
		if code := ParseCodeFromAttr(tag); len(code) > 0 {
			now := RenderString(code, content)
			str = strings.Replace(str, full, now, 1)
		}
	}
	return str
}
func ReplaceTag(str string) string {
	return tagParser.ParseByEnv(str)
}
func ParseCodeFromAttr(attr string) (code string) {
	if !strings.ContainsRune(attr, '=') {
		return
	}
	attr = strings.Trim(attr, ";=,")
	if len(attr) == 0 {
		return
	}
	var codes []string
	matched := attrRegex.FindAllStringSubmatch(attr, -1)
	for _, item := range matched {
		pos, val := item[1], item[2]
		switch pos {
		case "fg":
			if c, ok := FgColors[val]; ok {  
				codes = append(codes, c.String())
			} else if c, ok := ExFgColors[val]; ok {  
				codes = append(codes, c.String())
			} else if code := rgbHex256toCode(val, false); code != "" {
				codes = append(codes, code)
			}
		case "bg":
			if c, ok := BgColors[val]; ok {  
				codes = append(codes, c.String())
			} else if c, ok := ExBgColors[val]; ok {  
				codes = append(codes, c.String())
			} else if code := rgbHex256toCode(val, true); code != "" {
				codes = append(codes, code)
			}
		case "op":  
			if strings.Contains(val, ",") {
				ns := strings.Split(val, ",")
				for _, n := range ns {
					if c, ok := AllOptions[n]; ok {
						codes = append(codes, c.String())
					}
				}
			} else if c, ok := AllOptions[val]; ok {
				codes = append(codes, c.String())
			}
		}
	}
	return strings.Join(codes, ";")
}
func rgbHex256toCode(val string, isBg bool) (code string) {
	if len(val) == 6 && rxHexCode.MatchString(val) {  
		code = HEX(val, isBg).String()
	} else if strings.ContainsRune(val, ',') {  
		code = strings.Replace(val, ",", ";", -1)
		if isBg {
			code = BgRGBPfx + code
		} else {
			code = FgRGBPfx + code
		}
	} else if len(val) < 4 && rxNumStr.MatchString(val) {  
		if isBg {
			code = Bg256Pfx + val
		} else {
			code = Fg256Pfx + val
		}
	}
	return
}
func ClearTag(s string) string {
	if !strings.Contains(s, "</>") {
		return s
	}
	return stripRegex.ReplaceAllString(s, "")
}
func GetTagCode(name string) string {
	return colorTags[name]
}
func ApplyTag(tag string, a ...interface{}) string {
	return RenderCode(GetTagCode(tag), a...)
}
func WrapTag(s string, tag string) string {
	if s == "" || tag == "" {
		return s
	}
	return fmt.Sprintf("<%s>%s</>", tag, s)
}
func GetColorTags() map[string]string {
	return colorTags
}