// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.15 or later.

package excelize

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"encoding/binary"
	"encoding/xml"
	"hash"
	"math"
	"reflect"
	"strings"

	"github.com/richardlehane/mscfb"
	"golang.org/x/crypto/md4"
	"golang.org/x/crypto/ripemd160"
	"golang.org/x/text/encoding/unicode"
)

var (
	blockKey                   = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption
	oleIdentifier              = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
	iterCount                  = 50000
	packageEncryptionChunkSize = 4096
	packageOffset              = 8 // First 8 bytes are the size of the stream
	sheetProtectionSpinCount   = 1e5
)

// Encryption specifies the encryption structure, streams, and storages are
// required when encrypting ECMA-376 documents.
type Encryption struct {
	XMLName       xml.Name      `xml:"encryption"`
	KeyData       KeyData       `xml:"keyData"`
	DataIntegrity DataIntegrity `xml:"dataIntegrity"`
	KeyEncryptors KeyEncryptors `xml:"keyEncryptors"`
}

// KeyData specifies the cryptographic attributes used to encrypt the data.
type KeyData struct {
	SaltSize        int    `xml:"saltSize,attr"`
	BlockSize       int    `xml:"blockSize,attr"`
	KeyBits         int    `xml:"keyBits,attr"`
	HashSize        int    `xml:"hashSize,attr"`
	CipherAlgorithm string `xml:"cipherAlgorithm,attr"`
	CipherChaining  string `xml:"cipherChaining,attr"`
	HashAlgorithm   string `xml:"hashAlgorithm,attr"`
	SaltValue       string `xml:"saltValue,attr"`
}

// DataIntegrity specifies the encrypted copies of the salt and hash values
// used to help ensure that the integrity of the encrypted data has not been
// compromised.
type DataIntegrity struct {
	EncryptedHmacKey   string `xml:"encryptedHmacKey,attr"`
	EncryptedHmacValue string `xml:"encryptedHmacValue,attr"`
}

// KeyEncryptors specifies the key encryptors used to encrypt the data.
type KeyEncryptors struct {
	KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"`
}

// KeyEncryptor specifies that the schema used by this encryptor is the schema
// specified for password-based encryptors.
type KeyEncryptor struct {
	XMLName      xml.Name     `xml:"keyEncryptor"`
	URI          string       `xml:"uri,attr"`
	EncryptedKey EncryptedKey `xml:"encryptedKey"`
}

// EncryptedKey used to generate the encrypting key.
type EncryptedKey struct {
	XMLName                    xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"`
	SpinCount                  int      `xml:"spinCount,attr"`
	EncryptedVerifierHashInput string   `xml:"encryptedVerifierHashInput,attr"`
	EncryptedVerifierHashValue string   `xml:"encryptedVerifierHashValue,attr"`
	EncryptedKeyValue          string   `xml:"encryptedKeyValue,attr"`
	KeyData
}

// StandardEncryptionHeader structure is used by ECMA-376 document encryption
// [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify
// encryption properties for an encrypted stream.
type StandardEncryptionHeader struct {
	Flags        uint32
	SizeExtra    uint32
	AlgID        uint32
	AlgIDHash    uint32
	KeySize      uint32
	ProviderType uint32
	Reserved1    uint32
	Reserved2    uint32
	CspName      string
}

// StandardEncryptionVerifier structure is used by Office Binary Document RC4
// CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this
// structure MUST specify the hashing algorithm and encryption algorithm used
// in the EncryptionVerifier structure.
type StandardEncryptionVerifier struct {
	SaltSize              uint32
	Salt                  []byte
	EncryptedVerifier     []byte
	VerifierHashSize      uint32
	EncryptedVerifierHash []byte
}

// encryptionInfo structure is used for standard encryption with SHA1
// cryptographic algorithm.
type encryption struct {
	BlockSize, SaltSize                                                                  int
	EncryptedKeyValue, EncryptedVerifierHashInput, EncryptedVerifierHashValue, SaltValue []byte
	KeyBits                                                                              uint32
}

// Decrypt API decrypts the CFB file format with ECMA-376 agile encryption and
// standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160,
// SHA1, SHA256, SHA384 and SHA512 currently.
func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
	doc, err := mscfb.New(bytes.NewReader(raw))
	if err != nil {
		return
	}
	encryptionInfoBuf, encryptedPackageBuf := extractPart(doc)
	mechanism, err := encryptionMechanism(encryptionInfoBuf)
	if err != nil || mechanism == "extensible" {
		return
	}
	if mechanism == "agile" {
		return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
	}
	return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
}

// Encrypt API encrypt data with the password.
func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
	encryptor := encryption{
		EncryptedVerifierHashInput: make([]byte, 16),
		EncryptedVerifierHashValue: make([]byte, 32),
		SaltValue:                  make([]byte, 16),
		BlockSize:                  16,
		KeyBits:                    128,
		SaltSize:                   16,
	}
	// Key Encryption
	encryptionInfoBuffer, err := encryptor.standardKeyEncryption(opt.Password)
	if err != nil {
		return nil, err
	}
	// Package Encryption
	encryptedPackage := make([]byte, 8)
	binary.LittleEndian.PutUint64(encryptedPackage, uint64(len(raw)))
	encryptedPackage = append(encryptedPackage, encryptor.encrypt(raw)...)
	// Create a new CFB
	compoundFile := cfb{}
	packageBuf = compoundFile.Writer(encryptionInfoBuffer, encryptedPackage)
	return packageBuf, nil
}

// extractPart extract data from storage by specified part name.
func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) {
	for entry, err := doc.Next(); err == nil; entry, err = doc.Next() {
		switch entry.Name {
		case "EncryptionInfo":
			buf := make([]byte, entry.Size)
			i, _ := doc.Read(buf)
			if i > 0 {
				encryptionInfoBuf = buf
			}
		case "EncryptedPackage":
			buf := make([]byte, entry.Size)
			i, _ := doc.Read(buf)
			if i > 0 {
				encryptedPackageBuf = buf
			}
		}
	}
	return
}

// encryptionMechanism parse password-protected documents created mechanism.
func encryptionMechanism(buffer []byte) (mechanism string, err error) {
	if len(buffer) < 4 {
		err = ErrUnknownEncryptMechanism
		return
	}
	versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4])
	if versionMajor == 4 && versionMinor == 4 {
		mechanism = "agile"
		return
	} else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 {
		mechanism = "standard"
		return
	} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
		mechanism = "extensible"
	}
	err = ErrUnsupportedEncryptMechanism
	return
}

// ECMA-376 Standard Encryption

// standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption.
func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) ([]byte, error) {
	encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12])
	block := encryptionInfoBuf[12 : 12+encryptionHeaderSize]
	header := StandardEncryptionHeader{
		Flags:        binary.LittleEndian.Uint32(block[:4]),
		SizeExtra:    binary.LittleEndian.Uint32(block[4:8]),
		AlgID:        binary.LittleEndian.Uint32(block[8:12]),
		AlgIDHash:    binary.LittleEndian.Uint32(block[12:16]),
		KeySize:      binary.LittleEndian.Uint32(block[16:20]),
		ProviderType: binary.LittleEndian.Uint32(block[20:24]),
		Reserved1:    binary.LittleEndian.Uint32(block[24:28]),
		Reserved2:    binary.LittleEndian.Uint32(block[28:32]),
		CspName:      string(block[32:]),
	}
	block = encryptionInfoBuf[12+encryptionHeaderSize:]
	algIDMap := map[uint32]string{
		0x0000660E: "AES-128",
		0x0000660F: "AES-192",
		0x00006610: "AES-256",
	}
	algorithm := "AES"
	_, ok := algIDMap[header.AlgID]
	if !ok {
		algorithm = "RC4"
	}
	verifier := standardEncryptionVerifier(algorithm, block)
	secretKey, err := standardConvertPasswdToKey(header, verifier, opt)
	if err != nil {
		return nil, err
	}
	// decrypted data
	x := encryptedPackageBuf[8:]
	blob, err := aes.NewCipher(secretKey)
	if err != nil {
		return nil, err
	}
	decrypted := make([]byte, len(x))
	size := 16
	for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size {
		blob.Decrypt(decrypted[bs:be], x[bs:be])
	}
	return decrypted, err
}

// standardEncryptionVerifier extract ECMA-376 standard encryption verifier.
func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier {
	verifier := StandardEncryptionVerifier{
		SaltSize:          binary.LittleEndian.Uint32(blob[:4]),
		Salt:              blob[4:20],
		EncryptedVerifier: blob[20:36],
		VerifierHashSize:  binary.LittleEndian.Uint32(blob[36:40]),
	}
	if algorithm == "RC4" {
		verifier.EncryptedVerifierHash = blob[40:60]
	} else if algorithm == "AES" {
		verifier.EncryptedVerifierHash = blob[40:72]
	}
	return verifier
}

// standardConvertPasswdToKey generate intermediate key from given password.
func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opt *Options) ([]byte, error) {
	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
	passwordBuffer, err := encoder.Bytes([]byte(opt.Password))
	if err != nil {
		return nil, err
	}
	key := hashing("sha1", verifier.Salt, passwordBuffer)
	for i := 0; i < iterCount; i++ {
		iterator := createUInt32LEBuffer(i, 4)
		key = hashing("sha1", iterator, key)
	}
	var block int
	hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
	cbRequiredKeyLength := int(header.KeySize) / 8
	cbHash := sha1.Size
	buf1 := bytes.Repeat([]byte{0x36}, 64)
	buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...)
	x1 := hashing("sha1", buf1)
	buf2 := bytes.Repeat([]byte{0x5c}, 64)
	buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...)
	x2 := hashing("sha1", buf2)
	x3 := append(x1, x2...)
	keyDerived := x3[:cbRequiredKeyLength]
	return keyDerived, err
}

// standardXORBytes perform XOR operations for two bytes slice.
func standardXORBytes(a, b []byte) []byte {
	r := make([][2]byte, len(a))
	for i, e := range a {
		r[i] = [2]byte{e, b[i]}
	}
	buf := make([]byte, len(a))
	for p, q := range r {
		buf[p] = q[0] ^ q[1]
	}
	return buf
}

// encrypt provides a function to encrypt given value with AES cryptographic
// algorithm.
func (e *encryption) encrypt(input []byte) []byte {
	inputBytes := len(input)
	if pad := inputBytes % e.BlockSize; pad != 0 {
		inputBytes += e.BlockSize - pad
	}
	var output, chunk []byte
	encryptedChunk := make([]byte, e.BlockSize)
	for i := 0; i < inputBytes; i += e.BlockSize {
		if i+e.BlockSize <= len(input) {
			chunk = input[i : i+e.BlockSize]
		} else {
			chunk = input[i:]
		}
		chunk = append(chunk, make([]byte, e.BlockSize-len(chunk))...)
		c, _ := aes.NewCipher(e.EncryptedKeyValue)
		c.Encrypt(encryptedChunk, chunk)
		output = append(output, encryptedChunk...)
	}
	return output
}

// standardKeyEncryption encrypt convert the password to an encryption key.
func (e *encryption) standardKeyEncryption(password string) ([]byte, error) {
	if len(password) == 0 || len(password) > MaxFieldLength {
		return nil, ErrPasswordLengthInvalid
	}
	var storage cfb
	storage.writeUint16(0x0003)
	storage.writeUint16(0x0002)
	storage.writeUint32(0x24)
	storage.writeUint32(0xA4)
	storage.writeUint32(0x24)
	storage.writeUint32(0x00)
	storage.writeUint32(0x660E)
	storage.writeUint32(0x8004)
	storage.writeUint32(0x80)
	storage.writeUint32(0x18)
	storage.writeUint64(0x00)
	providerName := "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
	storage.writeStrings(providerName)
	storage.writeUint16(0x00)
	storage.writeUint32(0x10)
	keyDataSaltValue, _ := randomBytes(16)
	verifierHashInput, _ := randomBytes(16)
	e.SaltValue = keyDataSaltValue
	e.EncryptedKeyValue, _ = standardConvertPasswdToKey(
		StandardEncryptionHeader{KeySize: e.KeyBits},
		StandardEncryptionVerifier{Salt: e.SaltValue},
		&Options{Password: password})
	verifierHashInputKey := hashing("sha1", verifierHashInput)
	e.EncryptedVerifierHashInput = e.encrypt(verifierHashInput)
	e.EncryptedVerifierHashValue = e.encrypt(verifierHashInputKey)
	storage.writeBytes(e.SaltValue)
	storage.writeBytes(e.EncryptedVerifierHashInput)
	storage.writeUint32(0x14)
	storage.writeBytes(e.EncryptedVerifierHashValue)
	storage.position = 0
	return storage.stream, nil
}

// ECMA-376 Agile Encryption

// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256,
// SHA384 and SHA512.
func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) {
	var encryptionInfo Encryption
	if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
		return
	}
	// Convert the password into an encryption key.
	key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo)
	if err != nil {
		return
	}
	// Use the key to decrypt the package key.
	encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey
	saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
	if err != nil {
		return
	}
	encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue)
	if err != nil {
		return
	}
	packageKey, _ := decrypt(key, saltValue, encryptedKeyValue)
	// Use the package key to decrypt the package.
	return decryptPackage(packageKey, encryptedPackageBuf, encryptionInfo)
}

// convertPasswdToKey convert the password into an encryption key.
func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) {
	var b bytes.Buffer
	saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue)
	if err != nil {
		return
	}
	b.Write(saltValue)
	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
	passwordBuffer, err := encoder.Bytes([]byte(passwd))
	if err != nil {
		return
	}
	b.Write(passwordBuffer)
	// Generate the initial hash.
	key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes())
	// Now regenerate until spin count.
	for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ {
		iterator := createUInt32LEBuffer(i, 4)
		key = hashing(encryption.KeyData.HashAlgorithm, iterator, key)
	}
	// Now generate the final hash.
	key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey)
	// Truncate or pad as needed to get to length of keyBits.
	keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8
	if len(key) < keyBytes {
		tmp := make([]byte, 0x36)
		key = append(key, tmp...)
	} else if len(key) > keyBytes {
		key = key[:keyBytes]
	}
	return
}

// hashing data by specified hash algorithm.
func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
	hashMap := map[string]hash.Hash{
		"md4":        md4.New(),
		"md5":        md5.New(),
		"ripemd-160": ripemd160.New(),
		"sha1":       sha1.New(),
		"sha256":     sha256.New(),
		"sha384":     sha512.New384(),
		"sha512":     sha512.New(),
	}
	handler, ok := hashMap[strings.ToLower(hashAlgorithm)]
	if !ok {
		return key
	}
	for _, buf := range buffer {
		_, _ = handler.Write(buf)
	}
	key = handler.Sum(nil)
	return key
}

// createUInt32LEBuffer create buffer with little endian 32-bit unsigned
// integer.
func createUInt32LEBuffer(value int, bufferSize int) []byte {
	buf := make([]byte, bufferSize)
	binary.LittleEndian.PutUint32(buf, uint32(value))
	return buf
}

// parseEncryptionInfo parse the encryption info XML into an object.
func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) {
	err = xml.Unmarshal(encryptionInfo, &encryption)
	return
}

// decrypt provides a function to decrypt input by given cipher algorithm,
// cipher chaining, key and initialization vector.
func decrypt(key, iv, input []byte) (packageKey []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return input, err
	}
	cipher.NewCBCDecrypter(block, iv).CryptBlocks(input, input)
	return input, nil
}

// decryptPackage decrypt package by given packageKey and encryption
// info.
func decryptPackage(packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
	encryptedKey, offset := encryption.KeyData, packageOffset
	var i, start, end int
	var iv, outputChunk []byte
	for end < len(input) {
		start = end
		end = start + packageEncryptionChunkSize

		if end > len(input) {
			end = len(input)
		}
		// Grab the next chunk
		var inputChunk []byte
		if (end + offset) < len(input) {
			inputChunk = input[start+offset : end+offset]
		} else {
			inputChunk = input[start+offset : end]
		}

		// Pad the chunk if it is not an integer multiple of the block size
		remainder := len(inputChunk) % encryptedKey.BlockSize
		if remainder != 0 {
			inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...)
		}
		// Create the initialization vector
		iv, err = createIV(i, encryption)
		if err != nil {
			return
		}
		// Decrypt the chunk and add it to the array
		outputChunk, err = decrypt(packageKey, iv, inputChunk)
		if err != nil {
			return
		}
		outputChunks = append(outputChunks, outputChunk...)
		i++
	}
	return
}

// createIV create an initialization vector (IV).
func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
	encryptedKey := encryption.KeyData
	// Create the block key from the current index
	var blockKeyBuf []byte
	if reflect.TypeOf(blockKey).Kind() == reflect.Int {
		blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4)
	} else {
		blockKeyBuf = blockKey.([]byte)
	}
	saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue)
	if err != nil {
		return nil, err
	}
	// Create the initialization vector by hashing the salt with the block key.
	// Truncate or pad as needed to meet the block size.
	iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...))
	if len(iv) < encryptedKey.BlockSize {
		tmp := make([]byte, 0x36)
		iv = append(iv, tmp...)
	} else if len(iv) > encryptedKey.BlockSize {
		iv = iv[:encryptedKey.BlockSize]
	}
	return iv, nil
}

// randomBytes returns securely generated random bytes. It will return an
// error if the system's secure random number generator fails to function
// correctly, in which case the caller should not continue.
func randomBytes(n int) ([]byte, error) {
	b := make([]byte, n)
	_, err := rand.Read(b)
	return b, err
}

// ISO Write Protection Method

// genISOPasswdHash implements the ISO password hashing algorithm by given
// plaintext password, name of the cryptographic hash algorithm, salt value
// and spin count.
func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) {
	if len(passwd) < 1 || len(passwd) > MaxFieldLength {
		err = ErrPasswordLengthInvalid
		return
	}
	algorithmName, ok := map[string]string{
		"MD4":     "md4",
		"MD5":     "md5",
		"SHA-1":   "sha1",
		"SHA-256": "sha256",
		"SHA-384": "sha384",
		"SHA-512": "sha512",
	}[hashAlgorithm]
	if !ok {
		err = ErrUnsupportedHashAlgorithm
		return
	}
	var b bytes.Buffer
	s, _ := randomBytes(16)
	if salt != "" {
		if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
			return
		}
	}
	b.Write(s)
	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
	passwordBuffer, _ := encoder.Bytes([]byte(passwd))
	b.Write(passwordBuffer)
	// Generate the initial hash.
	key := hashing(algorithmName, b.Bytes())
	// Now regenerate until spin count.
	for i := 0; i < spinCount; i++ {
		iterator := createUInt32LEBuffer(i, 4)
		key = hashing(algorithmName, key, iterator)
	}
	hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
	return
}

// Compound File Binary Implements

// cfb structure is used for the compound file binary (CFB) file format writer.
type cfb struct {
	stream   []byte
	position int
}

// writeBytes write bytes in the stream by a given value with an offset.
func (c *cfb) writeBytes(value []byte) {
	pos := c.position
	for i := 0; i < len(value); i++ {
		for j := len(c.stream); j <= i+pos; j++ {
			c.stream = append(c.stream, 0)
		}
		c.stream[i+pos] = value[i]
	}
	c.position = pos + len(value)
}

// writeUint16 write an uint16 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint16(value int) {
	buf := make([]byte, 2)
	binary.LittleEndian.PutUint16(buf, uint16(value))
	c.writeBytes(buf)
}

// writeUint32 write an uint32 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint32(value int) {
	buf := make([]byte, 4)
	binary.LittleEndian.PutUint32(buf, uint32(value))
	c.writeBytes(buf)
}

// writeUint64 write an uint64 data type bytes in the stream by a given value
// with an offset.
func (c *cfb) writeUint64(value int) {
	buf := make([]byte, 8)
	binary.LittleEndian.PutUint64(buf, uint64(value))
	c.writeBytes(buf)
}

// writeBytes write strings in the stream by a given value with an offset.
func (c *cfb) writeStrings(value string) {
	encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
	buffer, err := encoder.Bytes([]byte(value))
	if err != nil {
		return
	}
	c.writeBytes(buffer)
}

// writeVersionStream provides a function to write compound file version
// stream.
func (c *cfb) writeVersionStream() []byte {
	var storage cfb
	storage.writeUint32(0x3c)
	storage.writeStrings("Microsoft.Container.DataSpaces")
	storage.writeUint32(0x01)
	storage.writeUint32(0x01)
	storage.writeUint32(0x01)
	return storage.stream
}

// writeDataSpaceMapStream provides a function to write compound file
// DataSpaceMap stream.
func (c *cfb) writeDataSpaceMapStream() []byte {
	var storage cfb
	storage.writeUint32(0x08)
	storage.writeUint32(0x01)
	storage.writeUint32(0x68)
	storage.writeUint32(0x01)
	storage.writeUint32(0x00)
	storage.writeUint32(0x20)
	storage.writeStrings("EncryptedPackage")
	storage.writeUint32(0x32)
	storage.writeStrings("StrongEncryptionDataSpace")
	storage.writeUint16(0x00)
	return storage.stream
}

// writeStrongEncryptionDataSpaceStream provides a function to write compound
// file StrongEncryptionDataSpace stream.
func (c *cfb) writeStrongEncryptionDataSpaceStream() []byte {
	var storage cfb
	storage.writeUint32(0x08)
	storage.writeUint32(0x01)
	storage.writeUint32(0x32)
	storage.writeStrings("StrongEncryptionTransform")
	storage.writeUint16(0x00)
	return storage.stream
}

// writePrimaryStream provides a function to write compound file Primary
// stream.
func (c *cfb) writePrimaryStream() []byte {
	var storage cfb
	storage.writeUint32(0x6C)
	storage.writeUint32(0x01)
	storage.writeUint32(0x4C)
	storage.writeStrings("{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}")
	storage.writeUint32(0x4E)
	storage.writeUint16(0x00)
	storage.writeUint32(0x01)
	storage.writeUint32(0x01)
	storage.writeUint32(0x01)
	storage.writeStrings("AES128")
	storage.writeUint32(0x00)
	storage.writeUint32(0x04)
	return storage.stream
}

// writeFileStream provides a function to write encrypted package in compound
// file by a given buffer and the short sector allocation table.
func (c *cfb) writeFileStream(encryptionInfoBuffer []byte, SSAT []int) ([]byte, []int) {
	var (
		storage        cfb
		miniProperties int
		stream         = make([]byte, 0x100)
	)
	if encryptionInfoBuffer != nil {
		copy(stream, encryptionInfoBuffer)
	}
	storage.writeBytes(stream)
	streamBlocks := len(stream) / 64
	if len(stream)%64 > 0 {
		streamBlocks++
	}
	for i := 1; i < streamBlocks; i++ {
		SSAT = append(SSAT, i)
	}
	SSAT = append(SSAT, -2)
	miniProperties += streamBlocks
	versionStream := make([]byte, 0x80)
	version := c.writeVersionStream()
	copy(versionStream, version)
	storage.writeBytes(versionStream)
	versionBlocks := len(versionStream) / 64
	if len(versionStream)%64 > 0 {
		versionBlocks++
	}
	for i := 1; i < versionBlocks; i++ {
		SSAT = append(SSAT, i+miniProperties)
	}
	SSAT = append(SSAT, -2)
	miniProperties += versionBlocks
	dataSpaceMap := make([]byte, 0x80)
	dataStream := c.writeDataSpaceMapStream()
	copy(dataSpaceMap, dataStream)
	storage.writeBytes(dataSpaceMap)
	dataSpaceMapBlocks := len(dataSpaceMap) / 64
	if len(dataSpaceMap)%64 > 0 {
		dataSpaceMapBlocks++
	}
	for i := 1; i < dataSpaceMapBlocks; i++ {
		SSAT = append(SSAT, i+miniProperties)
	}
	SSAT = append(SSAT, -2)
	miniProperties += dataSpaceMapBlocks
	dataSpaceStream := c.writeStrongEncryptionDataSpaceStream()
	storage.writeBytes(dataSpaceStream)
	dataSpaceStreamBlocks := len(dataSpaceStream) / 64
	if len(dataSpaceStream)%64 > 0 {
		dataSpaceStreamBlocks++
	}
	for i := 1; i < dataSpaceStreamBlocks; i++ {
		SSAT = append(SSAT, i+miniProperties)
	}
	SSAT = append(SSAT, -2)
	miniProperties += dataSpaceStreamBlocks
	primaryStream := make([]byte, 0x1C0)
	primary := c.writePrimaryStream()
	copy(primaryStream, primary)
	storage.writeBytes(primaryStream)
	primaryBlocks := len(primary) / 64
	if len(primary)%64 > 0 {
		primaryBlocks++
	}
	for i := 1; i < primaryBlocks; i++ {
		SSAT = append(SSAT, i+miniProperties)
	}
	SSAT = append(SSAT, -2)
	if len(SSAT) < 128 {
		for i := len(SSAT); i < 128; i++ {
			SSAT = append(SSAT, -1)
		}
	}
	storage.position = 0
	return storage.stream, SSAT
}

// writeRootEntry provides a function to write compound file root directory
// entry. The first entry in the first sector of the directory chain
// (also referred to as the first element of the directory array, or stream
// ID #0) is known as the root directory entry, and it is reserved for two
// purposes. First, it provides a root parent for all objects that are
// stationed at the root of the compound file. Second, its function is
// overloaded to store the size and starting sector for the mini stream.
func (c *cfb) writeRootEntry(customSectID int) []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("Root Entry")
	storage.position = 0x40
	storage.writeUint16(0x16)
	storage.writeBytes([]byte{5, 0})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(customSectID)
	storage.writeUint32(0x340)
	return storage.stream
}

// writeEncryptionInfo provides a function to write compound file
// writeEncryptionInfo stream. The writeEncryptionInfo stream contains
// detailed information that is used to initialize the cryptography used to
// encrypt the EncryptedPackage stream.
func (c *cfb) writeEncryptionInfo() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("EncryptionInfo")
	storage.position = 0x40
	storage.writeUint16(0x1E)
	storage.writeBytes([]byte{2, 1})
	storage.writeUint32(0x03)
	storage.writeUint32(0x02)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0xF8)
	return storage.stream
}

// writeEncryptedPackage provides a function to write compound file
// writeEncryptedPackage stream. The writeEncryptedPackage stream is an
// encrypted stream of bytes containing the entire ECMA-376 source file in
// compressed form.
func (c *cfb) writeEncryptedPackage(propertyCount, size int) []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("EncryptedPackage")
	storage.position = 0x40
	storage.writeUint16(0x22)
	storage.writeBytes([]byte{2, 0})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(propertyCount)
	storage.writeUint32(size)
	return storage.stream
}

// writeDataSpaces provides a function to write compound file writeDataSpaces
// stream. The data spaces structure consists of a set of interrelated
// storages and streams in an OLE compound file.
func (c *cfb) writeDataSpaces() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeUint16(0x06)
	storage.position = 0x40
	storage.writeUint16(0x18)
	storage.writeBytes([]byte{1, 0})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(5)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	return storage.stream
}

// writeVersion provides a function to write compound file version. The
// writeVersion structure specifies the version of a product or feature. It
// contains a major and a minor version number.
func (c *cfb) writeVersion() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("Version")
	storage.position = 0x40
	storage.writeUint16(0x10)
	storage.writeBytes([]byte{2, 1})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(4)
	storage.writeUint32(76)
	return storage.stream
}

// writeDataSpaceMap provides a function to write compound file
// writeDataSpaceMap stream. The writeDataSpaceMap structure associates
// protected content with data space definitions. The data space definition,
// in turn, describes the series of transforms that MUST be applied to that
// protected content to restore it to its original form. By using a map to
// associate data space definitions with content, a single data space
// definition can be used to define the transforms applied to more than one
// piece of protected content. However, a given piece of protected content can
// be referenced only by a single data space definition.
func (c *cfb) writeDataSpaceMap() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("DataSpaceMap")
	storage.position = 0x40
	storage.writeUint16(0x1A)
	storage.writeBytes([]byte{2, 1})
	storage.writeUint32(0x04)
	storage.writeUint32(0x06)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(6)
	storage.writeUint32(112)
	return storage.stream
}

// writeDataSpaceInfo provides a function to write compound file
// writeDataSpaceInfo storage. The writeDataSpaceInfo is a storage containing
// the data space definitions used in the file. This storage must contain one
// or more streams, each of which contains a DataSpaceDefinition structure.
// The storage must contain exactly one stream for each DataSpaceMapEntry
// structure in the DataSpaceMap stream. The name of each stream must be equal
// to the DataSpaceName field of exactly one DataSpaceMapEntry structure
// contained in the DataSpaceMap stream.
func (c *cfb) writeDataSpaceInfo() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("DataSpaceInfo")
	storage.position = 0x40
	storage.writeUint16(0x1C)
	storage.writeBytes([]byte{1, 1})
	storage.writeUint32(-1)
	storage.writeUint32(8)
	storage.writeUint32(7)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	return storage.stream
}

// writeStrongEncryptionDataSpace provides a function to write compound file
// writeStrongEncryptionDataSpace stream.
func (c *cfb) writeStrongEncryptionDataSpace() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("StrongEncryptionDataSpace")
	storage.position = 0x40
	storage.writeUint16(0x34)
	storage.writeBytes([]byte{2, 1})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(8)
	storage.writeUint32(64)
	return storage.stream
}

// writeTransformInfo provides a function to write compound file
// writeTransformInfo storage. writeTransformInfo is a storage containing
// definitions for the transforms used in the data space definitions stored in
// the DataSpaceInfo storage. The stream contains zero or more definitions for
// the possible transforms that can be applied to the data in content
// streams.
func (c *cfb) writeTransformInfo() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("TransformInfo")
	storage.position = 0x40
	storage.writeUint16(0x1C)
	storage.writeBytes([]byte{1, 0})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(9)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	return storage.stream
}

// writeStrongEncryptionTransform provides a function to write compound file
// writeStrongEncryptionTransform storage.
func (c *cfb) writeStrongEncryptionTransform() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeStrings("StrongEncryptionTransform")
	storage.position = 0x40
	storage.writeUint16(0x34)
	storage.writeBytes([]byte{1})
	storage.writeBytes([]byte{1})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(0x0A)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	return storage.stream
}

// writePrimary provides a function to write compound file writePrimary stream.
func (c *cfb) writePrimary() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.writeUint16(0x06)
	storage.writeStrings("Primary")
	storage.position = 0x40
	storage.writeUint16(0x12)
	storage.writeBytes([]byte{2, 1})
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.position = 0x64
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(9)
	storage.writeUint32(208)
	return storage.stream
}

// writeNoneDir provides a function to write compound file writeNoneDir stream.
func (c *cfb) writeNoneDir() []byte {
	storage := cfb{stream: make([]byte, 128)}
	storage.position = 0x40
	storage.writeUint16(0x00)
	storage.writeUint16(0x00)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	storage.writeUint32(-1)
	return storage.stream
}

// writeDirectoryEntry provides a function to write compound file directory
// entries. The directory entry array is an array of directory entries that
// are grouped into a directory sector. Each storage object or stream object
// within a compound file is represented by a single directory entry. The
// space for the directory sectors that are holding the array is allocated
// from the FAT.
func (c *cfb) writeDirectoryEntry(propertyCount, customSectID, size int) []byte {
	var storage cfb
	if size < 0 {
		size = 0
	}
	for _, entry := range [][]byte{
		c.writeRootEntry(customSectID),
		c.writeEncryptionInfo(),
		c.writeEncryptedPackage(propertyCount, size),
		c.writeDataSpaces(),
		c.writeVersion(),
		c.writeDataSpaceMap(),
		c.writeDataSpaceInfo(),
		c.writeStrongEncryptionDataSpace(),
		c.writeTransformInfo(),
		c.writeStrongEncryptionTransform(),
		c.writePrimary(),
		c.writeNoneDir(),
	} {
		storage.writeBytes(entry)
	}
	return storage.stream
}

// writeMSAT provides a function to write compound file master sector allocation
// table.
func (c *cfb) writeMSAT(MSATBlocks, SATBlocks int, MSAT []int) []int {
	if MSATBlocks > 0 {
		cnt, MSATIdx := MSATBlocks*128+109, 0
		for i := 0; i < cnt; i++ {
			if i < SATBlocks {
				bufferSize := i - 109
				if bufferSize > 0 && bufferSize%0x80 == 0 {
					MSATIdx++
					MSAT = append(MSAT, MSATIdx)
				}
				MSAT = append(MSAT, i+MSATBlocks)
				continue
			}
			MSAT = append(MSAT, -1)
		}
		return MSAT
	}
	for i := 0; i < 109; i++ {
		if i < SATBlocks {
			MSAT = append(MSAT, i)
			continue
		}
		MSAT = append(MSAT, -1)
	}
	return MSAT
}

// writeSAT provides a function to write compound file sector allocation
// table.
func (c *cfb) writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks int, SAT []int) (int, []int) {
	var blocks int
	if SATBlocks > 0 {
		for i := 1; i <= MSATBlocks; i++ {
			SAT = append(SAT, -4)
		}
		blocks = MSATBlocks
		for i := 1; i <= SATBlocks; i++ {
			SAT = append(SAT, -3)
		}
		blocks += SATBlocks
		for i := 1; i < SSATBlocks; i++ {
			SAT = append(SAT, i)
		}
		SAT = append(SAT, -2)
		blocks += SSATBlocks
		for i := 1; i < directoryBlocks; i++ {
			SAT = append(SAT, i+blocks)
		}
		SAT = append(SAT, -2)
		blocks += directoryBlocks
		for i := 1; i < fileBlocks; i++ {
			SAT = append(SAT, i+blocks)
		}
		SAT = append(SAT, -2)
		blocks += fileBlocks
		for i := 1; i < streamBlocks; i++ {
			SAT = append(SAT, i+blocks)
		}
		SAT = append(SAT, -2)
	}
	return blocks, SAT
}

// Writer provides a function to create compound file with given info stream
// and package stream.
//
//    MSAT - The master sector allocation table
//    SSAT - The short sector allocation table
//    SAT  - The sector allocation table
//
func (c *cfb) Writer(encryptionInfoBuffer, encryptedPackage []byte) []byte {
	var (
		storage                                 cfb
		MSAT, SAT, SSAT                         []int
		directoryBlocks, fileBlocks, SSATBlocks = 3, 2, 1
		size                                    = int(math.Max(float64(len(encryptedPackage)), float64(packageEncryptionChunkSize)))
		streamBlocks                            = len(encryptedPackage) / 0x200
	)
	if len(encryptedPackage)%0x200 > 0 {
		streamBlocks++
	}
	propertyBlocks := directoryBlocks + fileBlocks + SSATBlocks
	blockSize := (streamBlocks + propertyBlocks) * 4
	SATBlocks := blockSize / 0x200
	if blockSize%0x200 > 0 {
		SATBlocks++
	}
	MSATBlocks, blocksChanged := 0, true
	for blocksChanged {
		var SATCap, MSATCap int
		blocksChanged = false
		blockSize = (streamBlocks + propertyBlocks + SATBlocks + MSATBlocks) * 4
		SATCap = blockSize / 0x200
		if blockSize%0x200 > 0 {
			SATCap++
		}
		if SATCap > SATBlocks {
			SATBlocks, blocksChanged = SATCap, true
			continue
		}
		if SATBlocks > 109 {
			blockRemains := (SATBlocks - 109) * 4
			blockBuffer := blockRemains % 0x200
			MSATCap = blockRemains / 0x200
			if blockBuffer > 0 {
				MSATCap++
			}
			if blockBuffer+(4*MSATCap) > 0x200 {
				MSATCap++
			}
			if MSATCap > MSATBlocks {
				MSATBlocks, blocksChanged = MSATCap, true
			}
		}
	}
	MSAT = c.writeMSAT(MSATBlocks, SATBlocks, MSAT)
	blocks, SAT := c.writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks, SAT)
	for i := 0; i < 8; i++ {
		storage.writeBytes([]byte{oleIdentifier[i]})
	}
	storage.writeBytes(make([]byte, 16))
	storage.writeUint16(0x003E)
	storage.writeUint16(0x0003)
	storage.writeUint16(-2)
	storage.writeUint16(9)
	storage.writeUint32(6)
	storage.writeUint32(0)
	storage.writeUint32(0)
	storage.writeUint32(SATBlocks)
	storage.writeUint32(MSATBlocks + SATBlocks + SSATBlocks)
	storage.writeUint32(0)
	storage.writeUint32(0x00001000)
	storage.writeUint32(SATBlocks + MSATBlocks)
	storage.writeUint32(SSATBlocks)
	if MSATBlocks > 0 {
		storage.writeUint32(0)
		storage.writeUint32(MSATBlocks)
	} else {
		storage.writeUint32(-2)
		storage.writeUint32(0)
	}
	for _, block := range MSAT {
		storage.writeUint32(block)
	}
	for i := 0; i < SATBlocks*128; i++ {
		if i < len(SAT) {
			storage.writeUint32(SAT[i])
			continue
		}
		storage.writeUint32(-1)
	}
	fileStream, SSATStream := c.writeFileStream(encryptionInfoBuffer, SSAT)
	for _, block := range SSATStream {
		storage.writeUint32(block)
	}
	directoryEntry := c.writeDirectoryEntry(blocks, blocks-fileBlocks, size)
	storage.writeBytes(directoryEntry)
	storage.writeBytes(fileStream)
	storage.writeBytes(encryptedPackage)
	return storage.stream
}