package utils

import (
	"errors"
	"fmt"
	"github.com/lianggx6/goutf16"
	"io"
	"math/rand"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
	"unsafe"
)

type HWND uintptr

const (
	ERROR_FILE_NOT_FOUND = 2
	ERROR_PATH_NOT_FOUND = 3
	ERROR_BAD_FORMAT     = 11
)

const (
	SE_ERR_ACCESSDENIED    = 5
	SE_ERR_OOM             = 8
	SE_ERR_DLLNOTFOUND     = 32
	SE_ERR_SHARE           = 26
	SE_ERR_ASSOCINCOMPLETE = 27
	SE_ERR_DDETIMEOUT      = 28
	SE_ERR_DDEFAIL         = 29
	SE_ERR_DDEBUSY         = 30
	SE_ERR_NOASSOC         = 31
)

var (
	modshell32       = syscall.NewLazyDLL("shell32.dll")
	procShellExecute = modshell32.NewProc("ShellExecuteW")
)

// 判断文件夹是否存在
func PathExists(path string) (bool, error) {
	var err error
	if _, err = os.Stat(path); err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// 用b的所有字段覆盖a的
// 如果fields不为空, 表示用b的特定字段覆盖a的
// a应该为结构体指针
func CopyFields(a interface{}, b interface{}, fields ...string) (err error) {
	at := reflect.TypeOf(a)
	av := reflect.ValueOf(a)
	bt := reflect.TypeOf(b)
	bv := reflect.ValueOf(b)

	// 简单判断下
	if at.Kind() != reflect.Ptr {
		err = fmt.Errorf("a must be a struct pointer")
		return
	}
	av = reflect.ValueOf(av.Interface())

	// 要复制哪些字段
	_fields := make([]string, 0)
	if len(fields) > 0 {
		_fields = fields
	} else {
		for i := 0; i < bv.NumField(); i++ {
			_fields = append(_fields, bt.Field(i).Name)
		}
	}

	if len(_fields) == 0 {
		fmt.Println("no fields to copy")
		return
	}

	// 复制
	for i := 0; i < len(_fields); i++ {
		name := _fields[i]
		f := av.Elem().FieldByName(name)
		bValue := bv.FieldByName(name)

		// a中有同名的字段并且类型一致才复制
		if f.IsValid() && f.Kind() == bValue.Kind() {
			f.Set(bValue)
		} else {
			//fmt.Printf("no such field or different kind, fieldName: %s\n", name)
		}
	}
	return
}

//获取默认打印机
func GetDefaultPrinterName() (string, []uint16) {
	var (
		dll               = syscall.MustLoadDLL("winspool.drv")
		getDefaultPrinter = dll.MustFindProc("GetDefaultPrinterW")
		pn                [256]uint16
	)

	plen := len(pn)
	getDefaultPrinter.Call(uintptr(unsafe.Pointer(&pn)), uintptr(unsafe.Pointer(&plen)))
	printerName := syscall.UTF16ToString(pn[:])
	printer16 := goutf16.EncodeStringToUTF16(printerName)
	return printerName, printer16
}

// 设置默认打印机
func SetDefaultPrinter(printerName string) error {
	var (
		dll               = syscall.MustLoadDLL("winspool.drv")
		setDefaultPrinter = dll.MustFindProc("SetDefaultPrinterW")
		p                 []uint16
		pn                [256]uint16
	)

	p = goutf16.EncodeStringToUTF16(printerName)
	pn = UintSliceTo256Uint(p)
	ret, _, msg := setDefaultPrinter.Call(uintptr(unsafe.Pointer(&pn)))
	if ret == 0 {
		return msg
	}

	return nil
}

func Uint16ToBytes(n uint16) []byte {
	return []byte{
		byte(n),
		byte(n >> 8),
	}
}

func BytesToUint16(array []byte) uint16 {
	var data uint16 = 0
	for i := 0; i < len(array); i++ {
		data = data + uint16(uint(array[i])<<uint(8*i))
	}

	return data
}

func UintSliceTo256Uint(us []uint16) (res [256]uint16) {
	var i int

	for i = 0; i < len(us); i++ {
		res[i] = us[i]
	}
	return res
}

func GetExcelCellIntPos(pos string) (row, col int, err error) {
	var (
		chareg, numreg *regexp.Regexp
		cha, num       [][]string
		i              int
		uc             []rune
		rowstr         string
	)

	chareg = regexp.MustCompile("[a-zA-Z]")
	numreg = regexp.MustCompile("[0-9]")
	cha = chareg.FindAllStringSubmatch(pos, -1)
	num = numreg.FindAllStringSubmatch(pos, -1)
	// 获取字符的列值
	col = 0
	for i = 0; i < len(cha); i++ {
		uc = []rune(strings.ToUpper(cha[i][0]))
		col = col + i*26 + (int(uc[0]) - 65)
	}

	// 获取行值
	for i = 0; i < len(num); i++ {
		rowstr = rowstr + num[i][0]
	}
	row, _ = strconv.Atoi(rowstr)

	return row - 1, col, nil
}

func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
	var op, param, directory uintptr
	if len(lpOperation) != 0 {
		op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
	}
	if len(lpParameters) != 0 {
		param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
	}
	if len(lpDirectory) != 0 {
		directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
	}

	ret, _, _ := procShellExecute.Call(
		uintptr(hwnd),
		op,
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
		param,
		directory,
		uintptr(nShowCmd))

	errorMsg := ""
	if ret != 0 && ret <= 32 {
		switch int(ret) {
		case ERROR_FILE_NOT_FOUND:
			errorMsg = "The specified file was not found."
		case ERROR_PATH_NOT_FOUND:
			errorMsg = "The specified path was not found."
		case ERROR_BAD_FORMAT:
			errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)."
		case SE_ERR_ACCESSDENIED:
			errorMsg = "The operating system denied access to the specified file."
		case SE_ERR_ASSOCINCOMPLETE:
			errorMsg = "The file name association is incomplete or invalid."
		case SE_ERR_DDEBUSY:
			errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed."
		case SE_ERR_DDEFAIL:
			errorMsg = "The DDE transaction failed."
		case SE_ERR_DDETIMEOUT:
			errorMsg = "The DDE transaction could not be completed because the request timed out."
		case SE_ERR_DLLNOTFOUND:
			errorMsg = "The specified DLL was not found."
		case SE_ERR_NOASSOC:
			errorMsg = "There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable."
		case SE_ERR_OOM:
			errorMsg = "There was not enough memory to complete the operation."
		case SE_ERR_SHARE:
			errorMsg = "A sharing violation occurred."
		default:
			errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", ret)
		}
	} else {
		return nil
	}

	return errors.New(errorMsg)
}

// RandNumber 生成min - max之间的随机数
// 如果min大于max, panic
func RandNumber(min, max int) int {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	switch {
	case min == max:
		return min
	case min > max:
		panic("min must be less than or equal to max")
	}

	return min + r.Intn(max-min)
}

// PanicToError Panic转换为error
func PanicToError(f func()) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf(PanicTrace(e))
		}
	}()
	f()
	return
}

// PanicTrace panic调用链跟踪
func PanicTrace(err interface{}) string {
	stackBuf := make([]byte, 4096)
	n := runtime.Stack(stackBuf, false)

	return fmt.Sprintf("panic: %v %s", err, stackBuf[:n])
}

// DownloadFile 文件下载
func DownloadFile(filePath string, rw http.ResponseWriter) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	filename := path.Base(filePath)
	rw.Header().Set("Content-Type", "application/octet-stream")
	rw.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
	_, err = io.Copy(rw, file)

	return err
}

// WorkDir 获取程序运行时根目录
func WorkDir() (string, error) {
	execPath, err := os.Executable()
	if err != nil {
		return "", err
	}
	wd := filepath.Dir(execPath)
	if filepath.Base(wd) == "bin" {
		wd = filepath.Dir(wd)
	}

	return wd, nil
}

// WaitGroupWrapper waitGroup包装
type WaitGroupWrapper struct {
	sync.WaitGroup
}

// Wrap 包装Add, Done方法
func (w *WaitGroupWrapper) Wrap(f func()) {
	w.Add(1)
	go func() {
		defer w.Done()
		f()
	}()

}

// 生成长度为length的随机字符串
func RandString(length int64) string {
	sources := []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	var result []byte
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	sourceLength := len(sources)
	var i int64 = 0
	for ; i < length; i++ {
		result = append(result, sources[r.Intn(sourceLength)])
	}

	return string(result)
}

// 批量替换字符串
func ReplaceStrings(s string, old []string, replace []string) string {
	if s == "" {
		return s
	}
	if len(old) != len(replace) {
		return s
	}

	for i, v := range old {
		s = strings.Replace(s, v, replace[i], 1000)
	}

	return s
}

func InStringSlice(slice []string, element string) bool {
	element = strings.TrimSpace(element)
	for _, v := range slice {
		if strings.TrimSpace(v) == element {
			return true
		}
	}

	return false
}

// 转义json特殊字符
func EscapeJson(s string) string {
	specialChars := []string{"\\", "\b", "\f", "\n", "\r", "\t", "\""}
	replaceChars := []string{"\\\\", "\\b", "\\f", "\\n", "\\r", "\\t", "\\\""}

	return ReplaceStrings(s, specialChars, replaceChars)
}

// 判断文件是否存在及是否有权限访问
func FileExist(file string) bool {
	_, err := os.Stat(file)
	if os.IsNotExist(err) {
		return false
	}
	if os.IsPermission(err) {
		return false
	}

	return true
}

func Max(vals ...int) int {
	var max int
	for _, val := range vals {
		if val > max {
			max = val
		}
	}
	return max
}

func Min(vals ...int) int {
	var min int
	for _, val := range vals {
		if min == 0 || val <= min {
			min = val
		}
	}
	return min
}

func MaxTime(vals ...time.Time) time.Time {
	var max time.Time

	if len(vals) > 0 {
		max = vals[0]
	} else {
		// 默认返回一年前的时间
		return time.Now().Add(24 * 265 * time.Second)
	}

	for _, val := range vals {
		if val.Unix() > max.Unix() {
			max = val
		}
	}
	return max
}

func MinTime(vals ...time.Time) time.Time {
	var min time.Time

	if len(vals) > 0 {
		min = vals[0]
	} else {
		// 默认返回一年前的时间
		return time.Now().Add(24 * 265 * time.Second)
	}

	for _, val := range vals {
		if val.Unix() <= min.Unix() {
			min = val
		}
	}
	return min
}

func AddListOnce(list []string, element string) []string {
	isIn := false
	for _, info := range list {
		if info == element {
			isIn = true
		}
	}
	if !isIn {
		list = append(list, element)
	}
	return list
}

func Decimal(value float64,length int) float64 {
	if length <= 0{
		length = 2
	}
	format := "%."+strconv.Itoa(length)+"f"
	value, _ = strconv.ParseFloat(fmt.Sprintf(format, value), 64)
	return value
}