package starcrypto

import (
	"encoding/ascii85"
	"encoding/base64"
	"errors"
	"io"
	"os"
)

var (
	// ErrLength is returned from the Decode* methods if the input has an
	// impossible length.
	ErrLength = errors.New("base128: invalid length base128 string")
	// ErrBit is returned from the Decode* methods if the input has a byte with
	// the high-bit set (e.g. 0x80). This will never be the case for strings
	// produced from the Encode* methods in this package.
	ErrBit = errors.New("base128: high bit set in base128 string")
)

// Encoding table holds all the characters for base91 encoding
var enctab = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~'")

// Decoding table maps all the characters back to their integer values
var dectab = map[byte]byte{
	'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
	'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
	'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23,
	'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31,
	'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39,
	'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47,
	'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
	'4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '!': 62, '#': 63,
	'$': 64, '%': 65, '&': 66, '(': 67, ')': 68, '*': 69, '+': 70, ',': 71,
	'.': 72, '/': 73, ':': 74, ';': 75, '<': 76, '=': 77, '>': 78, '?': 79,
	'@': 80, '[': 81, ']': 82, '^': 83, '_': 84, '`': 85, '{': 86, '|': 87,
	'}': 88, '~': 89, '\'': 90,
}

// Base91EncodeToString encodes the given byte array and returns a string
func Base91EncodeToString(d []byte) string {
	return string(Base91Encode(d))
}

// Base91Encode returns the base91 encoded string
func Base91Encode(d []byte) []byte {
	var n, b uint
	var o []byte

	for i := 0; i < len(d); i++ {
		b |= uint(d[i]) << n
		n += 8
		if n > 13 {
			v := b & 8191
			if v > 88 {
				b >>= 13
				n -= 13
			} else {
				v = b & 16383
				b >>= 14
				n -= 14
			}
			o = append(o, enctab[v%91], enctab[v/91])
		}
	}
	if n > 0 {
		o = append(o, enctab[b%91])
		if n > 7 || b > 90 {
			o = append(o, enctab[b/91])
		}
	}
	return o
}

// Base91DecodeToString decodes a given byte array are returns a string
func Base91DecodeString(d string) []byte {
	return Base91Decode([]byte(d))
}

// Base91Decode decodes a base91 encoded string and returns the result
func Base91Decode(d []byte) []byte {
	var b, n uint
	var o []byte
	v := -1

	for i := 0; i < len(d); i++ {
		c, ok := dectab[d[i]]
		if !ok {
			continue
		}
		if v < 0 {
			v = int(c)
		} else {
			v += int(c) * 91
			b |= uint(v) << n
			if v&8191 > 88 {
				n += 13
			} else {
				n += 14
			}
			o = append(o, byte(b&255))
			b >>= 8
			n -= 8
			for n > 7 {
				o = append(o, byte(b&255))
				b >>= 8
				n -= 8
			}
			v = -1
		}
	}
	if v+1 > 0 {
		o = append(o, byte((b|uint(v)<<n)&255))
	}
	return o
}

// Base128Encode encodes src into EncodedLen(len(src)) bytes of dst. As a convenience,
// it returns the number of bytes written to dst, but this value is always
// EncodedLen(len(src)).
//
// Encode implements base128 encoding.
func Base128Encode(dst, src []byte) int {
	ret := Base128EncodedLen(len(src))
	if len(dst) < ret {
		panic("dst has insufficient length")
	}
	dst = dst[:0]
	whichByte := uint(1)
	bufByte := byte(0)
	for _, v := range src {
		dst = append(dst, bufByte|(v>>whichByte))
		bufByte = (v&(1<<whichByte) - 1) << (7 - whichByte)
		if whichByte == 7 {
			dst = append(dst, bufByte)
			bufByte = 0
			whichByte = 0
		}
		whichByte++
	}
	dst = append(dst, bufByte)
	return ret
}

// Base128Decode decodes src into DecodedLen(len(src)) bytes, returning the actual
// number of bytes written to dst.
//
// If Decode encounters invalid input, it returns an error describing the
// failure.
func Base128Decode(dst, src []byte) (int, error) {
	dLen := Base128DecodedLen(len(src))
	if Base128EncodedLen(dLen) != len(src) {
		return 0, ErrLength
	}
	if len(dst) < dLen {
		panic("dst has insufficient length")
	}
	dst = dst[:0]
	whichByte := uint(1)
	bufByte := byte(0)
	for _, v := range src {
		if (v & 0x80) != 0 {
			return len(dst), ErrBit
		}
		if whichByte > 1 {
			dst = append(dst, bufByte|(v>>(8-whichByte)))
		}
		bufByte = v << whichByte
		if whichByte == 8 {
			whichByte = 0
		}
		whichByte++
	}
	return len(dst), nil
}

// Base128DecodeString returns the bytes represented by the base128 string s.
func Base128DecodeString(s string) ([]byte, error) {
	src := []byte(s)
	dst := make([]byte, Base128DecodedLen(len(src)))
	if _, err := Base128Decode(dst, src); err != nil {
		return nil, err
	}
	return dst, nil
}

// Base128DecodedLen returns the number of bytes `encLen` encoded bytes decodes to.
func Base128DecodedLen(encLen int) int {
	return (encLen * 7 / 8)
}

// Base128EncodedLen returns the number of bytes that `dataLen` bytes will encode to.
func Base128EncodedLen(dataLen int) int {
	return (((dataLen * 8) + 6) / 7)
}

// Base128EncodeToString returns the base128 encoding of src.
func Base128EncodeToString(src []byte) string {
	dst := make([]byte, Base128EncodedLen(len(src)))
	Base128Encode(dst, src)
	return string(dst)
}

// Base64Encode 输出格式化后的Base64字符串
func Base64Encode(bstr []byte) string {
	return base64.StdEncoding.EncodeToString(bstr)
}

// Base64Decode 输出解密前的Base64数据
func Base64Decode(str string) ([]byte, error) {
	return base64.StdEncoding.DecodeString(str)
}

// Base85Encode 输出格式化后的Base85字符串
func Base85Encode(bstr []byte) string {
	var rtn []byte
	rtn = make([]byte, ascii85.MaxEncodedLen(len(bstr)))
	ascii85.Encode(rtn, bstr)
	return string(rtn)
}

// Base85Decode 输出解密前的Base85数据
func Base85Decode(str string) ([]byte, error) {
	var rtn []byte
	rtn = make([]byte, len(str))
	_, _, err := ascii85.Decode(rtn, []byte(str), true)
	return rtn, err
}

// Base85EncodeFile 用base85方法编码src文件到dst文件中去，shell传入当前进度
func Base85EncodeFile(src, dst 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
	fpdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	b85 := ascii85.NewEncoder(fpdst)
	defer b85.Close()
	for {
		buf := make([]byte, 1024000)
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		go shell(float64(sum) / filebig * 100)
		b85.Write(buf[0:n])
	}
	return nil
}

// Base85DecodeFile 用base85方法解码src文件到dst文件中去，shell传入当前进度
func Base85DecodeFile(src, dst 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()
	b85 := ascii85.NewDecoder(fpsrc)
	for {
		buf := make([]byte, 1280000)
		n, err := b85.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		per := float64(sum) / filebig * 100 / 4.0 * 5.0
		if per >= 100 {
			per = 100
		}
		go shell(per)
		fpdst.Write(buf[0:n])
	}
	return nil
}

// Base64EncodeFile 用base64方法编码src文件到dst文件中去，shell传入当前进度
func Base64EncodeFile(src, dst 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 = 0
	fpdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	b64 := base64.NewEncoder(base64.StdEncoding, fpdst)
	defer b64.Close()
	for {
		buf := make([]byte, 1048575)
		n, err := fpsrc.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		go shell(float64(sum) / filebig * 100)
		b64.Write(buf[0:n])
	}
	return nil
}

// Base64DecodeFile 用base64方法解码src文件到dst文件中去，shell传入当前进度
func Base64DecodeFile(src, dst 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 = 0
	defer fpsrc.Close()
	fpdst, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer fpdst.Close()
	b64 := base64.NewDecoder(base64.StdEncoding, fpsrc)
	for {
		buf := make([]byte, 1048576)
		n, err := b64.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}
		sum += int64(n)
		per := float64(sum) / filebig * 100 / 3.0 * 4.0
		if per >= 100 {
			per = 100
		}
		go shell(per)
		fpdst.Write(buf[0:n])
	}
	return nil
}
