///
/// Copyright (c) 2018 xdx. All rights reserved.
///
/// \file: sm4.go
///
/// \brief: SM4
///
/// \author: xdx
///

package sm4

// 这里都是SM4小包解密，大包的实现可以参照这里的多次调用CryptBlocks即可。
// 输入的dst可以是较大空间，返回dst的切片将其长度改变
// dstBuf = make([]byte, length, enoughCap)
// dst = ECBEncrypt(dstBuf[:0], key, src)
// now len(dst) = len(src)
// 输入dst是为了不在加密函数中分配空间。调用者可以预先分配好重复使用。
// SM4的相关函数如果确保输入数据长度都是正确的，那么可以不检查返回error

// TODO 大包加密 参照标准库实现。可以节省这里每次调用的轮密钥生成开销

// 如果不知道dst应该输入什么，那就输入nil
// 如果不知道dst应该输入什么，那就输入nil
// 如果不知道dst应该输入什么，那就输入nil

import (
	"crypto/cipher"

	"xdx.jelly/xgcl/gerrors"
	"xdx.jelly/xgcl/internal/modes"
	"xdx.jelly/xgcl/internal/subtle"
	"xdx.jelly/xgcl/internal/tags"
	"xdx.jelly/xgcl/utils"
)

// 取错名字了，用下面的名字
var (
	EncryptECB = ECBEncrypt
	DecryptECB = ECBDecrypt

	EncryptCBC = CBCEncrypt
	DecryptCBC = CBCDecrypt

	EncryptOFB = OFBEncrypt
	DecryptOFB = OFBDecrypt

	EncryptCFB = CFBEncrypt
	DecryptCFB = CFBDecrypt
)

// export ECB BlockMode
var NewECBEncrypter = modes.NewECBEncrypter
var NewECBDecrypter = modes.NewECBDecrypter

// ECBEncrypt len(encData) must exactly equal to len(data)
// or return error
// we not realloc memory for encData!!!
// data和encData要么完全重叠，要么完全不重叠！！！
//
// @param dst[out]	密文预分配空间，可以为nil
// @param key[in]	16字节密钥
// @param src[in]	明文,长度必须是16的倍数，函数不负责padding
// @return []byte	输入密文。
// @note 如果输入dst的空间（cap）比src小，则可能引起内存地址得变化。
// @usage dst = ECBEncrypt(dst, key, src)
func ECBEncrypt(dst, key, src []byte) ([]byte, error) {

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input size must be a multiple of %d", BlockSize)
	}
	// dst = nil, cap(dst) = 0
	if cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	if subtle.InexactOverlap(dst, src) {
		dst = make([]byte, len(src))
	}
	if len(src) == 0 {
		return dst[:0], nil
	}

	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	blockMode := modes.NewECBEncrypter(block)
	blockMode.CryptBlocks(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

// ECBDecrypt 解密
// params:
//
//	dst[in]		明文
//	src[in]		密文，长度必须是16的倍数，函数不负责解padding
//	key[in]		16字节密钥
func ECBDecrypt(dst, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}
	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := modes.NewECBDecrypter(block)
	blockMode.CryptBlocks(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

// CBCEncrypt cbc加密
// 函数返回时iv保存最后一个分组密文，因此对大包数据可以分组多次调用进行加密。
// params:
//
//	dst[out]	密文，输入时分配好长度，必须和data的长度相等
//	iv[in/out]	初始向量, 16字节，返回时是最后一个分组密文
//	key[in]		16字节密钥
//	src[in]		明文,长度必须是16的倍数，函数不负责padding
func CBCEncrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewCBCEncrypter(block, iv)
	blockMode.CryptBlocks(dst, src)

	// The last dst block is the return iv
	if len(dst) > 0 {
		copy(iv, dst[len(dst)-BlockSize:])
	}
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil

}

// CBCDecrypt cbc解密
// 函数返回时iv保存最后一个分组密文，因此对大包数据可以分组多次调用进行加密。
// params:
//
//	dst[out]	密文，输入时分配好长度，必须和data的长度相等
//	iv[in/out]	初始向量, 16字节，返回时是最后一个分组
//	key[in]		16字节密钥
//	src[in]		明文,长度必须是16的倍数，函数不负责padding
func CBCDecrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewCBCDecrypter(block, iv)
	blockMode.CryptBlocks(dst, src)

	// The last src block is the return iv
	if len(src) > 0 {
		copy(iv, src[len(src)-block.BlockSize():])
	}
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

/////////////////////////////////////////////////////
// CFB and OFB, not test carefully.

// CFBEncrypt cfb加密
// params:
//
//	dst[out]	密文，输入时分配好长度，必须和data的长度相等
//	iv[in/out]	初始向量, 16字节
//	key[in]		16字节密钥
//	src[in]		明文,长度必须是16的倍数，函数不负责padding
func CFBEncrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewCFBEncrypter(block, iv)
	blockMode.XORKeyStream(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

// CFBDecrypt ...
func CFBDecrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewCFBDecrypter(block, iv)
	blockMode.XORKeyStream(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

// OFBEncrypt ofb加密
func OFBEncrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4EncFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewOFB(block, iv)
	blockMode.XORKeyStream(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}

// OFBDecrypt ofb解密
func OFBDecrypt(dst, iv, key, src []byte) ([]byte, error) {
	block, err := NewCipher(key)
	if err != nil {
		return dst, err
	}

	if len(iv) != BlockSize {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input IV size must be a multiple of %d", BlockSize)
	}

	if len(src)%BlockSize != 0 {
		return dst, gerrors.WithAnnotatingf(ErrSM4DecFailed, "input size must be a multiple of %d", BlockSize)
	}

	if dst == nil || cap(dst) < len(src) {
		dst = make([]byte, len(src))
	}
	dst = dst[:len(src)]

	blockMode := cipher.NewOFB(block, iv)
	blockMode.XORKeyStream(dst, src)
	if tags.Gmt0028 {
		if c, ok := block.(utils.Clearable); ok {
			c.Clear()
		}
	}
	return dst, nil
}
