package common

import (
	"crypto/hmac"
	"crypto/md5"
	crand "crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/gogf/gf/util/gconv"
	"github.com/syyongx/php2go"
	"github.com/tidwall/gjson"
	"golang_open_platform/pkg/config"
	"golang_open_platform/pkg/vars"
	"log"
	"math"
	"math/big"
	"math/rand"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"
)

/*
gjson 判断某值是否存在 map
@param json string 分析json字符串
@param arrk string  分析的json键值转成map
@param targetk string 目标ID
*/
func GInArray(json string, arrK string, targetK string) bool {
	s := gjson.Get(json, arrK).Array()
	d := make([]string, 0)
	for _, a := range s {
		c := a.String()
		d = append(d, c)
	}
	return php2go.InArray(targetK, d)
}

/*
sha1加密(tme目前在用)
@param string query 查询字符串
@param string pri_key 加密字符串
@return 返回加密后的字符串
*/
func Sha1(query string, pri_key string) string {
	key := []byte(pri_key)
	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(query))
	query = base64.StdEncoding.EncodeToString(mac.Sum(nil))
	return query
}

/**
 @author wangsong
 获取请求过来的参数,并转为字符串,作用:为了有时候需要记录请求参数,压入日志
 注意:
	1.如果请求的是json,接收是需要读取body,而gin大部分函数只能读取一次body,读第二次会读不到
      而此函数需要频繁调用,所以需要在调用此函数之前,使用过ShouldBindBodyWith 方法,不然会读取不到。
      具体需了解 ShouldBindBodyWith 源码 和 多次使用shouldbindjsond 的问题
    2.表单提交不需要读body,则正常使用
*/
func GetRequestParam(ctx *gin.Context) string {
	ct := ctx.Request.Header.Get("Content-Type")
	switch {
	case ct == "application/json":
		var BodyJson string
		if cb, ok := ctx.Get(gin.BodyBytesKey); ok {
			if cbb, ok := cb.([]byte); ok {
				BodyJson = string(cbb)
				return BodyJson
			}
		}
		return BodyJson
	default:

		ctx.Request.ParseMultipartForm(32 << 20)
		postForm := ctx.Request.Form
		formData, _ := json.Marshal(postForm)
		params := string(formData)
		return params

		/*ctx.Request.ParseForm()
		postForm := ctx.Request.PostForm
		formData, _ := json.Marshal(postForm)
		params := string(formData)
		return params*/
	}
}

/**
结构复制,从struct1 复制到 struct2,字段类型不一样,那么就不复制那个字段
@param Starus 说明
	1,如果struct1 字段值为零值就不覆盖struct2对应的字段值(默认),
	2,struct1字段 覆盖struct2 字段的值
	3,struct2 字段为零值,才让struct1 的字段赋值
*/

func CopyStruct(struct1 interface{}, struct2 interface{}, Status ...int) {

	statusType := 1 //statusType 默认参数
	for num, opt := range Status {
		if num > 0 { //只接受一个参数
			break
		}
		switch num {
		case 0:
			if opt == 2 {
				statusType = 2
			}
			break
		}
	}

	sval := reflect.ValueOf(struct1).Elem()
	dval := reflect.ValueOf(struct2).Elem()

	for i := 0; i < sval.NumField(); i++ {
		value := sval.Field(i)
		if statusType == 1 { //如果struct1 字段值为零值就不覆盖struct2对应的字段值
			if value.IsZero() {
				continue
			}
		}
		name := sval.Type().Field(i).Name
		dvalue := dval.FieldByName(name)

		if statusType == 3 { //struct2字段为零值,才让struct1 的字段赋值,不是零值就跳过此字段的赋值
			if dvalue.IsZero() == false {
				continue
			}
		}

		if(name=="LadderPrice"){
			if dvalue.Kind() != sval.Field(i).Kind() { //类型不一样不复制
				continue
			}
		}

		if dvalue.Kind() != sval.Field(i).Kind() { //类型不一样不复制
			continue
		}
		if dvalue.IsValid() == false { //dvalue.IsValid() 与  dvalue.IsZero() 不一样,如果返回fasle是无效的值
			continue
		}

		dvalue.Set(value) //这里默认共同成员的类型一样,否则这个地方可能导致 panic,需要简单修改一下。
	}
}

/**
@author wangsong
合并map,都有值以map1为主,字段类型以map1为主
*/
func MergeMap(map1 map[string]interface{}, map2 map[string]interface{}) map[string]interface{} {

	var newMap map[string]interface{}
	newMap = map1 //以map1为主,就先将map1给到newMap
	//先算mp1
	for key, _ := range map1 {
		if !reflect.ValueOf(map1[key]).IsZero() { //如果不为空,就以取map1的值

			s := reflect.ValueOf(map1[key])
			if s.Kind() == reflect.String { //string
				newMap[key] = gconv.String(map1[key])
			} else {
				newMap[key] = gconv.Int(map1[key]) //int
			}

			delete(map2, key) //删一个重复的map2的key

		} else { //没有数据,取mp2的值,类型要是map1字段的类型

			if _, ok := map2[key]; ok { //map1的key在map2是否存在,不存在就不处理

				if !reflect.ValueOf(map2[key]).IsZero() { //map2不能为0值才处理

					m1 := reflect.ValueOf(map1[key])
					if m1.Kind() == reflect.String { //string
						newMap[key] = gconv.String(map2[key])
					} else {
						newMap[key] = gconv.Int(map2[key]) //int
					}
				}
				delete(map2, key) //删一个重复的map2的key
			}
		}
	}
	//处理map2,重复的被删了,剩下的直接添加
	for key, value := range map2 {
		s := reflect.ValueOf(value)
		if s.Kind() == reflect.String { //string
			newMap[key] = gconv.String(value)
		} else {
			newMap[key] = gconv.Int(value) //int
		}
	}
	return newMap

}

/*
兼容表单提交,不适用嵌套数组表单提交
*/
func ShouldBind(ctx *gin.Context, obj interface{}) error{
	ct := ctx.Request.Header.Get("Content-Type")
	switch {
	case ct == "application/json":
		if err := ctx.ShouldBindBodyWith(obj,binding.JSON);err != nil {
			return err
		}
	default:
		if err := ctx.ShouldBind(obj);err != nil {
			return err
		}
	}
	return nil
}


/*
输出header
*/
func PrintDebugHeader(ctx *gin.Context) {
	if ctx.Request.FormValue("flag") == "101" {
		ctx.Header("Content-Type", "text/html; charset=utf-8")
	}
}

/*
格式化数据直接输出浏览器
@parm jsonStr 需要json输出的内容
*/
func PrintDebugHtml(ctx *gin.Context, jsonStr interface{}) {
	if ctx.Request.FormValue("flag") != "101" || jsonStr == "" {
		return
	}
	if v, p := jsonStr.(string); p {
		ctx.String(200, "</br></br>-----------"+v)
	} else {
		jsonData, err := json.Marshal(jsonStr)
		if err != nil {
			fmt.Println("错误:-----", err)
		}
		ctx.String(200, "</br></br>-----------"+string(jsonData))
	}
}



func PrintDebugString(msg string,arg ...interface{})  {
	if(config.IsOpenDebug()){
		logOut := log.New(os.Stdout, "", log.Lshortfile|log.Ldate|log.Ltime)
		if(len(arg)>0){
			logOut.Printf(msg,arg)
		}else{
			logOut.Printf(msg)
		}

	}
}
/**
打印文本到控制台
common.PrintStdout().Printf("新增到redis")
 */
func PrintStdout() *log.Logger {
	return log.New(os.Stdout, "", log.Lshortfile|log.Ldate|log.Ltime)
}

// Md5 md5()
func Md5(str string) string {
	hash := md5.New()
	hash.Write([]byte(str))
	return hex.EncodeToString(hash.Sum(nil))
}

//非精确匹配,字符串截取向下80%,5个字符以下不截,5-10个截一个,10-20个向下取80%,20个以上向下取70%
func SubKeyWordStr(str string) string {
	strLen := len(str)
	strLenFloat := float64(strLen)
	if strLen < 5 {
		return str
	}
	if strLen >= 5 && strLen <= 10 {
		str = php2go.Substr(str, 0, strLen-1)
	}
	if strLen > 10 && strLen <= 20 {
		num := php2go.Floor(strLenFloat * 0.2)
		str = php2go.Substr(str, 0, strLen-int(num))
	}
	if strLen > 20 {
		num := php2go.Floor(strLenFloat * 0.3)
		str = php2go.Substr(str, 0, strLen-int(num))
	}
	return str
}

//转成字符串的方法
func ToString(a interface{}) string {
	if v, p := a.(int); p {
		return strconv.Itoa(v)
	}
	if v, p := a.(int16); p {
		return strconv.Itoa(int(v))
	}
	if v, p := a.(int32); p {
		return strconv.Itoa(int(v))
	}
	if v, p := a.(uint); p {
		return strconv.Itoa(int(v))
	}
	if v, p := a.(float32); p {
		return strconv.FormatFloat(float64(v), 'f', -1, 32)
	}
	if v, p := a.(float64); p {
		return strconv.FormatFloat(v, 'f', -1, 32)
	}
	return "change to String error"
}

//替换字符串,不区分大小写
func CaseInsensitiveReplace(subject string, search string, replace string) string {
	searchRegex := regexp.MustCompile("(?i)" + search)
	return searchRegex.ReplaceAllString(subject, replace)
}

/*
 md5字符串
*/
func GetKey(s string) string {
	return php2go.Md5(strings.ToLower(s))
}

/**
 * 获取联营活动价
 */
func lyActivityPrice() {

}

/*构造SPU和SKU的ID
*生成19位纯数组id
 */
func CreateId(types string) string {
	var id, db string
	if types == "sku" {
		id = "1"
		db = strconv.Itoa(php2go.Rand(0, 9)) + strconv.Itoa(php2go.Rand(0, 9))
	} else {
		id = "2"
		db = "0" + strconv.Itoa(php2go.Rand(0, 9))
	}
	s := rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000)
	return id + strconv.FormatInt(int64(s), 10) + strconv.FormatInt(php2go.Time(), 10) + db
}

/*
 * 反爬虫用html标签替换数字,不包括“.”
 * number 数字串
 */
func NumberToHtml(number int) (html string) {
	numberToClassSlice := vars.NumberToClass
	if number != 0 {
		//数字转字符串
		numberStr := ToString(number)
		for i := 0; i < len(numberStr); i++ {
			var classHtml string
			numStr := php2go.Substr(numberStr, uint(i), 1)
			if php2go.IsNumeric(numStr) {
				num, _ := strconv.Atoi(numStr)
				index := php2go.Rand(0, 3)
				class := numberToClassSlice[num][index]
				otherClass := StrRandom(3)
				classHtml = `<font class="` + class + otherClass + `"></font>`
			}
			html = html + classHtml
			return
		}
	}
	return
}

/**
 * 生成纯小写字母的字符串
 * 返回形式如下 :  yuiopkdsi rnvewjeil xmiqplmza
 */
func StrRandom(lenNum int) string {
	randStr := "sdwpkxmiqplmzacbmeruwulurjlauejrifkfghjklzxcvbnmqwwertyuiopkdsieurnvewjeilweiskvnx"
	strLen := len(randStr) - 9
	var result string
	for i := 0; i < lenNum; i++ {
		start := php2go.Rand(0, strLen)
		str := php2go.Substr(randStr, uint(start), 9)
		result = result + " " + str
	}
	return result
}

//生成范围区间内的随机数
func Rand(min, max int) int {
	n, _ := crand.Int(crand.Reader, big.NewInt(int64(max+1)))
	return int(n.Int64()) + min
}

func CopyMapString(distmap map[string]string) map[string]string {
	tmpmap := make(map[string]string, 0)
	for k, v := range distmap {
		tmpmap[k] = v
	}
	return tmpmap
}

//反爬虫加密验证
func CheckSignApi(ctx *gin.Context) (resNo int) {
	params := make(map[string]string)
	if ctx == nil {
		return
	}
	referer := ctx.Request.Referer()
	request := make(map[string]string)
	ctx.MultipartForm()
	for name, value := range ctx.Request.Form {
		if value[0] != "" {
			request[name] = strings.TrimSpace(value[0])
		}
	}
	if request["hcy_test"] == "1122" {
		return 0
	}
	if request["no_rule"] == "1122" {
		return 0
	}

	if request["check_button"] == "2" || strings.Contains(ctx.Request.Referer(), "liexin.com") {
		return 0
	}

	//如果是内部验证通过,则内部通过
	if auth(ctx) {
		return 0
	}

	//验证必填参数
	if referer == "" {
		return 4
	}

	if ctx.Query("asdfghjkl") != "" {
		params = request
	}

	if ctx.PostForm("asdfghjkl") != "" {
		params = request
	}

	params["Yo4teW_gid"], _ = ctx.Cookie("Yo4teW_gid")

	if params["asdfghjkl"] == "" || params["Yo4teW_gid"] == "" || params["qwertyuiop"] == "" {
		return 1
	}
	var temp1 []string
	//根据参数,按照规则进行签名生成
	for k, v := range params {
		if k == "asdfghjkl" || k == "_" || k == "callback" {
			continue
		}
		if v == "" || v == "undefined" || v == "null" || v == "NULL" {
			continue
		}
		if len(v) <= 0 {
			continue
		}
		temp1 = append(temp1, k+"="+v)
	}

	SortSlice(temp1)
	temp2 := strings.Join(temp1, "")
	r := regexp.MustCompile(`[^0-9a-zA-Z]`)
	temp2 = r.ReplaceAllString(temp2, "")
	temp2 = strings.ToUpper(temp2)
	r = regexp.MustCompile(`[ABC]`)
	temp2 = r.ReplaceAllString(temp2, "")
	temp3 := php2go.Sha1(temp2)
	//'YO4TEW_GID%3DEFDDE3E06D15F887866E9D96DOM_RNK%3D1GOODS_NME%2FONDITION%3DLM358P%3D1PF%3D1TIME_LIEXIN%3D1545009990450';
	//验证签名
	if temp3 == params["asdfghjkl"] {
		return 0
	}
	return 3
}

//内部免验证通过
func auth(ctx *gin.Context) bool {
	if ctx == nil {
		return false
	}
	request := make(map[string]string)
	ctx.MultipartForm()
	for name, value := range ctx.Request.Form {
		if value[0] != "" {
			request[name] = strings.TrimSpace(value[0])
		}
	}
	k1 := request["k1"]
	k1Num, _ := strconv.Atoi(k1)
	k2 := request["k2"]
	key := config.Get("AUTH.SUPER_AUTH_KEY").String()
	if k1 != "" && int64(k1Num) < (time.Now().Unix()-600) {
		return false
	}
	//md5(md5($pwd).$salt);
	pwd := Md5(Md5(k1) + key)
	if k2 == pwd {
		return true
	}
	return false
}

func CreateAnyTypeSlice(slice interface{}) ([]interface{}, bool) {
	val, ok := isSlice(slice)

	if !ok {
		return nil, false
	}

	sliceLen := val.Len()

	out := make([]interface{}, sliceLen)

	for i := 0; i < sliceLen; i++ {
		out[i] = val.Index(i).Interface()
	}

	return out, true
}

// 判断是否为slcie数据
func isSlice(arg interface{}) (val reflect.Value, ok bool) {
	val = reflect.ValueOf(arg)

	if val.Kind() == reflect.Slice {
		ok = true
	}

	return
}

//向左补充字符串
func StrPadLeft(input string, padLength int, padString string) string {
	output := padString

	for padLength > len(output) {
		output += output
	}

	if len(input) >= padLength {
		return input
	}

	return output[:padLength-len(input)] + input
}

//移除字符串切片中某個元素
func RemoveSliceString(s []string, i string) []string {
	b := make([]string, 0)
	for _, v := range s {
		if v != i {
			b = append(b, v)
		}

	}
	return b
}

//map排序
func MapSort(mapList map[int]int) []int {
	var (
		keys    []int
		newList []int
	)

	newList = make([]int, 0)

	for _, v := range mapList {
		keys = append(keys, v)
	}
	sort.Ints(keys)
	for _, k := range keys {
		newList = append(newList, mapList[k])
	}
	return newList
}

////////////类型转换/////////////////////
func MyInt(str string) int {
	res, _ := strconv.ParseInt(str, 10, 64)
	return int(res)
}
func MyInt8(str string) int8 {
	res, _ := strconv.ParseInt(str, 10, 64)
	return int8(res)
}
func MyInt16(str string) int16 {
	res, _ := strconv.ParseInt(str, 10, 64)
	return int16(res)
}
func MyInt64(str string) int64 {
	res, _ := strconv.ParseInt(str, 10, 64)
	return int64(res)
}
func MyFloat32(str string) float32 {
	res, _ := strconv.ParseFloat(str, 64)
	return float32(res)
}
func MyFloat64(str string) float64 {
	res, _ := strconv.ParseFloat(str, 64)
	return float64(res)
}
func MyFloat64ToStr(numb float64) string {
	return strconv.FormatFloat(numb, 'f', 6, 64)
}
func MyInt64ToStr(numb int64) string {
	return strconv.FormatInt(numb, 10)
}
func MyIntToStr(i int) string {
	return strconv.Itoa(i)
}

/*
 四舍五入取多少位
@param float64 x 目标分析字符串
@param int wei   取小数多少位
*/
func MyRound(x float64, wei int) float64 {
	if wei == 0 {
		return math.Floor(x + 0.5)
	}
	weis := map[int]float64{
		1: 10,
		2: 100,
		3: 1000,
		4: 10000,
		5: 100000,
		6: 1000000,
	}
	weishu := weis[wei]
	a := math.Floor(x*weishu+0.5) / weishu
	return a
}

/*
@author wangsong
保留小数点几位(四舍五入)
@param float64 val 目标分析字符串
@param int precision   取小数多少位
*/
func Round(val float64, precision int) (valueFloat64 float64) {
	//strconv.ParseFloat(fmt.Sprintf("%.2f", 12.0), 64) //两位
	format:="%."+strconv.Itoa(precision)+"f"
	valueFloat64,err:= strconv.ParseFloat(fmt.Sprintf(format, val), 64)
	if(err!=nil){
		panic("round err"+err.Error())
	}
	return
}

/**
判断是否包含中文
 */
func IsChineseChar(str string) bool {
	for _, r := range str {
		if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
			return true
		}
	}
	return false
}


////////////类型转换/////////////////////