package gofakeit

import (
	crand "crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"math"
	"math/rand"
	"reflect"
	"strings"
	"unicode"

	"github.com/brianvoe/gofakeit/v6/data"
)

const lowerStr = "abcdefghijklmnopqrstuvwxyz"

const upperStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

const numericStr = "0123456789"

const specialStr = "@#$%&?|!(){}<>=*+-_:;,."

const specialSafeStr = "@#$&?!-_*."

const spaceStr = " "

const allStr = lowerStr + upperStr + numericStr + specialStr + spaceStr

const vowels = "aeiou"

const hashtag = '#'

const questionmark = '?'

const dash = '-'

const base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

const minUint = 0

const maxUint = ^uint(0)

const minInt = -maxInt - 1

const maxInt = int(^uint(0) >> 1)

// Seed will set the global random value. Setting seed to 0 will use crypto/rand

func Seed(seed int64) {

	if seed == 0 {

		binary.Read(crand.Reader, binary.BigEndian, &seed)

		globalFaker.Rand.Seed(seed)

	} else {

		globalFaker.Rand.Seed(seed)

	}

}

// Check if in lib

func dataCheck(dataVal []string) bool {

	var checkOk bool

	if len(dataVal) == 2 {

		_, checkOk = data.Data[dataVal[0]]

		if checkOk {

			_, checkOk = data.Data[dataVal[0]][dataVal[1]]

		}

	}

	return checkOk

}

// Get Random Value

func getRandValue(r *rand.Rand, dataVal []string) string {

	if !dataCheck(dataVal) {

		return ""

	}

	return data.Data[dataVal[0]][dataVal[1]][r.Intn(len(data.Data[dataVal[0]][dataVal[1]]))]

}

// Replace # with numbers

func replaceWithNumbers(r *rand.Rand, str string) string {

	if str == "" {

		return str

	}

	bytestr := []byte(str)

	for i := 0; i < len(bytestr); i++ {

		if bytestr[i] == hashtag {

			bytestr[i] = byte(randDigit(r))

		}

	}

	if bytestr[0] == '0' {

		bytestr[0] = byte(r.Intn(8)+1) + '0'

	}

	return string(bytestr)

}

// Replace ? with ASCII lowercase letters

func replaceWithLetters(r *rand.Rand, str string) string {

	if str == "" {

		return str

	}

	bytestr := []byte(str)

	for i := 0; i < len(bytestr); i++ {

		if bytestr[i] == questionmark {

			bytestr[i] = byte(randLetter(r))

		}

	}

	return string(bytestr)

}

// Replace ? with ASCII lowercase letters between a and f

func replaceWithHexLetters(r *rand.Rand, str string) string {

	if str == "" {

		return str

	}

	bytestr := []byte(str)

	for i := 0; i < len(bytestr); i++ {

		if bytestr[i] == questionmark {

			bytestr[i] = byte(randHexLetter(r))

		}

	}

	return string(bytestr)

}

// Generate random lowercase ASCII letter

func randLetter(r *rand.Rand) rune {

	allLetters := upperStr + lowerStr

	return rune(allLetters[r.Intn(len(allLetters))])

}

func randCharacter(r *rand.Rand, s string) string {

	return string(s[r.Int63()%int64(len(s))])

}

// Generate random lowercase ASCII letter between a and f

func randHexLetter(r *rand.Rand) rune {

	return rune(byte(r.Intn(6)) + 'a')

}

// Generate random ASCII digit

func randDigit(r *rand.Rand) rune {

	return rune(byte(r.Intn(10)) + '0')

}

// Generate random integer between min and max

func randIntRange(r *rand.Rand, min, max int) int {

	// If they pass in the same number, just return that number

	if min == max {

		return min

	}

	// If they pass in a min that is bigger than max, swap them

	if min > max {

		ogmin := min

		min = max

		max = ogmin

	}

	// Figure out if the min/max numbers calculation

	// would cause a panic in the Int63() function.

	if max-min+1 > 0 {

		return min + int(r.Int63n(int64(max-min+1)))

	}

	// Loop through the range until we find a number that fits

	for {

		v := int(r.Uint64())

		if (v >= min) && (v <= max) {

			return v

		}

	}

}

// Generate random uint between min and max

func randUintRange(r *rand.Rand, min, max uint) uint {

	// If they pass in the same number, just return that number

	if min == max {

		return min

	}

	// If they pass in a min that is bigger than max, swap them

	if min > max {

		ogmin := min

		min = max

		max = ogmin

	}

	// Figure out if the min/max numbers calculation

	// would cause a panic in the Int63() function.

	if int(max)-int(min)+1 > 0 {

		return uint(r.Intn(int(max)-int(min)+1) + int(min))

	}

	// Loop through the range until we find a number that fits

	for {

		v := uint(r.Uint64())

		if (v >= min) && (v <= max) {

			return v

		}

	}

}

func toFixed(num float64, precision int) float64 {

	output := math.Pow(10, float64(precision))

	return float64(math.Floor(num*output)) / output

}

func equalSliceString(a, b []string) bool {

	sizeA, sizeB := len(a), len(b)

	if sizeA != sizeB {

		return false

	}

	for i, va := range a {

		vb := b[i]

		if va != vb {

			return false

		}

	}

	return true

}

func equalSliceInt(a, b []int) bool {

	sizeA, sizeB := len(a), len(b)

	if sizeA != sizeB {

		return false

	}

	for i, va := range a {

		vb := b[i]

		if va != vb {

			return false

		}

	}

	return true

}

func equalSliceInterface(a, b []any) bool {

	sizeA, sizeB := len(a), len(b)

	if sizeA != sizeB {

		return false

	}

	for i, va := range a {

		if !reflect.DeepEqual(va, b[i]) {

			return false

		}

	}

	return true

}

func stringInSlice(a string, list []string) bool {

	for _, b := range list {

		if b == a {

			return true

		}

	}

	return false

}

func anyToString(a any) string {

	if a == nil {

		return ""

	}

	// If it's a slice of bytes or struct, unmarshal it into an interface

	if bytes, ok := a.([]byte); ok {

		return string(bytes)

	}

	// If it's a struct, map, or slice, convert to JSON

	switch reflect.TypeOf(a).Kind() {

	case reflect.Struct, reflect.Map, reflect.Slice:

		b, err := json.Marshal(a)

		if err == nil {

			return string(b)

		}

	}

	return fmt.Sprintf("%v", a)

}

// Title returns a copy of the string s with all Unicode letters that begin words

// mapped to their Unicode title case

func title(s string) string {

	// isSeparator reports whether the rune could mark a word boundary

	isSeparator := func(r rune) bool {

		// ASCII alphanumerics and underscore are not separators

		if r <= 0x7F {

			switch {

			case '0' <= r && r <= '9':

				return false

			case 'a' <= r && r <= 'z':

				return false

			case 'A' <= r && r <= 'Z':

				return false

			case r == '_':

				return false

			}

			return true

		}

		// Letters and digits are not separators

		if unicode.IsLetter(r) || unicode.IsDigit(r) {

			return false

		}

		// Otherwise, all we can do for now is treat spaces as separators.

		return unicode.IsSpace(r)

	}

	prev := ' '

	return strings.Map(

		func(r rune) rune {

			if isSeparator(prev) {

				prev = r

				return unicode.ToTitle(r)

			}

			prev = r

			return r

		},

		s)

}

func funcLookupSplit(str string) []string {

	out := []string{}

	for str != "" {

		if strings.HasPrefix(str, "[") {

			startIndex := strings.Index(str, "[")

			endIndex := strings.Index(str, "]")

			val := str[(startIndex) : endIndex+1]

			out = append(out, strings.TrimSpace(val))

			str = strings.Replace(str, val, "", 1)

			// Trim off comma if it has it

			if strings.HasPrefix(str, ",") {

				str = strings.Replace(str, ",", "", 1)

			}

		} else {

			strSplit := strings.SplitN(str, ",", 2)

			strSplitLen := len(strSplit)

			if strSplitLen >= 1 {

				out = append(out, strings.TrimSpace(strSplit[0]))

			}

			if strSplitLen >= 2 {

				str = strSplit[1]

			} else {

				str = ""

			}

		}

	}

	return out

}

// Used for parsing the tag in a struct

func parseNameAndParamsFromTag(tag string) (string, string) {

	// Trim the curly on the beginning and end

	tag = strings.TrimLeft(tag, "{")

	tag = strings.TrimRight(tag, "}")

	// Check if has params separated by :

	fNameSplit := strings.SplitN(tag, ":", 2)

	fName := ""

	fParams := ""

	if len(fNameSplit) >= 1 {

		fName = fNameSplit[0]

	}

	if len(fNameSplit) >= 2 {

		fParams = fNameSplit[1]

	}

	return fName, fParams

}

// Used for parsing map params

func parseMapParams(info *Info, fParams string) *MapParams {

	// 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 {

		return mapParams

	} else {

		return nil

	}

}

// Used for splitting the values

func addSplitValsToMapParams(splitVals []string, info *Info, mapParams *MapParams) *MapParams {

	for ii := 0; ii < len(splitVals); ii++ {

		if len(info.Params)-1 >= ii {

			if strings.HasPrefix(splitVals[ii], "[") {

				lookupSplits := funcLookupSplit(strings.TrimRight(strings.TrimLeft(splitVals[ii], "["), "]"))

				for _, v := range lookupSplits {

					mapParams.Add(info.Params[ii].Field, v)

				}

			} else {

				mapParams.Add(info.Params[ii].Field, splitVals[ii])

			}

		}

	}

	return mapParams

}