/*
=================================================================================
* License: GPL-2.0 license
* Author: 众产®  https://ciy.cn/code
* Version: 0.1.0
=================================================================================
命名规范。引入默认包
NewCiy*		类库实例
Ciy*		私有类库struct
Ciy*inf		接口定义
Ciy*		全局变量
CIY*		全局常量
=================================================================================
Log		日志全局变量
=================================================================================

 * 功能函数相关
 * Sleep		延时函数。秒为单位，支持0.5秒
 * Pr			带时间的打印函数
 * Iff			三元运算符

 * Is_numeric	是否数字
 * Is_int		是否整数
 * Tostr		转字符串
 * Toint		转int
 * Tofloat		转float64
 * Tobool		转bool
 * Tostamp		转时间戳。将Y-m-d H:i:s格式转时间戳，同时识别字符串时间戳
 * Todate		时间格式化。传递0为当前时间。支持Y-m-d H:i:s。支持1970以前

 * Locinzone    		当前经纬度是否在围栏中
 * Timems               获取当前微秒数
 * Ismobile             判断手机号是否合法
 x Isweixin             判断客户端是否在微信中	post.go
 * Idcard               判断身份证号合法
 * Iduscc               判断统一社会信用代码合法

 * 加解密相关
 * CiySign							最多8位签名
 * Encrypt						 	字符串加解密
 * EnID/DeID                        ID数字加解密
 * Conv33_10/Conv10_33              数字转33进制
 * MD5/MD5Byte

 * 字符串/数组相关
 * Uniqid							生成唯一ID
 * Array_unshift					数组顶部插入数据
 * In_array                       	某元素在数组中存在位置
 * Array_rand                       随机抽取数组元素并删除
 * Startwith/Endwith				首字符/尾字符匹配，可用strings.HasPrefix / strings.HasSuffix替代

 * Strpos_first                     字符数组优先匹配，一般匹配 ' "
 * Gb_substr/Gb_strlen/Gb_haschinese	中文字符串处理函数
 * Getstrparam/Setstrparam	 		比json还简化的数据保存方式，一般用于数据字典保存。例: name=AAA|age=12|cc=CCTT

 * 数据库相关
 * FieldAdd							手动增加列表显示字段，并返回排序字段字符串
 * Getrelation                      获取表内关联数据，用于大数据量表的所属ID转文字
 * Tran_delcheck/Tran_delall/me     事务删除前确认/删除相关数据
 * Getconfig/Setconfig              从配置表中读写配置项

 * 字典相关
 * Getcatas/Getsaascatas            从字典库/SaaS字典中读取 代码数组
 * Id2map                           将 id/name代码数组对 转换为 键值对
 * Ccode                            代码数组中，通过代码值找代码名
 * Mcode                            代码数组中，通过代码值找代码名，多级
 * Scode                            代码数组中，查找多个代码值，返回数组
 * Dcode                            代码数组中，通过代码名找代码值

 * LOG相关
 * SaveLog							log信息记录到数据库log表
 * SaveLogDB						增删改数据变更记录到数据库log表
 x SaveLogFile						保存log信息写入到本地文件   log.go

 * 文件相关
 * DirMake/FileDel/Copy/Save/Load	目录创建，多级目录创建。默认权限666 创建多层新文件夹/文件静默删除/拷贝/保存/读取
 * DirExist/FileExist				目录/文件是否存在
 * Saaspath                         获取SaaS多租户文件存储路径
 * fileext                          获取文件扩展名，如jpg
*/

package zciyon

import (
	"bufio"
	"bytes"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"math"
	"math/rand"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"
	"unicode"
)

const (
	CIYRUN_DEV  = 0
	CIYRUN_PROD = 2
)

type Ciy_Vars struct {
	Version       string
	Ini           CiyINI
	Func_succ     int
	Func_fail     int
	Func_commit   int
	Func_rollback int
	Func_runms    int
}

var Log *CiyLog            //日志
var CiyVars *Ciy_Vars      //应用全局变量
var CiyRunMode int         //运行模式: 0 dev/1 test/2 prod
var CiyWebDir string       //当前目录，尾缀不包含/
var CiyDirSep string       //操作系统目录分隔符
var CiyDB *CiyMysql        //单一数据库服务器或写库服务器连接池
var CiyRouteDB *CiyDBRoute //多台同一镜像读库服务器
// 对于单一数据库的多个微业务线，建议用map数据仓管理  如: c.DB["ag"].Get  或   CiyDBxxx
// 对于海量分库业务线，建议封装分布策略，聚合管理读写库

func init() {
	CiyRunMode = CIYRUN_DEV
	CiyVars = &Ciy_Vars{}
	CiyVars.Version = ""
	CiyVars.Func_succ = 0
	CiyVars.Func_fail = 0
	CiyVars.Func_commit = 0
	CiyVars.Func_rollback = 0
	CiyVars.Func_runms = 0
	CiyDirSep = string(os.PathSeparator)
	dir, err := os.Getwd()
	if err == nil {
		DirMake(dir + "/log")
		DirMake(dir + "/web/ud/tmp")
		CiyWebDir = strings.Replace(dir, CiyDirSep, "/", -1) + "/web"
	}
}
func Sleep(s float64) {
	if s < 1 {
		time.Sleep(time.Duration(s*1000) * time.Millisecond)
	} else {
		if CiyRunMode == CIYRUN_DEV {
			for i := 0; i < int(s); i++ {
				time.Sleep(time.Duration(1000) * time.Millisecond)
			}
		} else {
			time.Sleep(time.Duration(s*1000) * time.Millisecond)
		}
	}
}
func Clog(format any, args ...any) {
	//https://github.com/go-ffmt/ffmt
	now := time.Now()
	millis := (now.UnixNano() / int64(time.Millisecond)) % 1000
	fmt.Print(Todate(-1), ".", fmt.Sprintf("%03d", millis), ": ")
	if fmstr, ok := format.(string); ok {
		if strings.Contains(fmstr, "%") {
			fmt.Printf(fmstr, args...)
			fmt.Println("")
			return
		}
	}
	args = Array_unshift(args, format)
	fmt.Println(args...)
	// for _, arg := range args {
	// 	typ := fmt.Sprintf("%T", arg)
	// 	if typ == "uint8" {
	// 		typ = "byte"
	// 	}
	// 	fmt.Printf(typ + ": ")
	// 	switch {
	// 	case typ == "string":
	// 		fmt.Println(arg)
	// 	case typ == "byte":
	// 		fmt.Println(arg, fmt.Sprintf("%c", arg))
	// 	// case int, int64, int32, int16, int8, uint, uint64, uint32, uint16:
	// 	// 	fmt.Println(arg)
	// 	// case float32, float64:
	// 	// 	fmt.Println("["+fmt.Sprintf("%T", arg)+"]:", arg, fmt.Sprintf("%f", arg))
	// 	// case complex64, complex128:
	// 	// 	fmt.Println("["+fmt.Sprintf("%T", arg)+"]:", arg)
	// 	// case bool:
	// 	// 	fmt.Println("[bool]:", arg)
	// 	case strings.HasPrefix(typ, "[]"):
	// 		printarr(reflect.ValueOf(arg), 1)
	// 	case strings.HasPrefix(typ, "map"):
	// 		printmap(reflect.ValueOf(arg), 1)
	// 	default:
	// 		fmt.Println(arg)
	// 	}
	// }
}
func Iff[T int | float32 | float64 | string](b bool, t, f T) T {
	if b {
		return t
	}
	return f
}
func Is_numeric(a any) bool {
	switch v := a.(type) {
	case float32, float64, int, uint, int64, int32, int16, int8, uint64, uint32, uint16, uint8:
		return true
	default:
		floatRegex := regexp.MustCompile(`^[-+]?(\d+(\.\d*)?|\.\d+)$`)
		return floatRegex.MatchString(Tostr(v))
	}
}
func Is_int(a any) bool {
	switch v := a.(type) {
	case float32, float64, int, uint, int64, int32, int16, int8, uint64, uint32, uint16, uint8:
		return true
	default:
		integerRegex := regexp.MustCompile(`^-?[0-9]+$`)
		return integerRegex.MatchString(Tostr(v))
	}
}
func Randstr(nun int, argletter ...string) string {
	letter := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
	if len(argletter) > 0 {
		letter = argletter[0]
	}
	b := make([]byte, 10) // 创建一个长度为10的字节切片
	for i := range b {
		b[i] = letter[rand.Intn(len(letter))] // 随机生成字符
	}
	return string(b)
}
func Tostr(dat any, argdef ...string) (ret string) {
	ret = ""
	defer func() {
		if r := recover(); r != nil {
			Clog("Tostr panic:", r)
			if len(argdef) > 0 {
				ret = argdef[0]
			}
		}
	}()
	switch v := dat.(type) {
	case int:
		return strconv.Itoa(v)
	case uint:
		return strconv.Itoa(int(v))
	case int64:
		return strconv.Itoa(int(v))
	case int32:
		return strconv.Itoa(int(v))
	case int16:
		return strconv.Itoa(int(v))
	case int8:
		return strconv.Itoa(int(v))
	case uint64:
		return strconv.Itoa(int(v))
	case uint32:
		return strconv.Itoa(int(v))
	case uint16:
		return strconv.Itoa(int(v))
	case uint8:
		return strconv.Itoa(int(v))
	case []byte:
		return string(v)
	case string:
		return v
	case float32, float64:
		s := fmt.Sprintf("%.4f", v)
		s = strings.TrimRight(s, "0")
		s = strings.TrimRight(s, ".")
		return s
	case bool:
		if v {
			return "true"
		} else {
			return "false"
		}
	default:
		if len(argdef) > 0 {
			ret = argdef[0]
		}
	}
	return ret
}
func Toint(dat any, argdef ...int) (ret int) {
	ret = 0
	defer func() {
		if r := recover(); r != nil {
			Clog("Toint panic:", r)
			if len(argdef) > 0 {
				ret = argdef[0]
			}
		}
	}()
	switch v := dat.(type) {
	case int:
		return v
	case uint:
		return int(v)
	case int64:
		return int(v)
	case int32:
		return int(v)
	case int16:
		return int(v)
	case int8:
		return int(v)
	case uint64:
		return int(v)
	case uint32:
		return int(v)
	case uint16:
		return int(v)
	case uint8:
		return int(v)
	case float32:
		return int(v)
	case float64:
		return int(v)
	case []byte:
		num, err := strconv.Atoi(strings.TrimSpace(string(v)))
		if err == nil {
			return num
		} else {
			num, err := strconv.ParseFloat(strings.TrimSpace(string(v)), 64)
			if err == nil {
				return int(num)
			}
		}
	case string:
		num, err := strconv.Atoi(strings.TrimSpace(v))
		if err == nil {
			return num
		} else {
			num, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
			if err == nil {
				return int(num)
			}
		}
	case bool:
		if v {
			return 1
		} else {
			return 0
		}
	default:
		if len(argdef) > 0 {
			ret = argdef[0]
		}
	}
	return ret
}
func Tofloat(dat any, argdef ...float64) (ret float64) {
	ret = 0.0
	defer func() {
		if r := recover(); r != nil {
			Clog("Tofloat panic:", r)
			if len(argdef) > 0 {
				ret = argdef[0]
			}
		}
	}()
	switch v := dat.(type) {
	case float64:
		return v
	case int:
		return float64(v)
	case uint:
		return float64(v)
	case int64:
		return float64(v)
	case int32:
		return float64(v)
	case int16:
		return float64(v)
	case int8:
		return float64(v)
	case uint64:
		return float64(v)
	case uint32:
		return float64(v)
	case uint16:
		return float64(v)
	case uint8:
		return float64(v)
	case float32:
		return float64(v)
	case []byte:
		num, err := strconv.ParseFloat(string(v), 64)
		if err == nil {
			return num
		}
	case string:
		num, err := strconv.ParseFloat(v, 64)
		if err == nil {
			return num
		}
	case bool:
		if v {
			return 1.0
		} else {
			return 0.0
		}
	default:
		if len(argdef) > 0 {
			ret = argdef[0]
		}
	}
	return ret
}
func Tobool(dat any, argdef ...bool) (ret bool) {
	ret = false
	defer func() {
		if r := recover(); r != nil {
			Clog("Tobool panic:", r)
			if len(argdef) > 0 {
				ret = argdef[0]
			}
		}
	}()
	switch v := dat.(type) {
	case int, uint, uint64, uint32, uint16, uint8, int64, int32, int16, int8:
		return v != 0
	case float32:
		return v > 0.0
	case float64:
		return v > 0.0
	case []byte:
		str := strings.TrimSpace(strings.ToLower(string(v)))
		return str == "true" || str != "0"
	case string:
		str := strings.TrimSpace(strings.ToLower(v))
		return str == "true" || str != "0"
	case bool:
		return v
	default:
		if len(argdef) > 0 {
			ret = argdef[0]
		}
	}
	return ret
}
func Tostamp(argstr ...string) int {
	str := "now"
	if len(argstr) > 0 {
		str = argstr[0]
	}
	if str == "now" {
		timestamp := time.Now().Unix()
		return int(timestamp)
	}
	str = strings.Replace(str, "/", "-", -1)
	if strings.Contains(str, "-") {
		var ind, year, hour, minute, second int
		var month, day = 1, 1
		cnt := strings.Count(str, "-")
		if cnt >= 2 {
			ind = strings.Index(str, "-")
			if ind > -1 {
				year = Toint(str[:ind])
				str = strings.TrimSpace(str[ind+1:])
			}
		} else {
			//判读是否4位
			ind = strings.Index(str, "-")
			if ind > 3 {
				year = Toint(str[:ind])
				str = strings.TrimSpace(str[ind+1:])
			} else {
				year = time.Now().Year()
			}
		}
		ind = strings.Index(str, "-")
		if ind == -1 {
			month = Toint(str)
		} else {
			month = Toint(str[:ind])
			str = strings.TrimSpace(str[ind+1:])
			ind = strings.Index(str, " ")
			if ind == -1 {
				day = Toint(str)
			} else {
				day = Toint(str[:ind])
				str = strings.TrimSpace(str[ind+1:])
				ind = strings.Index(str, ":")
				if ind == -1 {
					hour = Toint(str)
				} else {
					hour = Toint(str[:ind])
					str = strings.TrimSpace(str[ind+1:])
					ind = strings.Index(str, ":")
					if ind == -1 {
						minute = Toint(str)
					} else {
						minute = Toint(str[:ind])
						str = strings.TrimSpace(str[ind+1:])
						second = Toint(str)
					}
				}
			}
		}
		t := time.Date(year, time.Month(month), day, hour, minute, second, 0, time.Local)
		timestamp := t.Unix()
		return int(timestamp)
	}
	return 0
}
func Todate(tim int, format ...string) string {
	var t time.Time
	if tim == -1 {
		t = time.Now()
	} else if tim == 0 {
		return ""
	} else {
		t = time.Unix(int64(tim), 0)
	}
	str := "Y-m-d H:i:s"
	if len(format) > 0 {
		str = format[0]
	}
	str = strings.Replace(str, "Y", fmt.Sprintf("%04d", t.Year()), -1)
	str = strings.Replace(str, "y", fmt.Sprintf("%02d", t.Year()%100), -1)
	str = strings.Replace(str, "m", fmt.Sprintf("%02d", t.Month()), -1)
	str = strings.Replace(str, "d", fmt.Sprintf("%02d", t.Day()), -1)
	str = strings.Replace(str, "H", fmt.Sprintf("%02d", t.Hour()), -1)
	str = strings.Replace(str, "i", fmt.Sprintf("%02d", t.Minute()), -1)
	str = strings.Replace(str, "s", fmt.Sprintf("%02d", t.Second()), -1)
	return str
}
func Locinzone(paths string, zlat float64, zlng float64) bool {
	//30.434272 120.274938,30.433977 120.277270,30.432403 120.277957,30.431764 120.276340,30.432270 120.273358,30.433997 120.273283
	fences := strings.Split(paths, ",")
	nvert := len(fences)
	vertx := make([]float64, nvert)
	verty := make([]float64, nvert)
	for i, r := range fences {
		latlng := strings.Split(r, " ")
		vertx[i] = Tofloat(latlng[0])
		verty[i] = Tofloat(latlng[1])
	}
	i, j := 0, nvert-1
	c := false
	for i < nvert {
		if ((verty[i] > zlng) != (verty[j] > zlng)) &&
			(zlat < (vertx[j]-vertx[i])*(zlng-verty[i])/(verty[j]-verty[i])+vertx[i]) {
			c = !c
		}
		j = i
		i++
	}
	return c
}
func RunCmd(name string, arg ...string) []string {
	cmd := exec.Command(name, arg...)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		fmt.Println("Error executing ps command:", err)
		return make([]string, 0)
	}
	return strings.Split(out.String(), "\n")
}
func Timems() int {
	return Toint(time.Now().UnixNano() / 1000000)
}
func Ismobile(mob string) bool {
	match, _ := regexp.MatchString("^1\\d{10}$", mob)
	return match
}
func Idcard(str string) (map[string]string, error) {
	str = strings.ToLower(strings.TrimSpace(str))
	if str == "" {
		return nil, fmt.Errorf("身份证未输入")
	}
	if len(str) != 18 {
		return nil, fmt.Errorf("身份证长度错误")
	}
	province := []string{"11", "12", "13", "14", "15", "21", "22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42", "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71", "81", "82", "91"}
	if !strings.Contains(strings.Join(province, ""), str[:2]) {
		return nil, fmt.Errorf("身份证地区不合法")
	}
	birthyear := str[6:10]
	birthmonth := str[10:12]
	birthday := str[12:14]
	birth := birthyear + "-" + birthmonth + "-" + birthday
	stamp := Tostamp(birth)
	if Todate(stamp, "Y-m-d") != birth {
		return nil, fmt.Errorf("身份证生日错误")
	}
	if stamp > Tostamp() {
		return nil, fmt.Errorf("身份证生日不能超过今天")
	}
	ahead17_char := str[:17]
	last_char := str[17:]
	factor := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
	c := []string{"1", "0", "x", "9", "8", "7", "6", "5", "4", "3", "2"}
	t_res := 0
	for i := 0; i < 17; i++ {
		t_res += int(ahead17_char[i]) * factor[i]
	}
	calc_last_char := c[t_res%11]
	if last_char != calc_last_char {
		return nil, fmt.Errorf("身份证校验位错误")
	}
	sex := Toint(str[16:17])
	if sex%2 == 0 {
		sex = 2
	} else {
		sex = 1
	}
	return map[string]string{"code": str, "birth": birth, "sex": Tostr(sex), "area": str[:6]}, nil
}
func Iduscc(uscc string) (map[string]string, error) {
	uscc = strings.ToUpper(strings.TrimSpace(uscc))
	if uscc == "" {
		return nil, fmt.Errorf("统一社会信用代码未输入")
	}
	if len(uscc) != 18 {
		return nil, fmt.Errorf("统一社会信用代码不是18位")
	}
	province := []string{"11", "12", "13", "14", "15", "21", "22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42", "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71", "81", "82", "91"}
	if In_array(province, uscc[2:4]) == -1 {
		return nil, fmt.Errorf("统一社会信用代码地区不合法")
	}
	lcrc := _tsucc(uscc[:17])
	if Tostr(uscc[17]) != lcrc {
		return nil, fmt.Errorf("统一社会信用代码校验位错误")
	}
	return map[string]string{"code": uscc, "area": uscc[2:8], "pn": uscc[:2]}, nil
}
func _tsucc(usc string) string {
	list := map[string]int{"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "G": 16, "H": 17, "J": 18, "K": 19, "L": 20, "M": 21, "N": 22, "P": 23, "Q": 24, "R": 25, "T": 26, "U": 27, "W": 28, "X": 29, "Y": 30}
	listf := map[int]string{}
	for k, v := range list {
		listf[v] = k
	}
	wi := []int{1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}
	num := 0
	for i := 0; i < 17; i++ {
		num += list[usc[i:i]] * wi[i]
	}
	result := 0
	switch num % 31 {
	case 0:
		result = 0
	default:
		result = 31 - num%31
	}
	return listf[result]
}

func CiySign(str string, key string) string {
	h := hmac.New(sha256.New, []byte(key))
	h.Write([]byte(str))
	signature := h.Sum(nil)
	sid := fmt.Sprintf("%x", signature)
	sid = sid[:16]
	sid = strings.ReplaceAll(sid, "d", "p")
	sid = strings.ReplaceAll(sid, "e", "z")
	sid = strings.ReplaceAll(sid, "f", "m")
	return sid
}

/* * *******************************************************************
 * 函数名称:encrypt
 * 函数作用:加密解密字符串
 * 安全函数，建议有能力自行修改一些，源码不泄露，很难猜算法。
 * 使用方法:
 * 加密     :encrypt('str','E','nowamagic');
 * 解密     :encrypt('被加密过的字符串','D','nowamagic');
 * 参数说明:
 * $string   :需要加密解密的字符串
 * $operation:判断是加密还是解密:E:加密   D:解密
 * $key      :加密的钥匙(密匙);
 * ******************************************************************* */
// authstr := "加密字串"
// enstr := Encrypt(authstr, "E", p.Project_Commonkey)
// destr := Encrypt(enstr, "D", p.Project_Commonkey)
// Clog(authstr == destr, authstr, enstr, destr)
func Encrypt(str string, operation string, argkey ...string) string {
	key := ""
	if len(argkey) > 0 {
		key = argkey[0]
	}
	keybyt := MD5Byte([]byte(key))
	strbyt := []byte(str)
	key_length := len(keybyt)
	var byts []byte
	if operation == "D" {
		var err error
		byts, err = base64.StdEncoding.DecodeString(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(str, "*", "="), "-", "+"), "_", "/")[1:])
		if err != nil {
			return ""
		}
	} else {
		mdbyt := MD5Byte(strbyt, keybyt)
		var buffer2 bytes.Buffer
		buffer2.Write(mdbyt[:8])
		buffer2.Write(strbyt)
		byts = buffer2.Bytes()
	}
	string_length := len(byts)
	rndkey := make([]byte, 256)
	box := make([]byte, 256)
	result := make([]byte, 0)
	for i := 0; i < 256; i++ {
		rndkey[i] = keybyt[i%key_length]
		box[i] = uint8(i)
	}
	for j, i := 0, 0; i < 256; i++ {
		j = int(j+int(box[i])+int(rndkey[i])) % 256
		tmp := box[i]
		box[i] = box[j]
		box[j] = tmp
	}
	for a, j, i := 0, 0, 0; i < string_length; i++ {
		a = (a + 1) % 256
		j = (j + int(box[a])) % 256
		tmp := box[a]
		box[a] = box[j]
		box[j] = tmp
		result = append(result, byts[i]^box[(int(box[a])+int(box[j]))%256])
	}
	if operation == "D" {
		if bytes.Equal(result[:8], MD5Byte(result[8:], keybyt)[:8]) {
			return string(result[8:])
		} else {
			return ""
		}
	} else {
		return "1" + strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(base64.StdEncoding.EncodeToString(result), "=", "*"), "+", "-"), "/", "_")
	}
}

/* * *******************************************************************
 * 函数名称:enid/deid
 * 函数作用:id加密函数/id解密函数。
 * 安全函数，建议有能力自行修改一些
 * 参数说明:
 * $id    两位数的数字id（id>=10）
 * 返回值：加密后的数字
 *   例：enid(8245) = 872435
 *   例：deid(872435) = 8245
 * ******************************************************************* */
func EnID(id int, argkey ...int) string {
	key := 253
	if len(argkey) > 0 {
		key = argkey[0]
	}
	strid := ""
	if id <= 0 {
		return "0"
	}
	if id < 10 {
		strid = "0" + Tostr(id)
	} else {
		strid = Tostr(id)
	}
	fx := _calnumber(id, key)
	ulen := len(strid)
	return strid[:1] + fx[0:1] + strid[1:ulen-1] + fx[1:] + strid[len(strid)-1:]
}
func DeID(strid string, argkey ...int) int {
	key := 253
	if len(argkey) > 0 {
		key = argkey[0]
	}
	ulen := len(strid)
	fx := strid[1:2] + strid[ulen-2:ulen-1]
	id := Toint(strid[:1] + strid[2:ulen-2] + strid[ulen-1:])
	if fx == _calnumber(id, key) {
		return id
	}
	return 0
}

/* * *******************************************************************
 * 函数名称:calnumber
 * 函数作用:换算id的加密校验数，被enid、deid函数调用。
 * 参数说明:
 * $key    默认加密因子，可以自行修改。尽量用奇数100-1000
 * $len    返回校验数位数。
 * 返回值：len位数字字符串
 *   例：calnumber(8245,224,2) = 73
 * ******************************************************************* */
func _calnumber(num, key int) string {
	len := 2
	if num > 250600 {
		num %= 250600
	}
	n := num % 8566
	if n < 100 {
		n += 100
	}
	xx := num*n + key
	if xx < 0 {
		xx = -xx
	}
	if xx%13 > 8 {
		xx += key
	}
	if xx%13 > 4 {
		xx += key + key + key
	}
	ret := xx % Toint(math.Pow(10, Tofloat(len)))
	if ret < 0 {
		ret = -ret
	}
	return fmt.Sprintf("%0"+Tostr(len)+"d", ret)
}
func Conv33_10(num string) int {
	n := 33
	var new_num float64
	new_num = 0.0
	nNum := len(strings.Split(num, "")) - 1
	for _, value := range strings.Split(num, "") {
		tmp := -1
		var nummap map[int]string = map[int]string{0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h", 8: "i", 9: "j", 10: "k", 11: "m", 12: "n", 13: "p", 14: "q", 15: "r", 16: "s", 17: "t", 18: "u", 19: "v", 20: "w", 21: "x", 22: "y", 23: "z", 24: "1", 25: "2", 26: "3", 27: "4", 28: "5", 29: "6", 30: "7", 31: "8", 32: "9"}
		for k, v := range nummap {
			if value == v {
				tmp = k
			}
		}
		if tmp != -1 {
			new_num = new_num + float64(tmp)*math.Pow(float64(n), float64(nNum))
			nNum = nNum - 1
		} else {
			break
		}
	}
	return int(new_num)
}
func Conv10_33(num int) string {
	var nummap map[int]string = map[int]string{0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h", 8: "i", 9: "j", 10: "k", 11: "m", 12: "n", 13: "p", 14: "q", 15: "r", 16: "s", 17: "t", 18: "u", 19: "v", 20: "w", 21: "x", 22: "y", 23: "z", 24: "1", 25: "2", 26: "3", 27: "4", 28: "5", 29: "6", 30: "7", 31: "8", 32: "9"}
	n := 33
	new_num_str := ""
	var remainder int
	var remainder_string string
	for num != 0 {
		remainder = num % n
		if 76 > remainder && remainder > 9 {
			remainder_string = nummap[remainder]
		} else {
			remainder_string = strconv.Itoa(remainder)
		}
		new_num_str = remainder_string + new_num_str
		num = num / n
	}
	return new_num_str
}
func MD5file(file string) string {
	f, err := os.Open(file)
	if err != nil {
		return ""
	}
	defer f.Close()
	h := md5.New()
	if _, err := io.Copy(h, f); err != nil {
		return ""
	}
	return hex.EncodeToString(h.Sum(nil))
}
func MD5(src string) string {
	h := md5.New()
	h.Write([]byte(src))
	return hex.EncodeToString(h.Sum(nil))
}
func MD5Byte(src ...[]byte) []byte {
	h := md5.New()
	for _, byt := range src {
		h.Write(byt)
	}
	return h.Sum(nil)
}
func Sha256(src string) string {
	hash := sha256.Sum256([]byte(src))
	return hex.EncodeToString(hash[:])
}
func Sha512(src string) string {
	hash := sha512.Sum512([]byte(src))
	return hex.EncodeToString(hash[:])
}
func Uniqid(length int) string {
	numbers := make([]byte, length)
	for i := range numbers {
		numbers[i] = byte(rand.Intn(256))
	}
	return fmt.Sprintf("%x", numbers)
}
func Array_unshift[T string | any](arr []T, value T, arginpoint ...int) []T {
	if arr == nil {
		return []T{value}
	}
	size := len(arr)
	newArr := make([]T, size+1)
	inpoint := 0
	if len(arginpoint) > 0 {
		inpoint = arginpoint[0]
	}
	for i := 0; i < inpoint; i++ {
		newArr[i] = arr[i]
	}
	newArr[inpoint] = value
	for i := inpoint; i < size; i++ {
		newArr[i+1] = arr[i]
	}
	return newArr
}
func In_array[T string | int | float64](arr []T, val T) int {
	for i, v := range arr {
		if v == val {
			return i
		}
	}
	return -1
}
func Array_rand[T string | int | float64 | any](arr []T, num int) []T {
	retarr := make([]T, num)
	if len(arr) < num {
		num = len(arr)
	}
	for i := 0; i < num; i++ {
		ind := rand.Intn(len(arr))
		retarr[i] = arr[ind]
		arr = append(arr[:ind], arr[ind+1:]...)
	}
	return retarr
}
func Startwith(str string, suf string) bool {
	return strings.HasPrefix(str, suf)
}
func Endwith(str string, suf string) bool {
	return strings.HasSuffix(str, suf)
}
func Strpos_first(search string, offset int, findstrs ...string) (int, string, string) {
	ind := strings.IndexAny(search[offset:], strings.Join(findstrs, ""))
	if ind == -1 {
		return -1, "", ""
	}
	ind += offset
	f_str := search[ind : ind+1]
	ind2 := strings.Index(search[ind+1:], f_str)
	if ind2 > -1 {
		ind2 += ind + 1
		return ind, f_str, search[ind+1 : ind2]
	}
	return ind, f_str, ""
}
func Gb_substr(str string, length int, argdot ...string) string {
	dot := ""
	if len(argdot) > 0 {
		dot = argdot[0]
	}
	ret := ""
	for _, v := range str {
		if unicode.Is(unicode.Han, v) {
			length--
		}
		length--
		ret += string(v)
		if length <= 0 {
			ret += dot
			break
		}
	}
	return ret
}
func Gb_strlen(str string) int {
	count := 0
	for _, v := range str {
		if unicode.Is(unicode.Han, v) {
			count++
		}
		count++
	}
	return count
}
func Gb_haschinese(str string) bool {
	for _, v := range str {
		if unicode.Is(unicode.Han, v) {
			return true
		}
	}
	return false
}

func Getstrparam(pstr string, argsplit ...string) map[string]string {
	jsn := map[string]string{}
	split := "|"
	if len(argsplit) > 0 {
		split = argsplit[0]
	}
	pstrs := strings.Split(pstr, split)
	for _, str := range pstrs {
		ind := strings.Index(str, "=")
		if ind > 0 {
			jsn[strings.TrimSpace(str[:ind])] = strings.TrimSpace(str[ind+1:])
		} else {
			jsn[strings.TrimSpace(str)] = ""
		}
	}
	return jsn
}
func Setstrparam(parr map[string]string, argsplit ...string) string {
	rets := make([]string, 0)
	split := "|"
	if len(argsplit) > 0 {
		split = argsplit[0]
	}
	for k, v := range parr {
		rets = append(rets, k+"="+v)
	}
	return strings.Join(rets, split)
}
func FieldAdd(field *map[string]map[string]any, fshow *string, inpoint int, fname string, comment string) {
	(*field)[fname] = map[string]any{
		"c": comment,
	}
	fshows := strings.Split(*fshow, ",")
	if inpoint == -1 {
		if len(fshows[0]) == 0 {
			fshows[0] = fname
		} else {
			fshows = append(fshows, fname)
		}
	} else {
		fshows = Array_unshift(fshows, fname, inpoint)
	}
	*fshow = strings.Join(fshows, ",")
}

// Getrelation(CiyDB, rows, "xx_user", "xxid", map[string]string{"column": "id,name"}, map[string]string{"queryid": "id"})
func Getrelation(db *CiyMysql, rows []map[string]any, table string, field string, argopn ...map[string]string) []map[string]any {
	column := "id,name"
	queryid := "id"
	if len(argopn) > 0 {
		for _, arg := range argopn {
			if v, ok := arg["column"]; ok {
				column = v
			}
			if v, ok := arg["queryid"]; ok {
				queryid = v
			}
		}
	}
	ids := []string{}
	for _, row := range rows {
		ids = append(ids, Tostr(row[field]))
	}
	if len(ids) == 0 {
		return []map[string]any{}
	}
	csql := NewCiySQL(table)
	csql.Where(queryid+" in ", strings.Join(ids, ","))
	csql.Column(column)
	rows2, _, err := db.Get(csql)
	if err != nil {
		return []map[string]any{}
	}
	return rows2
}
func Delcheck(db *CiyMysql, delid int, table, field, msg string) {
	csql := NewCiySQL(table)
	csql.Where(field, delid)
	cnt := Toint(db.Get1(csql))
	if cnt > 0 {
		panic(fmt.Sprintf("存在%d个%v，不能删除", cnt, msg))
	}
}
func Delall(db *CiyMysql, delid int, table, field, msg string) {
	csql := NewCiySQL(table)
	csql.Where(field, delid)
	_, err := db.Delete(csql)
	if err != nil {
		panic(msg + "删除失败:" + err.Error())
	}
}
func Delme(db *CiyMysql, delid int, table string) {
	csql := NewCiySQL(table)
	csql.Where("id", delid)
	_, err := db.Delete(csql)
	if err != nil {
		panic("删除失败:" + err.Error())
	}
}
func Mapid2data(datas []map[string]any) map[int]map[string]any {
	ret := map[int]map[string]any{}
	for _, data := range datas {
		ret[Toint(data["id"])] = data
	}
	return ret
}
func Ccode(rows []map[string]any, code int) string {
	showcode := "id"
	showtitle := "name"
	for _, row := range rows {
		if code == Toint(row[showcode]) {
			return Tostr(row[showtitle])
		}
	}
	return "--"
}
func Mcode(rows []map[string]any, code int) []string {
	showcode := "id"
	showtitle := "name"
	upcode := "upid"
	codes := make([]string, 0)
	for i := 0; i < 10; i++ {
		if code <= 0 {
			break
		}
		bfind := false
		for _, row := range rows {
			if code == Toint(row[showcode]) {
				codes = append(codes, Tostr(row[showtitle]))
				code = Toint(row[upcode])
				bfind = true
				break
			}
		}
		if !bfind {
			break
		}
	}
	lencode := len(codes)
	ret := make([]string, lencode)
	for i := 0; i < lencode; i++ {
		ret[i] = codes[lencode-1-i]
	}
	return ret
}

func Scode(rows []map[string]any, code string) []string {
	showcode := "id"
	showtitle := "name"
	rets := make([]string, 0)
	codes := strings.Split(code, ",")
	for _, c := range codes {
		if c == "" {
			continue
		}
		for _, row := range rows {
			if c == row[showcode] {
				rets = append(rets, Tostr(row[showtitle]))
				break
			}
		}
	}
	return rets
}

func Dcode(rows []map[string]any, codename string) int {
	showcode := "id"
	showtitle := "name"
	for _, row := range rows {
		if codename == row[showtitle] {
			return Toint(row[showcode])
		}
	}
	return -1
}
func LogDBStr(oldrow map[string]any, newrow map[string]any) string {
	msg := ""
	if oldrow != nil && newrow != nil {
		msg = "Upd=" + Tostr(oldrow["id"])
		modify := false
		for f, v := range newrow {
			if _, ok := oldrow[f]; !ok {
				continue
			}
			if oldrow[f] != v {
				msg += "_|@|_" + f + "=" + Tostr(oldrow[f]) + "→" + Tostr(v)
				modify = true
			}
		}
		if !modify {
			return ""
		}
	} else if newrow != nil {
		msg = "Ins=" + Tostr(newrow["newid"])
		for f, v := range newrow {
			if f == "newid" || f == "id" {
				continue
			}
			msg += "_|@|_" + f + "=" + Tostr(v)
		}
	} else if oldrow != nil {
		msg = "Del=" + Tostr(oldrow["id"])
		for f, v := range oldrow {
			if f == "id" {
				continue
			}
			msg += "_|@|_" + f + "=" + Tostr(v)
		}
	} else {
		return ""
	}
	return msg
}
func DirMake(dir string, argpower ...int) error {
	power := 0777
	if len(argpower) > 0 {
		power = argpower[0]
	}
	if dir == "" {
		return fmt.Errorf("DirMake Error:dir is empty")
	}
	err := DirExist(dir)
	if err == nil {
		return nil
	}
	err = os.MkdirAll(dir, os.ModeDir)
	if err != nil {
		err = fmt.Errorf("dirMake Error:%v[%v]", err, dir)
		Log.Error("DIR", err.Error())
		return err
	}
	err = syscall.Chmod(dir, uint32(power))
	if err != nil {
		err = fmt.Errorf("dirMake setting permissions Error:%v[%v]", err, dir)
		Log.Error("DIR", err.Error())
		return err
	}
	return nil
}
func FileDel(file string) error {
	err := os.Remove(file)
	if err != nil {
		if !os.IsNotExist(err) {
			err = fmt.Errorf("Delete Error:%v[%v]", err, file)
			Log.Error("FILE", err.Error())
			return err
		}
	}
	return nil
}
func FileCopy(srcfile string, dstfile string) error {
	srcF, err := os.Open(srcfile)
	if err != nil {
		err = fmt.Errorf("copy open error:%v[%v]", err, srcfile)
		Log.Error("FILE", err.Error())
		return err
	}
	defer srcF.Close()
	reader := bufio.NewReader(srcF)

	dstF, err := os.OpenFile(dstfile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		err = fmt.Errorf("copy create error:%v[%v]", err, dstfile)
		Log.Error("FILE", err.Error())
		return err
	}
	defer dstF.Close()
	writer := bufio.NewWriter(dstF)
	_, err = io.Copy(writer, reader)
	if err != nil {
		err = fmt.Errorf("copy write error:%v[%v][%v]", err, srcfile, dstfile)
		Log.Error("FILE", err.Error())
		return err
	}
	return nil
}
func FileSave(filename string, text any) error {
	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()
	writer := bufio.NewWriter(f)
	switch v := text.(type) {
	case string:
		_, err = writer.WriteString(v)
	case []byte:
		_, err = writer.Write(v)
	case []string:
		for _, s := range v {
			_, err = writer.WriteString(s)
			if err != nil {
				return err
			}
		}
	default:
		_, err = writer.WriteString(Tostr(v))
	}

	if err != nil {
		return err
	}
	err = writer.Flush()
	if err != nil {
		return err
	}
	return nil
}
func FileLoad(filename string) []string {
	f, err := os.Open(filename)
	if err != nil {
		return nil
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	scanner.Split(bufio.ScanLines)

	lines := make([]string, 0)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines
}
func DirExist(pathname string) error {
	info, err := os.Stat(pathname)
	if err != nil {
		return err
	}
	if info.IsDir() {
		return nil
	}
	return fmt.Errorf("%s is not a directory", pathname)
}
func FileExist(filename string) error {
	if _, err := os.Stat(filename); err == nil {
		return nil
	} else if os.IsNotExist(err) {
		return err
	}
	return fmt.Errorf("%s is not a file", filename)
}
func Fileext(file string) (string, string) {
	ind := strings.LastIndex(file, ".")
	if ind == -1 {
		return file, ""
	}

	return file[:ind], strings.ToLower(file[ind+1:])
}
