package pbkd

import (
	"errors"

	"xdx.jelly/xgcl/sm/sm4"
	"xdx.jelly/xgcl/utils/padding"
)

const (
	ENCRYPT = iota
	DECRYPT
)

type Pbeser interface {
	// EncryptBlocks
	EncryptBlocks(dst, src []byte) ([]byte, error)
	DecryptBlocks(dst, src []byte) ([]byte, error)

	EncryptWithPadding(src []byte) ([]byte, error)
	DecryptWithPadding(src []byte) ([]byte, error)

	ResetIv(iv []byte) error
}

type PbesWithSm4Cbc struct {
	key []byte
	iv  []byte
}

var errOperation = errors.New("error operation")
var errInvalidIV = errors.New("invalid iv length")

func NewPbesWithSm4Cbc(password, salt []byte, count int, iv []byte) (Pbeser, error) {
	key, err := PbkdfWithHmacSm3(password, salt, count, int64(sm4.BlockSize))
	if err != nil {
		return nil, err
	}
	if len(iv) != sm4.BlockSize {
		return nil, errInvalidIV
	}

	myIv := make([]byte, sm4.BlockSize)
	copy(myIv, iv)

	return &PbesWithSm4Cbc{
		key: key,
		iv:  myIv,
	}, nil
}

func (pbes *PbesWithSm4Cbc) EncryptBlocks(dst, src []byte) ([]byte, error) {
	return sm4.EncryptCBC(dst, pbes.iv, pbes.key, src)
}

func (pbes *PbesWithSm4Cbc) DecryptBlocks(dst, src []byte) ([]byte, error) {
	return sm4.EncryptCBC(dst, pbes.iv, pbes.key, src)
}

func (pbes *PbesWithSm4Cbc) EncryptWithPadding(src []byte) ([]byte, error) {
	pad := padding.P7.Pad(src, sm4.BlockSize)
	return sm4.EncryptCBC(nil, pbes.iv, pbes.key, pad)
}

func (pbes *PbesWithSm4Cbc) DecryptWithPadding(src []byte) ([]byte, error) {
	dst, err := sm4.DecryptCBC(nil, pbes.iv, pbes.key, src)
	if err != nil {
		return nil, err
	}

	dst, err = padding.P7.Unpad(dst, sm4.BlockSize)
	if err != nil {
		return nil, err
	}
	return dst, nil
}

func (pbes *PbesWithSm4Cbc) ResetIv(iv []byte) error {
	if len(iv) != sm4.BlockSize {
		return errInvalidIV
	}
	copy(pbes.iv, iv)
	return nil
}
