package gofakeit

import (
	"errors"
	"fmt"
	"math"
	"math/rand"
	"regexp/syntax"
	"strings"
)

// Generate fake information from given string.
// Replaceable values should be within {}
//
// Functions
// Ex: {firstname} - billy
// Ex: {sentence:3} - Record river mind.
// Ex: {number:1,10} - 4
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
//
// Letters/Numbers
// Ex: ### - 481 - random numbers
// Ex: ??? - fda - random letters
//
// For a complete list of runnable functions use FuncsLookup
func Generate(dataVal string) string { return generate(globalFaker.Rand, dataVal) }

// Generate fake information from given string.
// Replaceable values should be within {}
//
// Functions
// Ex: {firstname} - billy
// Ex: {sentence:3} - Record river mind.
// Ex: {number:1,10} - 4
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
//
// Letters/Numbers
// Ex: ### - 481 - random numbers
// Ex: ??? - fda - random letters
//
// For a complete list of runnable functions use FuncsLookup
func (f *Faker) Generate(dataVal string) string { return generate(f.Rand, dataVal) }

func generate(r *rand.Rand, dataVal string) string {
	// Replace # with numbers and ? with letters
	dataVal = replaceWithNumbers(r, dataVal)
	dataVal = replaceWithLetters(r, dataVal)

	// Check if string has any replaceable values
	if !strings.Contains(dataVal, "{") && !strings.Contains(dataVal, "}") {
		return dataVal
	}

	// Variables to identify the index in which it exists
	startCurly := -1
	startCurlyIgnore := []int{}
	endCurly := -1
	endCurlyIgnore := []int{}

	// Loop through string characters
	for i := 0; i < len(dataVal); i++ {
		// Check for ignores if equal skip
		shouldSkip := false
		for _, igs := range startCurlyIgnore {
			if i == igs {
				shouldSkip = true
			}
		}
		for _, ige := range endCurlyIgnore {
			if i == ige {
				shouldSkip = true
			}
		}
		if shouldSkip {
			continue
		}

		// Identify items between brackets. Ex: {firstname}
		if string(dataVal[i]) == "{" {
			startCurly = i
			continue
		}
		if startCurly != -1 && string(dataVal[i]) == "}" {
			endCurly = i
		}
		if startCurly == -1 || endCurly == -1 {
			continue
		}

		// Get the value between brackets
		fParts := dataVal[startCurly+1 : endCurly]

		// Check if has params separated by :
		fNameSplit := strings.SplitN(fParts, ":", 2)
		fName := ""
		fParams := ""
		if len(fNameSplit) >= 1 {
			fName = fNameSplit[0]
		}
		if len(fNameSplit) >= 2 {
			fParams = fNameSplit[1]
		}

		// Check to see if its a replaceable lookup function
		if info := GetFuncLookup(fName); info != nil {
			// Get parameters, make sure params and the split both have values
			mapParams := NewMapParams()
			paramsLen := len(info.Params)

			// If just one param and its a string simply just pass it
			if paramsLen == 1 && info.Params[0].Type == "string" {
				mapParams.Add(info.Params[0].Field, fParams)
			} else if paramsLen > 0 && fParams != "" {
				splitVals := funcLookupSplit(fParams)
				mapParams = addSplitValsToMapParams(splitVals, info, mapParams)
			}
			if mapParams.Size() == 0 {
				mapParams = nil
			}

			// Call function
			fValue, err := info.Generate(r, mapParams, info)
			if err != nil {
				// If we came across an error just dont replace value
				dataVal = strings.Replace(dataVal, "{"+fParts+"}", err.Error(), 1)
			} else {
				// Successfully found, run replace with new value
				dataVal = strings.Replace(dataVal, "{"+fParts+"}", fmt.Sprintf("%v", fValue), 1)
			}

			// Reset the curly index back to -1 and reset ignores
			startCurly = -1
			startCurlyIgnore = []int{}
			endCurly = -1
			endCurlyIgnore = []int{}
			i = -1 // Reset back to the start of the string
			continue
		}

		// Couldnt find anything - mark curly brackets to skip and rerun
		startCurlyIgnore = append(startCurlyIgnore, startCurly)
		endCurlyIgnore = append(endCurlyIgnore, endCurly)

		// Reset the curly index back to -1
		startCurly = -1
		endCurly = -1
		i = -1 // Reset back to the start of the string
		continue
	}

	return dataVal
}

// Regex will generate a string based upon a RE2 syntax
func Regex(regexStr string) string { return regex(globalFaker.Rand, regexStr) }

// Regex will generate a string based upon a RE2 syntax
func (f *Faker) Regex(regexStr string) string { return regex(f.Rand, regexStr) }

func regex(r *rand.Rand, regexStr string) (gen string) {
	re, err := syntax.Parse(regexStr, syntax.Perl)
	if err != nil {
		return "Could not parse regex string"
	}

	// Panic catch
	defer func() {
		if r := recover(); r != nil {
			gen = fmt.Sprint(r)
			return

		}
	}()

	return regexGenerate(r, re, len(regexStr)*100)
}

func regexGenerate(ra *rand.Rand, re *syntax.Regexp, limit int) string {
	if limit <= 0 {
		panic("Length limit reached when generating output")
	}

	op := re.Op
	switch op {
	case syntax.OpNoMatch: // matches no strings
		// Do Nothing
	case syntax.OpEmptyMatch: // matches empty string
		return ""
	case syntax.OpLiteral: // matches Runes sequence
		var b strings.Builder
		for _, ru := range re.Rune {
			b.WriteRune(ru)
		}
		return b.String()
	case syntax.OpCharClass: // matches Runes interpreted as range pair list
		// number of possible chars
		sum := 0
		for i := 0; i < len(re.Rune); i += 2 {
			sum += int(re.Rune[i+1]-re.Rune[i]) + 1
			if re.Rune[i+1] == 0x10ffff { // rune range end
				sum = -1
				break
			}
		}

		// pick random char in range (inverse match group)
		if sum == -1 {
			chars := []uint8{}
			for j := 0; j < len(allStr); j++ {
				c := allStr[j]

				// Check c in range
				for i := 0; i < len(re.Rune); i += 2 {
					if rune(c) >= re.Rune[i] && rune(c) <= re.Rune[i+1] {
						chars = append(chars, c)
						break
					}
				}
			}
			if len(chars) > 0 {
				return string([]byte{chars[ra.Intn(len(chars))]})
			}
		}

		r := ra.Intn(int(sum))
		var ru rune
		sum = 0
		for i := 0; i < len(re.Rune); i += 2 {
			gap := int(re.Rune[i+1]-re.Rune[i]) + 1
			if sum+gap > r {
				ru = re.Rune[i] + rune(r-sum)
				break
			}
			sum += gap
		}

		return string(ru)
	case syntax.OpAnyCharNotNL, syntax.OpAnyChar: // matches any character(and except newline)
		return randCharacter(ra, allStr)
	case syntax.OpBeginLine: // matches empty string at beginning of line
	case syntax.OpEndLine: // matches empty string at end of line
	case syntax.OpBeginText: // matches empty string at beginning of text
	case syntax.OpEndText: // matches empty string at end of text
	case syntax.OpWordBoundary: // matches word boundary `\b`
	case syntax.OpNoWordBoundary: // matches word non-boundary `\B`
	case syntax.OpCapture: // capturing subexpression with index Cap, optional name Name
		return regexGenerate(ra, re.Sub0[0], limit)
	case syntax.OpStar: // matches Sub[0] zero or more times
		var b strings.Builder
		for i := 0; i < number(ra, 0, 10); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpPlus: // matches Sub[0] one or more times
		var b strings.Builder
		for i := 0; i < number(ra, 1, 10); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpQuest: // matches Sub[0] zero or one times
		var b strings.Builder
		for i := 0; i < number(ra, 0, 1); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpRepeat: // matches Sub[0] at least Min times, at most Max (Max == -1 is no limit)
		var b strings.Builder
		count := 0
		re.Max = int(math.Min(float64(re.Max), float64(10)))
		if re.Max > re.Min {
			count = ra.Intn(re.Max - re.Min + 1)
		}
		for i := 0; i < re.Min || i < (re.Min+count); i++ {
			for _, rs := range re.Sub {
				b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
			}
		}
		return b.String()
	case syntax.OpConcat: // matches concatenation of Subs
		var b strings.Builder
		for _, rs := range re.Sub {
			b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
		}
		return b.String()
	case syntax.OpAlternate: // matches alternation of Subs
		return regexGenerate(ra, re.Sub[number(ra, 0, len(re.Sub)-1)], limit)
	}

	return ""
}

// Map will generate a random set of map data
func Map() map[string]interface{} { return mapFunc(globalFaker.Rand) }

// Map will generate a random set of map data
func (f *Faker) Map() map[string]interface{} { return mapFunc(f.Rand) }

func mapFunc(r *rand.Rand) map[string]interface{} {
	m := map[string]interface{}{}

	randWordType := func() string {
		s := randomString(r, []string{"lorem", "bs", "job", "name", "address"})
		switch s {
		case "bs":
			return bs(r)
		case "job":
			return jobTitle(r)
		case "name":
			return name(r)
		case "address":
			return street(r) + ", " + city(r) + ", " + state(r) + " " + zip(r)
		}
		return word(r)
	}

	randSlice := func() []string {
		var sl []string
		for ii := 0; ii < number(r, 3, 10); ii++ {
			sl = append(sl, word(r))
		}
		return sl
	}

	for i := 0; i < number(r, 3, 10); i++ {
		t := randomString(r, []string{"string", "int", "float", "slice", "map"})
		switch t {
		case "string":
			m[word(r)] = randWordType()
		case "int":
			m[word(r)] = number(r, 1, 10000000)
		case "float":
			m[word(r)] = float32Range(r, 1, 1000000)
		case "slice":
			m[word(r)] = randSlice()
		case "map":
			mm := map[string]interface{}{}
			tt := randomString(r, []string{"string", "int", "float", "slice"})
			switch tt {
			case "string":
				mm[word(r)] = randWordType()
			case "int":
				mm[word(r)] = number(r, 1, 10000000)
			case "float":
				mm[word(r)] = float32Range(r, 1, 1000000)
			case "slice":
				mm[word(r)] = randSlice()
			}
			m[word(r)] = mm
		}
	}

	return m
}

func addGenerateLookup() {
	AddFuncLookup("generate", Info{
		Display:     "Generate",
		Category:    "generate",
		Description: "Random string generated from string value based upon available data sets",
		Example:     "{firstname} {lastname} {email} - Markus Moen markusmoen@pagac.net",
		Output:      "string",
		Params: []Param{
			{Field: "str", Display: "String", Type: "string", Description: "String value to generate from"},
		},
		Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
			str, err := info.GetString(m, "str")
			if err != nil {
				return nil, err
			}

			// Limit the length of the string passed
			if len(str) > 1000 {
				return nil, errors.New("string length is too large. Limit to 1000 characters")
			}

			return generate(r, str), nil
		},
	})

	AddFuncLookup("regex", Info{
		Display:     "Regex",
		Category:    "generate",
		Description: "Random string generated from regex RE2 syntax string",
		Example:     "[abcdef]{5} - affec",
		Output:      "string",
		Params: []Param{
			{Field: "str", Display: "String", Type: "string", Description: "Regex RE2 syntax string"},
		},
		Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
			str, err := info.GetString(m, "str")
			if err != nil {
				return nil, err
			}

			// Limit the length of the string passed
			if len(str) > 500 {
				return nil, errors.New("string length is too large. Limit to 500 characters")
			}

			return regex(r, str), nil
		},
	})

	AddFuncLookup("map", Info{
		Display:     "Map",
		Category:    "generate",
		Description: "Random map of generated data",
		Example:     `map[consult:respond context:9285735]`,
		Output:      "map[string]interface{}",
		ContentType: "application/json",
		Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
			return mapFunc(r), nil
		},
	})
}
