/*
 * @description: 常用函数
 * @Author: CP
 * @Date: 2020-08-21 11:12:56
 * @FilePath: \construction_management\comm\functions.go
 */
package comm

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
	"math"
	"math/rand"
	"time"
	"unicode"

	"crypto/md5"
	"encoding/binary"
	"fmt"
	"strconv"
	"strings"

	"go.mod/conf"
	"go.mod/models"
	"go.mod/web/viewmodels"
)

// 账号-构造视图层models
func MakeProjectAccountVM(modelsAccount *models.CmProjectAccount) viewmodels.ProjectAccount {
	viewAccountData := viewmodels.ProjectAccount{}
	aesId, _ := AesEncrypt(strconv.Itoa(modelsAccount.Id), conf.SignSecret)
	aesProjectId, _ := AesEncrypt(strconv.Itoa(modelsAccount.ProjectId), conf.SignSecret)
	viewAccountData.Id = aesId
	viewAccountData.ProjectId = aesProjectId
	viewAccountData.Account = modelsAccount.Account
	viewAccountData.Name = modelsAccount.Name
	viewAccountData.Company = modelsAccount.Company
	viewAccountData.Role = modelsAccount.Role
	viewAccountData.Mobile = modelsAccount.Mobile
	viewAccountData.Telephone = modelsAccount.Telephone
	viewAccountData.IsAdmin = modelsAccount.IsAdmin
	viewAccountData.AccountGroup = modelsAccount.AccountGroup
	viewAccountData.Enable = modelsAccount.Enable
	viewAccountData.Position = modelsAccount.Position

	// viewAccountData.ContractPermission = modelsAccount.ContractPermission
	// viewAccountData.QualityPermission = modelsAccount.QualityPermission
	// viewAccountData.SafePermission = modelsAccount.SafePermission

	return viewAccountData
}

// 创建合同目录树
func MakeFolderContract(Data []*viewmodels.FolderContract, node *viewmodels.FolderContract) { //参数为父节点，添加父节点的子节点指针切片
	childs, _ := HaveChildContract(Data, node) //判断节点是否有子节点并返回
	if childs != nil {
		// 子节点总数
		Total := len(childs)
		node.ChildsTotal = Total
		// 标记最后一个元素
		end := Total - 1
		childs[end].IsEnd = true
		// 往父节点添加子节点
		node.Children = append(node.Children, childs[0:]...) //添加子节点

		// 1.标段汇总
		// 1-1.合同相关汇总-收入金额
		totalIncome := 0.00
		// 1-2.合同总数
		totalContractNumber := 0
		// 1-3.汇款进度
		totalReturn := 0.00
		// 1-4支出金额
		totalPay := 0.00
		totalPaid := 0.00
		// 1-5 安全巡检
		safeTotal := 0
		safeTotalRectification := 0
		safeTotalRectificationIn := 0
		// 1-6 质量巡检
		qualityTotal := 0
		qualityTotalRectification := 0
		qualityTotalRectificationIn := 0

		for _, v := range childs { //查询子节点的子节点，并添加到子节点
			_, has := HaveChildContract(Data, v)
			if has {
				// 递归添加节点
				MakeFolderContract(Data, v)
			}
			// 目录下是否包含标段
			if v.Isfolder == 0 {
				node.IsBid = true
			} else {
				node.HasFolder = true
			}
			// 2.标段汇总
			// 2-1收入金额 合计
			price, _ := strconv.ParseFloat(v.ContractsIncome, 64)
			totalIncome = totalIncome + price
			// 2-2合同总数 合计
			totalContractNumber = totalContractNumber + v.Contracts
			// 2-3 汇款进度
			price, _ = strconv.ParseFloat(v.ContractsReturned, 64)
			totalReturn = totalReturn + price
			//
			price, _ = strconv.ParseFloat(v.ContractsPay, 64)
			totalPay = totalPay + price
			price, _ = strconv.ParseFloat(v.ContractsPaid, 64)
			totalPaid = totalPaid + price
			// 安全巡检
			safeTotal = safeTotal + v.SafeTotal
			safeTotalRectification = safeTotalRectification + v.SafeRectification
			safeTotalRectificationIn = safeTotalRectificationIn + v.SafeRectificationIn
			// 1-6 质量巡检
			qualityTotal = safeTotal + v.QualityTotal
			qualityTotalRectification = safeTotalRectification + v.QualityRectification
			qualityTotalRectificationIn = safeTotalRectificationIn + v.QualityRectificationIn
		}
		node.ContractsIncome = fmt.Sprintf("%.2f", totalIncome)
		node.Contracts = totalContractNumber
		// 汇款进度
		quotient := totalReturn / totalIncome
		if math.IsNaN(quotient) {
			quotient = 0
		}
		node.ContractsIncomeProgress = fmt.Sprintf("%.0f", (quotient)*100) + "%"
		// 支出相关
		node.ContractsPay = fmt.Sprintf("%.2f", totalPay)
		quotient = totalPaid / totalPay
		if math.IsNaN(quotient) {
			quotient = 0
		}
		node.ContractsPayProgress = fmt.Sprintf("%.0f", (quotient)*100) + "%"
		// 安全巡检
		node.SafeTotal = safeTotal
		node.SafeRectification = safeTotalRectification
		node.SafeRectificationIn = safeTotalRectificationIn
		// 质量巡检
		node.QualityTotal = qualityTotal
		node.QualityRectification = qualityTotalRectification
		node.QualityRectificationIn = qualityTotalRectificationIn
	}
}

// 是否有子树
func HaveChildContract(Data []*viewmodels.FolderContract, node *viewmodels.FolderContract) (child []*viewmodels.FolderContract, yes bool) {
	for _, v := range Data {
		if v.ParentId == node.Id {
			child = append(child, v)
		}
	}
	if child != nil {
		yes = true
	}
	return
}

// 创建合同项目节树
func MakeSectionContract(Data []*viewmodels.TreeSectionContract, node *viewmodels.TreeSectionContract) { //参数为父节点，添加父节点的子节点指针切片

	childs, _ := haveChildSectionContract(Data, node) //判断节点是否有子节点并返回

	if childs != nil {
		// 往父节点添加子节点
		// 孩子们从小到大排序
		if len(childs) != 0 {
			sectionSelectionSort(childs)
			childs[0].ElderBrother = false
			childs[len(childs)-1].IsEnd = true
		}

		// 项目节父项使用
		// 1.合同总金额 回款总金额 已支付总金额
		// contractPriceTotal := 0.00
		// returnPriceTotal := 0.00
		// paidPriceTotal := 0.00

		node.Children = append(node.Children, childs[0:]...) //添加子节点
		for _, v := range childs {                           //查询子节点的子节点，并添加到子节点
			_, has := haveChildSectionContract(Data, v)
			if has {
				// 递归添加节点
				MakeSectionContract(Data, v)
			}

			// 2.计算父项中金额
			// contractPrice, _ := strconv.ParseFloat(v.ContractPrice, 64)
			// contractPriceTotal += contractPrice
			// returnPrice, _ := strconv.ParseFloat(v.ContractReturned, 64)
			// returnPriceTotal += returnPrice
			// paidPrice, _ := strconv.ParseFloat(v.ContractsPaid, 64)
			// paidPriceTotal += paidPrice
		}

		// 3.赋值到父项中
		// node.ContractPrice = fmt.Sprintf("%.2f", contractPriceTotal)
		// node.ContractReturned = fmt.Sprintf("%.2f", returnPriceTotal)
		// node.ContractsPaid = fmt.Sprintf("%.2f", paidPriceTotal)
	}
}

// 合同项目节孩子排序
func sectionSelectionSort(child []*viewmodels.TreeSectionContract) {
	for i := 0; i < len(child); i++ {
		minIndex := i
		// 查找最小值
		for j := i; j < len(child); j++ {
			if child[j].Serial < child[minIndex].Serial {
				minIndex = j
			}
		}
		swapContract(child, i, minIndex)
	}
}

func swapContract(child []*viewmodels.TreeSectionContract, i int, minIndex int) {
	t := child[i]
	child[i] = child[minIndex]
	child[minIndex] = t
}

// 是否有子树
func haveChildSectionContract(Data []*viewmodels.TreeSectionContract, node *viewmodels.TreeSectionContract) (child []*viewmodels.TreeSectionContract, yes bool) {

	for _, v := range Data {
		if v.ParentId == node.Id {
			child = append(child, v)
		}
	}

	if child != nil {
		yes = true
	}
	return
}

// 当前时间的时间戳
func NowUnix() int {
	return int(time.Now().In(conf.SysTimeLocation).Unix())
}

// 将unix时间戳格式化为yyyymmdd H:i:s格式字符串
func FormatFromUnixTime(t int64) string {
	if t > 0 {
		return time.Unix(t, 0).Format(conf.SysTimeform)
	} else {
		return time.Now().Format(conf.SysTimeform)
	}
}

// 将unix时间戳格式化为yyyymmdd格式字符串
func FormatFromUnixTimeShort(t int64) string {
	if t > 0 {
		return time.Unix(t, 0).Format(conf.SysTimeformShort)
	} else {
		return time.Now().Format(conf.SysTimeformShort)
	}
}

// 将字符串转成时间
func ParseTime(str string) (time.Time, error) {
	return time.ParseInLocation(conf.SysTimeform, str, conf.SysTimeLocation)
}

// 得到一个随机数
func Random(max int) int {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	if max < 1 {
		return r.Int()
	} else {
		return r.Intn(max)
	}
}

// 加密密码
func CreatePasswordSign(password string, account string) string {
	str := string(conf.SignSecret) + password + account
	str1 := fmt.Sprintf("%x", md5.Sum([]byte(str)))
	sign := fmt.Sprintf("%x", md5.Sum([]byte(str1)))
	return sign
}

// 对字符串进行签名
func CreateSign(str string) string {
	str = string(conf.SignSecret) + str
	str1 := fmt.Sprintf("%x", md5.Sum([]byte(str)))
	sign := fmt.Sprintf("%x", md5.Sum([]byte(str1)))
	return sign
}

// 对一个字符串进行加密
func Encrypt(key, text []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	b := base64.StdEncoding.EncodeToString(text)
	ciphertext := make([]byte, aes.BlockSize+len(b))
	iv := ciphertext[:aes.BlockSize]
	//if _, err := io.ReadFull(rand.Reader, iv); err != nil {
	//	return nil, err
	//}
	cfb := cipher.NewCFBEncrypter(block, iv)
	cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))

	return base64.RawURLEncoding.EncodeToString(ciphertext), nil
}

// 对一个字符串进行解密
func Decrypt(key, text []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	if len(text) < aes.BlockSize {
		return nil, errors.New("ciphertext too short")
	}
	iv := text[:aes.BlockSize]
	text = text[aes.BlockSize:]
	cfb := cipher.NewCFBDecrypter(block, iv)
	cfb.XORKeyStream(text, text)
	data, err := base64.StdEncoding.DecodeString(string(text))
	if err != nil {
		return nil, err
	}
	return data, nil
}

// 加密
func AesEncrypt(orig string, key string) (string, error) {
	// 转成字节数组
	origData := []byte(orig)
	k := []byte(key)

	// 分组秘钥
	block, err := aes.NewCipher(k)
	if err != nil {
		return "", err
	}
	// 获取秘钥块的长度
	blockSize := block.BlockSize()
	// 补全码
	origData = PKCS7Padding(origData, blockSize)
	// 加密模式
	blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
	// 创建数组
	cryted := make([]byte, len(origData))
	// 加密
	blockMode.CryptBlocks(cryted, origData)
	//使用RawURLEncoding 不要使用StdEncoding
	//不要使用StdEncoding  放在url参数中回导致错误
	return base64.RawURLEncoding.EncodeToString(cryted), nil

}

// 解密
func AesDecrypt(cryted string, key string) (string, error) {
	//使用RawURLEncoding 不要使用StdEncoding
	//不要使用StdEncoding  放在url参数中回导致错误
	crytedByte, err := base64.RawURLEncoding.DecodeString(cryted)
	if err != nil {
		return "", err
	}
	k := []byte(key)

	// 分组秘钥
	block, err := aes.NewCipher(k)
	if err != nil {
		return "", err
	}
	// 获取秘钥块的长度
	blockSize := block.BlockSize()
	// 加密模式
	blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
	// 创建数组
	orig := make([]byte, len(crytedByte))
	// 解密--传入不正确的字符时会报错-TODO 需捕获
	blockMode.CryptBlocks(orig, crytedByte)
	// 去补全码
	orig = PKCS7UnPadding(orig)
	return string(orig), nil
}

//补码
func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
	padding := blocksize - len(ciphertext)%blocksize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

//去码
func PKCS7UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

// addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
// 预定义字符是：
// 单引号（'）
// 双引号（"）
// 反斜杠（\）
func Addslashes(str string) string {
	tmpRune := []rune{}
	strRune := []rune(str)
	for _, ch := range strRune {
		switch ch {
		case []rune{'\\'}[0], []rune{'"'}[0], []rune{'\''}[0]:
			tmpRune = append(tmpRune, []rune{'\\'}[0])
			tmpRune = append(tmpRune, ch)
		default:
			tmpRune = append(tmpRune, ch)
		}
	}
	return string(tmpRune)
}

// stripslashes() 函数删除由 addslashes() 函数添加的反斜杠。
func Stripslashes(str string) string {
	dstRune := []rune{}
	strRune := []rune(str)
	strLenth := len(strRune)
	for i := 0; i < strLenth; i++ {
		if strRune[i] == []rune{'\\'}[0] {
			i++
		}
		dstRune = append(dstRune, strRune[i])
	}
	return string(dstRune)
}

// 将字符串的IP转化为数字
func Ip4toInt(ip string) int64 {
	bits := strings.Split(ip, ".")
	if len(bits) == 4 {
		b0, _ := strconv.Atoi(bits[0])
		b1, _ := strconv.Atoi(bits[1])
		b2, _ := strconv.Atoi(bits[2])
		b3, _ := strconv.Atoi(bits[3])
		var sum int64
		sum += int64(b0) << 24
		sum += int64(b1) << 16
		sum += int64(b2) << 8
		sum += int64(b3)
		return sum
	} else {
		return 0
	}
}

// 得到当前时间到下一天零点的延时
func NextDayDuration() time.Duration {
	year, month, day := time.Now().Add(time.Hour * 24).Date()
	next := time.Date(year, month, day, 0, 0, 0, 0, conf.SysTimeLocation)
	return next.Sub(time.Now())
}

// 从接口类型安全获取到int64
func GetInt64(i interface{}, d int64) int64 {
	if i == nil {
		return d
	}
	switch i.(type) {
	case string:
		num, err := strconv.Atoi(i.(string))
		if err != nil {
			return d
		} else {
			return int64(num)
		}
	case []byte:
		bits := i.([]byte)
		if len(bits) == 8 {
			return int64(binary.LittleEndian.Uint64(bits))
		} else if len(bits) <= 4 {
			num, err := strconv.Atoi(string(bits))
			if err != nil {
				return d
			} else {
				return int64(num)
			}
		}
	case uint:
		return int64(i.(uint))
	case uint8:
		return int64(i.(uint8))
	case uint16:
		return int64(i.(uint16))
	case uint32:
		return int64(i.(uint32))
	case uint64:
		return int64(i.(uint64))
	case int:
		return int64(i.(int))
	case int8:
		return int64(i.(int8))
	case int16:
		return int64(i.(int16))
	case int32:
		return int64(i.(int32))
	case int64:
		return i.(int64)
	case float32:
		return int64(i.(float32))
	case float64:
		return int64(i.(float64))
	}
	return d
}

// 从接口类型安全获取到字符串类型
func GetString(str interface{}, d string) string {
	if str == nil {
		return d
	}
	switch str.(type) {
	case string:
		return str.(string)
	case []byte:
		return string(str.([]byte))
	}
	return fmt.Sprintf("%s", str)
}

// 从map中得到指定的key
func GetInt64FromMap(dm map[string]interface{}, key string, dft int64) int64 {
	data, ok := dm[key]
	if !ok {
		return dft
	}
	return GetInt64(data, dft)
}

// 从map中得到指定的key
func GetInt64FromStringMap(dm map[string]string, key string, dft int64) int64 {
	data, ok := dm[key]
	if !ok {
		return dft
	}
	return GetInt64(data, dft)
}

// 从map中得到指定的key
func GetStringFromMap(dm map[string]interface{}, key string, dft string) string {
	data, ok := dm[key]
	if !ok {
		return dft
	}
	return GetString(data, dft)
}

// 从map中得到指定的key
func GetStringFromStringMap(dm map[string]string, key string, dft string) string {
	data, ok := dm[key]
	if !ok {
		return dft
	}
	return data
}

// 首字母大写
func Ucfirst(str string) string {
	for i, v := range str {
		return string(unicode.ToUpper(v)) + str[i+1:]
	}
	return ""
}
