// Block Chaining operation mode (BC mode) in Chinese national standard GB/T 17964-2021.
// See GB/T 17964-2021 Chapter 12.
package cipher

import (
	_cipher "crypto/cipher"

	"github.com/emmansun/gmsm/internal/subtle"
)

type bc struct {
	b         _cipher.Block
	blockSize int
	iv        []byte
}

func newBC(b _cipher.Block, iv []byte) *bc {
	c := &bc{
		b:         b,
		blockSize: b.BlockSize(),
		iv:        make([]byte, b.BlockSize()),
	}
	copy(c.iv, iv)
	return c
}

type bcEncrypter bc

// bcEncAble is an interface implemented by ciphers that have a specific
// optimized implementation of BC encryption.
// NewBCEncrypter will check for this interface and return the specific
// BlockMode if found.
type bcEncAble interface {
	NewBCEncrypter(iv []byte) _cipher.BlockMode
}

// NewBCEncrypter returns a BlockMode which encrypts in block chaining
// mode, using the given Block. The length of iv must be the same as the
// Block's block size.
func NewBCEncrypter(b _cipher.Block, iv []byte) _cipher.BlockMode {
	if len(iv) != b.BlockSize() {
		panic("cipher.NewBCEncrypter: IV length must equal block size")
	}
	if bc, ok := b.(bcEncAble); ok {
		return bc.NewBCEncrypter(iv)
	}
	return (*bcEncrypter)(newBC(b, iv))
}

func (x *bcEncrypter) BlockSize() int { return x.blockSize }

func (x *bcEncrypter) CryptBlocks(dst, src []byte) {
	validate(x.blockSize, dst, src)

	iv := x.iv

	for len(src) > 0 {
		// Write the xor to dst, then encrypt in place.
		subtle.XORBytes(dst[:x.blockSize], src[:x.blockSize], iv)
		x.b.Encrypt(dst[:x.blockSize], dst[:x.blockSize])
		subtle.XORBytes(iv, iv, dst[:x.blockSize])

		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}

	// Save the iv for the next CryptBlocks call.
	copy(x.iv, iv)
}

func (x *bcEncrypter) SetIV(iv []byte) {
	if len(iv) != len(x.iv) {
		panic("cipher: incorrect length IV")
	}
	copy(x.iv, iv)
}

type bcDecrypter bc

// bcDecAble is an interface implemented by ciphers that have a specific
// optimized implementation of BC decryption.
// NewBCDecrypter will check for this interface and return the specific
// BlockMode if found.
type bcDecAble interface {
	NewBCDecrypter(iv []byte) _cipher.BlockMode
}

// NewBCDecrypter returns a BlockMode which decrypts in block chaining
// mode, using the given Block. The length of iv must be the same as the
// Block's block size and must match the iv used to encrypt the data.
func NewBCDecrypter(b _cipher.Block, iv []byte) _cipher.BlockMode {
	if len(iv) != b.BlockSize() {
		panic("cipher.NewBCDecrypter: IV length must equal block size")
	}
	if bc, ok := b.(bcDecAble); ok {
		return bc.NewBCDecrypter(iv)
	}
	return (*bcDecrypter)(newBC(b, iv))
}

func (x *bcDecrypter) BlockSize() int { return x.blockSize }

func (x *bcDecrypter) CryptBlocks(dst, src []byte) {
	validate(x.blockSize, dst, src)

	if len(src) == 0 {
		return
	}

	iv := x.iv
	nextIV := make([]byte, x.blockSize)

	for len(src) > 0 {
		// Get F(i+1)
		subtle.XORBytes(nextIV, iv, src[:x.blockSize])
		// Get plaintext P(i)
		x.b.Decrypt(dst[:x.blockSize], src[:x.blockSize])
		subtle.XORBytes(dst[:x.blockSize], dst[:x.blockSize], iv)

		copy(iv, nextIV)
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}

	// Save the iv for the next CryptBlocks call.
	copy(x.iv, iv)
}

func (x *bcDecrypter) SetIV(iv []byte) {
	if len(iv) != len(x.iv) {
		panic("cipher: incorrect length IV")
	}
	copy(x.iv, iv)
}
