package stdlib

import (
	"regexp"
	"strconv"
	"strings"

	"github.com/d5/tengo/objects"
)

var textModule = map[string]objects.Object{
	"re_match":       &objects.UserFunction{Value: textREMatch},     // re_match(pattern, text) => bool/error
	"re_find":        &objects.UserFunction{Value: textREFind},      // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
	"re_replace":     &objects.UserFunction{Value: textREReplace},   // re_replace(pattern, text, repl) => string/error
	"re_split":       &objects.UserFunction{Value: textRESplit},     // re_split(pattern, text, count) => [string]/error
	"re_compile":     &objects.UserFunction{Value: textRECompile},   // re_compile(pattern) => Regexp/error
	"compare":        FuncASSRI(strings.Compare),                    // compare(a, b) => int
	"contains":       FuncASSRB(strings.Contains),                   // contains(s, substr) => bool
	"contains_any":   FuncASSRB(strings.ContainsAny),                // contains_any(s, chars) => bool
	"count":          FuncASSRI(strings.Count),                      // count(s, substr) => int
	"equal_fold":     FuncASSRB(strings.EqualFold),                  // "equal_fold(s, t) => bool
	"fields":         FuncASRSs(strings.Fields),                     // fields(s) => [string]
	"has_prefix":     FuncASSRB(strings.HasPrefix),                  // has_prefix(s, prefix) => bool
	"has_suffix":     FuncASSRB(strings.HasSuffix),                  // has_suffix(s, suffix) => bool
	"index":          FuncASSRI(strings.Index),                      // index(s, substr) => int
	"index_any":      FuncASSRI(strings.IndexAny),                   // index_any(s, chars) => int
	"join":           FuncASsSRS(strings.Join),                      // join(arr, sep) => string
	"last_index":     FuncASSRI(strings.LastIndex),                  // last_index(s, substr) => int
	"last_index_any": FuncASSRI(strings.LastIndexAny),               // last_index_any(s, chars) => int
	"repeat":         FuncASIRS(strings.Repeat),                     // repeat(s, count) => string
	"replace":        &objects.UserFunction{Value: textReplace},     // replace(s, old, new, n) => string
	"split":          FuncASSRSs(strings.Split),                     // split(s, sep) => [string]
	"split_after":    FuncASSRSs(strings.SplitAfter),                // split_after(s, sep) => [string]
	"split_after_n":  FuncASSIRSs(strings.SplitAfterN),              // split_after_n(s, sep, n) => [string]
	"split_n":        FuncASSIRSs(strings.SplitN),                   // split_n(s, sep, n) => [string]
	"title":          FuncASRS(strings.Title),                       // title(s) => string
	"to_lower":       FuncASRS(strings.ToLower),                     // to_lower(s) => string
	"to_title":       FuncASRS(strings.ToTitle),                     // to_title(s) => string
	"to_upper":       FuncASRS(strings.ToUpper),                     // to_upper(s) => string
	"trim_left":      FuncASSRS(strings.TrimLeft),                   // trim_left(s, cutset) => string
	"trim_prefix":    FuncASSRS(strings.TrimPrefix),                 // trim_prefix(s, prefix) => string
	"trim_right":     FuncASSRS(strings.TrimRight),                  // trim_right(s, cutset) => string
	"trim_space":     FuncASRS(strings.TrimSpace),                   // trim_space(s) => string
	"trim_suffix":    FuncASSRS(strings.TrimSuffix),                 // trim_suffix(s, suffix) => string
	"atoi":           FuncASRIE(strconv.Atoi),                       // atoi(str) => int/error
	"format_bool":    &objects.UserFunction{Value: textFormatBool},  // format_bool(b) => string
	"format_float":   &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
	"format_int":     &objects.UserFunction{Value: textFormatInt},   // format_int(i, base) => string
	"itoa":           FuncAIRS(strconv.Itoa),                        // itoa(i) => string
	"parse_bool":     &objects.UserFunction{Value: textParseBool},   // parse_bool(str) => bool/error
	"parse_float":    &objects.UserFunction{Value: textParseFloat},  // parse_float(str, bits) => float/error
	"parse_int":      &objects.UserFunction{Value: textParseInt},    // parse_int(str, base, bits) => int/error
	"quote":          FuncASRS(strconv.Quote),                       // quote(str) => string
	"unquote":        FuncASRSE(strconv.Unquote),                    // unquote(str) => string/error
}

func textREMatch(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 2 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	matched, err := regexp.MatchString(s1, s2)
	if err != nil {
		ret = wrapError(err)
		return
	}

	if matched {
		ret = objects.TrueValue
	} else {
		ret = objects.FalseValue
	}

	return
}

func textREFind(args ...objects.Object) (ret objects.Object, err error) {
	numArgs := len(args)
	if numArgs != 2 && numArgs != 3 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	re, err := regexp.Compile(s1)
	if err != nil {
		ret = wrapError(err)
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	if numArgs < 3 {
		m := re.FindStringSubmatchIndex(s2)
		if m == nil {
			ret = objects.UndefinedValue
			return
		}

		arr := &objects.Array{}
		for i := 0; i < len(m); i += 2 {
			arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
				"text":  &objects.String{Value: s2[m[i]:m[i+1]]},
				"begin": &objects.Int{Value: int64(m[i])},
				"end":   &objects.Int{Value: int64(m[i+1])},
			}})
		}

		ret = &objects.Array{Value: []objects.Object{arr}}

		return
	}

	i3, ok := objects.ToInt(args[2])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}
	m := re.FindAllStringSubmatchIndex(s2, i3)
	if m == nil {
		ret = objects.UndefinedValue
		return
	}

	arr := &objects.Array{}
	for _, m := range m {
		subMatch := &objects.Array{}
		for i := 0; i < len(m); i += 2 {
			subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{
				"text":  &objects.String{Value: s2[m[i]:m[i+1]]},
				"begin": &objects.Int{Value: int64(m[i])},
				"end":   &objects.Int{Value: int64(m[i+1])},
			}})
		}

		arr.Value = append(arr.Value, subMatch)
	}

	ret = arr

	return
}

func textREReplace(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 3 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s3, ok := objects.ToString(args[2])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	re, err := regexp.Compile(s1)
	if err != nil {
		ret = wrapError(err)
	} else {
		ret = &objects.String{Value: re.ReplaceAllString(s2, s3)}
	}

	return
}

func textRESplit(args ...objects.Object) (ret objects.Object, err error) {
	numArgs := len(args)
	if numArgs != 2 && numArgs != 3 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	var i3 = -1
	if numArgs > 2 {
		i3, ok = objects.ToInt(args[2])
		if !ok {
			err = objects.ErrInvalidTypeConversion
			return
		}
	}

	re, err := regexp.Compile(s1)
	if err != nil {
		ret = wrapError(err)
		return
	}

	arr := &objects.Array{}
	for _, s := range re.Split(s2, i3) {
		arr.Value = append(arr.Value, &objects.String{Value: s})
	}

	ret = arr

	return
}

func textRECompile(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 1 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	re, err := regexp.Compile(s1)
	if err != nil {
		ret = wrapError(err)
	} else {
		ret = makeTextRegexp(re)
	}

	return
}

func textReplace(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 4 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := objects.ToString(args[0])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s3, ok := objects.ToString(args[2])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i4, ok := objects.ToInt(args[3])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)}

	return
}

func textFormatBool(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 1 {
		err = objects.ErrWrongNumArguments
		return
	}

	b1, ok := args[0].(*objects.Bool)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	if b1 == objects.TrueValue {
		ret = &objects.String{Value: "true"}
	} else {
		ret = &objects.String{Value: "false"}
	}

	return
}

func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 4 {
		err = objects.ErrWrongNumArguments
		return
	}

	f1, ok := args[0].(*objects.Float)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	s2, ok := objects.ToString(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i3, ok := objects.ToInt(args[2])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i4, ok := objects.ToInt(args[3])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)}

	return
}

func textFormatInt(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 2 {
		err = objects.ErrWrongNumArguments
		return
	}

	i1, ok := args[0].(*objects.Int)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i2, ok := objects.ToInt(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)}

	return
}

func textParseBool(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 1 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := args[0].(*objects.String)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	parsed, err := strconv.ParseBool(s1.Value)
	if err != nil {
		ret = wrapError(err)
		return
	}

	if parsed {
		ret = objects.TrueValue
	} else {
		ret = objects.FalseValue
	}

	return
}

func textParseFloat(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 2 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := args[0].(*objects.String)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i2, ok := objects.ToInt(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	parsed, err := strconv.ParseFloat(s1.Value, i2)
	if err != nil {
		ret = wrapError(err)
		return
	}

	ret = &objects.Float{Value: parsed}

	return
}

func textParseInt(args ...objects.Object) (ret objects.Object, err error) {
	if len(args) != 3 {
		err = objects.ErrWrongNumArguments
		return
	}

	s1, ok := args[0].(*objects.String)
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i2, ok := objects.ToInt(args[1])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	i3, ok := objects.ToInt(args[2])
	if !ok {
		err = objects.ErrInvalidTypeConversion
		return
	}

	parsed, err := strconv.ParseInt(s1.Value, i2, i3)
	if err != nil {
		ret = wrapError(err)
		return
	}

	ret = &objects.Int{Value: parsed}

	return
}