package starcrypto

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"hash"
	"hash/crc32"
	"io"
	"io/ioutil"
	rands "math/rand"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func String(bstr []byte) string {
	return hex.EncodeToString(bstr)
}

// MD5 输出MD5校验值
func Md5(bstr []byte) []byte {
	md5sum := md5.New()
	md5sum.Write(bstr)
	return md5sum.Sum(nil)
}

func Md5Str(bstr []byte) string {
	return String(Md5(bstr))
}

// CRC32 输出CRC32校验值
func Crc32(bstr []byte) []byte {
	crcsum := crc32.NewIEEE()
	crcsum.Write(bstr)
	return crcsum.Sum(nil)
}

func Crc32Str(bstr []byte) string {
	return String(Crc32(bstr))
}

// SHA512 输出SHA512校验值
func Sha512(bstr []byte) []byte {
	shasum := sha512.New()
	shasum.Write(bstr)
	return shasum.Sum(nil)
}

func Sha512Str(bstr []byte) string {
	return String(Sha512(bstr))
}

// SHA384 输出SHA384校验值
func Sha384(bstr []byte) []byte {
	shasum := sha512.New384()
	shasum.Write(bstr)
	return shasum.Sum(nil)
}

func Sha384Str(bstr []byte) string {
	return String(Sha384(bstr))
}

// SHA256 输出SHA256校验值
func Sha256(bstr []byte) []byte {
	shasum := sha256.New()
	shasum.Write(bstr)
	return shasum.Sum(nil)
}

func Sha256Str(bstr []byte) string {
	return String(Sha256(bstr))
}

// SHA224 输出SHA224校验值
func Sha224(bstr []byte) []byte {
	shasum := sha256.New224()
	shasum.Write(bstr)
	return shasum.Sum(nil)
}

func Sha224Str(bstr []byte) string {
	return String(Sha224(bstr))
}

// SHA1 输出SHA1校验值
func Sha1(bstr []byte) []byte {
	shasum := sha1.New()
	shasum.Write(bstr)
	return shasum.Sum(nil)
}

func Sha1Str(bstr []byte) string {
	return String(Sha512(bstr))
}

// SumAll 可以对同一数据进行多种校验
func SumAll(data []byte, method []string) (map[string][]byte, error) {
	result := make(map[string][]byte)
	methods := make(map[string]hash.Hash)
	var iscrc bool
	if len(method) == 0 {
		method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
	}
	sum512 := sha512.New()
	sum384 := sha512.New384()
	sum256 := sha256.New()
	sum224 := sha256.New224()
	sum1 := sha1.New()
	crcsum := crc32.NewIEEE()
	md5sum := md5.New()
	for _, v := range method {
		switch v {
		case "md5":
			methods["md5"] = md5sum
		case "crc32":
			iscrc = true
		case "sha1":
			methods["sha1"] = sum1
		case "sha224":
			methods["sha224"] = sum224
		case "sha256":
			methods["sha256"] = sum256
		case "sha384":
			methods["sha384"] = sum384
		case "sha512":
			methods["sha512"] = sum512
		}
	}
	for _, v := range methods {
		v.Write(data)
	}
	if iscrc {
		crcsum.Write(data)
	}

	for k, v := range methods {
		result[k] = v.Sum(nil)
	}
	if iscrc {
		result["crc32"] = crcsum.Sum(nil)
	}
	return result, nil
}

// FileSum 输出文件内容校验值，method为单个校验方法,小写
//例：FileSum("./test.txt","md5",shell(pect float64){fmt.Sprintf("已完成 %f\r",pect)})
func FileSum(filepath, method string, shell func(float64)) (string, error) {
	var sum hash.Hash
	var sum32 hash.Hash32
	var issum32 bool
	var result string
	fp, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	switch method {
	case "sha512":
		sum = sha512.New()
	case "sha384":
		sum = sha512.New384()
	case "sha256":
		sum = sha256.New()
	case "sha224":
		sum = sha256.New224()
	case "sha1":
		sum = sha1.New()
	case "crc32":
		sum32 = crc32.NewIEEE()
		issum32 = true
	case "md5":
		sum = md5.New()
	default:
		return "", errors.New("Cannot Recognize The Method:" + method)
	}
	writer := 0
	stat, _ := os.Stat(filepath)
	filebig := float64(stat.Size())
	if !issum32 {
		//	if _, err := io.Copy(sum, fp); err != nil {
		for {
			buf := make([]byte, 1048574)
			n, err := fp.Read(buf)
			if err != nil {
				if err == io.EOF {
					break
				}
				return result, err
			}
			writer += n
			pect := (float64(writer) / filebig) * 100
			go shell(pect)
			sum.Write(buf[0:n])
		}
		result = hex.EncodeToString(sum.Sum(nil))
	} else {
		for {
			buf := make([]byte, 1048574)
			n, err := fp.Read(buf)
			if err != nil {
				if err == io.EOF {
					break
				}
				return result, err
			}
			writer += n
			pect := (float64(writer) / filebig) * 100
			go shell(pect)
			sum32.Write(buf[0:n])
		}
		result = hex.EncodeToString(sum32.Sum(nil))
	}
	return result, nil
}

// FileSumAll 可以对同一文件进行多种校验
func FileSumAll(filepath string, method []string, shell func(float64)) (map[string]string, error) {
	result := make(map[string]string)
	methods := make(map[string]hash.Hash)
	var iscrc bool

	if len(method) == 0 {
		method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
	}
	fp, err := os.Open(filepath)
	defer fp.Close()
	if err != nil {
		return result, err
	}
	stat, _ := os.Stat(filepath)
	filebig := float64(stat.Size())
	sum512 := sha512.New()
	sum384 := sha512.New384()
	sum256 := sha256.New()
	sum224 := sha256.New224()
	sum1 := sha1.New()
	crcsum := crc32.NewIEEE()
	md5sum := md5.New()
	for _, v := range method {
		switch v {
		case "md5":
			methods["md5"] = md5sum
		case "crc32":
			iscrc = true
		case "sha1":
			methods["sha1"] = sum1
		case "sha224":
			methods["sha224"] = sum224
		case "sha256":
			methods["sha256"] = sum256
		case "sha384":
			methods["sha384"] = sum384
		case "sha512":
			methods["sha512"] = sum512
		}
	}

	writer := 0
	for {
		buf := make([]byte, 1048574)
		n, err := fp.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return result, err
		}
		writer += n
		pect := (float64(writer) / filebig) * 100
		go shell(pect)
		for _, v := range methods {
			v.Write(buf[0:n])
		}
		if iscrc {
			crcsum.Write(buf[0:n])
		}
	}
	for k, v := range methods {
		result[k] = hex.EncodeToString(v.Sum(nil))
	}
	if iscrc {
		result["crc32"] = hex.EncodeToString(crcsum.Sum(nil))
	}
	return result, nil
}

// Attach 合并src与dst文件并输出到output中
func Attach(src, dst, output string) error {
	fpsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fpsrc.Close()
	fpdst, err := os.Open(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	fpout, err := os.Create(output)
	if err != nil {
		return err
	}
	defer fpout.Close()
	if _, err := io.Copy(fpout, fpsrc); err != nil {
		return err
	}
	for {
		buf := make([]byte, 1048574)
		n, err := fpdst.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		fpout.Write(buf[0:n])
	}
	return nil
}

// Detach 按bytenum字节大小分割src文件到dst1与dst2两个新文件中去
func Detach(src string, bytenum int, dst1, dst2 string) error {
	fpsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fpsrc.Close()
	fpdst1, err := os.Create(dst1)
	if err != nil {
		return err
	}
	defer fpdst1.Close()
	fpdst2, err := os.Create(dst2)
	if err != nil {
		return err
	}
	defer fpdst2.Close()
	sumall := 0
	var buf []byte
	for {
		if bytenum-sumall < 1048576 {
			buf = make([]byte, bytenum-sumall)
		} else {
			buf = make([]byte, 1048576)
		}
		n, err := fpsrc.Read(buf)
		if err != nil {
			return err
		}
		sumall += n
		fpdst1.Write(buf[0:n])
		if sumall == bytenum {
			break
		}
	}
	for {
		buf = make([]byte, 1048576)
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		fpdst2.Write(buf[0:n])
	}
	return nil
}

// SplitFile 把src文件按要求分割到dst中去,dst应传入带*号字符串
// 如果bynum=true 则把文件分割成num份
// 如果bynum=false 则把文件按num字节分成多份
func SplitFile(src, dst string, num int, bynum bool, shell func(float64)) error {
	fpsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fpsrc.Close()
	stat, _ := os.Stat(src)
	filebig := float64(stat.Size())
	if bynum {
		if int(filebig) < num {
			return errors.New("file is too small to split")
		}
	}
	balance := int(filebig/float64(num)) + 1
	if !bynum {
		balance = num
	}
	nownum := 0
	fpdst, err := os.Create(strings.Replace(dst, "*", fmt.Sprint(nownum), -1))
	if err != nil {
		return err
	}
	defer fpdst.Close()
	var sum, tsum int = 0, 0
	var buf []byte
	for {
		if balance-sum < 1048576 {
			buf = make([]byte, balance-sum)
		} else {
			buf = make([]byte, 1048576)
		}
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += n
		tsum += n
		fpdst.Write(buf[0:n])
		go shell(float64(tsum) / filebig * 100)
		if sum == balance {
			fpdst.Close()
			nownum++
			fpdst, err = os.Create(strings.Replace(dst, "*", fmt.Sprint(nownum), -1))
			if err != nil {
				return err
			}
			sum = 0
		}
	}
	return nil
}

// MergeFile 合并src文件到dst文件中去，src文件应传入带*号字符串
func MergeFile(src, dst string, shell func(float64)) error {
	tmp := strings.Replace(src, "*", "0", -1)
	dir, err := ioutil.ReadDir(filepath.Dir(tmp))
	if err != nil {
		return err
	}
	base := filepath.Base(src)
	tmp = strings.Replace(base, "*", "(\\d+)", -1)
	reg := regexp.MustCompile(tmp)
	count := 0
	var filebig float64
	for _, v := range dir {
		if reg.MatchString(v.Name()) {
			count++
			filebig += float64(v.Size())
		}
	}
	fpdst, err := os.Create(dst)
	defer fpdst.Close()
	if err != nil {
		return err
	}
	var sum int64
	for i := 0; i < count; i++ {
		fpsrc, err := os.Open(strings.Replace(src, "*", strconv.Itoa(i), -1))
		if err != nil {
			return err
		}
		for {
			buf := make([]byte, 1048576)
			n, err := fpsrc.Read(buf)
			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}
			sum += int64(n)
			go shell(float64(sum) / filebig * 100)
			fpdst.Write(buf[0:n])
		}
		fpsrc.Close()
	}
	return nil
}

// VicqueEncodeV1 Best！
func VicqueEncodeV1(srcdata []byte, key string) []byte {
	var keys []int
	var saku, piku uint8
	data := make([]byte, len(srcdata))
	copy(data, srcdata)
	binary.Read(rand.Reader, binary.LittleEndian, &saku)
	binary.Read(rand.Reader, binary.LittleEndian, &piku)
	keys = append(keys, len(key)+int(saku))
	lens := len(data)
	for _, v := range key {
		keys = append(keys, int(byte(v))+int(saku)-int(piku))
	}
	lenkey := len(keys)
	for k, v := range data {
		if k == lens/2 {
			break
		}
		nv := int(v)
		t := 0
		if k%2 == 0 {
			nv += keys[k%lenkey]
			if nv > 255 {
				nv -= 256
			}
			t = int(data[lens-1-k])
			t += keys[k%lenkey]
			if t > 255 {
				t -= 256
			}
		} else {
			nv -= keys[k%lenkey]
			if nv < 0 {
				nv += 256
			}
			t = int(data[lens-1-k])
			t -= keys[k%lenkey]
			if t > 255 {
				t += 256
			}
		}
		data[k] = byte(t)
		data[lens-1-k] = byte(nv)
	}
	data = append(data, byte(saku), byte(piku))
	return data
}

// VicqueDecodeV1 Best！
func VicqueDecodeV1(srcdata []byte, key string) []byte {
	var keys []int
	var saku, piku int
	data := make([]byte, len(srcdata))
	copy(data, srcdata)
	lens := len(data)
	saku = int(data[lens-2])
	piku = int(data[lens-1])
	keys = append(keys, len(key)+int(saku))
	for _, v := range key {
		keys = append(keys, int(byte(v))+int(saku)-int(piku))
	}
	lenkey := len(keys)
	lens -= 2
	for k, v := range data {
		if k == lens/2 {
			break
		}
		nv := int(v)
		t := 0
		if k%2 == 0 {
			nv -= keys[k%lenkey]
			if nv < 0 {
				nv += 256
			}
			t = int(data[lens-1-k])
			t -= keys[k%lenkey]
			if t > 255 {
				t += 256
			}
		} else {
			nv += keys[k%lenkey]
			if nv > 255 {
				nv -= 256
			}
			t = int(data[lens-1-k])
			t += keys[k%lenkey]
			if t > 255 {
				t -= 256
			}
		}
		data[k] = byte(t)
		data[lens-1-k] = byte(nv)
	}
	return data[:lens]
}

// VicqueEncodeV1File best
func VicqueEncodeV1File(src, dst, pwd string, shell func(float64)) error {
	fpsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fpsrc.Close()
	stat, _ := os.Stat(src)
	filebig := float64(stat.Size())
	var sum int64
	defer fpsrc.Close()
	fpdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	for {
		buf := make([]byte, 1048576)
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		go shell(float64(sum) / filebig * 100)
		data := VicqueEncodeV1(buf[0:n], pwd)
		fpdst.Write(data)
	}
	return nil
}

// VicqueDecodeV1File best
func VicqueDecodeV1File(src, dst, pwd string, shell func(float64)) error {
	fpsrc, err := os.Open(src)
	if err != nil {
		return err
	}
	defer fpsrc.Close()
	stat, _ := os.Stat(src)
	filebig := float64(stat.Size())
	var sum int64
	defer fpsrc.Close()
	fpdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	for {
		buf := make([]byte, 1048578)
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		go shell(float64(sum) / filebig * 100)
		data := VicqueDecodeV1(buf[0:n], pwd)
		fpdst.Write(data)
	}
	return nil
}

// FillWithRandom 随机写filesize大小的文件，每次buf大小为bufcap，随机bufnum个字符
func FillWithRandom(filepath string, filesize int, bufcap int, bufnum int, shell func(float64)) error {
	var buf [][]byte
	var buftmp []byte
	rands.Seed(time.Now().Unix())
	if bufnum <= 0 {
		bufnum = 1
	}
	if bufcap > filesize {
		bufcap = filesize
	}
	myfile, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer myfile.Close()
	writer := bufio.NewWriter(myfile)
	for i := 0; i < bufnum; i++ {
		buftmp = []byte{}
		for j := 0; j < bufcap; j++ {
			buftmp = append(buftmp, byte(rands.Intn(256)))
		}
		buf = append(buf, buftmp)
	}
	sum := 0
	for {
		if filesize-sum < bufcap {
			writer.Write(buf[rands.Intn(bufnum)][0 : filesize-sum])
			sum += filesize - sum
		} else {
			writer.Write(buf[rands.Intn(bufnum)])
			sum += bufcap
		}
		go shell(float64(sum) / float64(filesize) * 100)
		if sum >= filesize {
			break
		}
	}
	writer.Flush()
	return nil
}

// =================== CFB ======================
func AesEncryptCFB(origData []byte, key []byte) (encrypted []byte) {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	encrypted = make([]byte, aes.BlockSize+len(origData))
	iv := encrypted[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(encrypted[aes.BlockSize:], origData)
	return encrypted
}
func AesDecryptCFB(encrypted []byte, key []byte) (decrypted []byte) {
	block, _ := aes.NewCipher(key)
	if len(encrypted) < aes.BlockSize {
		panic("ciphertext too short")
	}
	iv := encrypted[:aes.BlockSize]
	encrypted = encrypted[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(encrypted, encrypted)
	return encrypted
}

func AesEncryptCFBNoBlock(origData []byte, key []byte) (encrypted []byte) {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	encrypted = make([]byte, len(origData))
	iv := Sha1(key)[:16]
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(encrypted, origData)
	return encrypted
}
func AesDecryptCFBNoBlock(encrypted []byte, key []byte) (decrypted []byte) {
	block, _ := aes.NewCipher(key)
	iv := Sha1(key)[:16]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(encrypted, encrypted)
	return encrypted
}
