/*
Package sm3 implements the SM3 Hash algorithms
*/
package sm3

import (
	"hash"

	"xdx.jelly/xgcl/sm"
)

func init() {
	// 注册sm.SM3、sm.SM3WithID到sm.hashs中去.
	sm.RegisterHash(sm.SM3, New)
	sm.RegisterHash(sm.SM3WithID, New)
}

type digest struct {
	h   [8]uint32
	x   [chunk]byte
	nx  int
	len uint64
}

// NewDigest return a new digest type, void change `digest` to `Digest`.
// 相比标准库，增加此函数。
// 在上下文明确使用sm3的时候，可以显示用这个函数来获取sm3的digest实例，
// 而不是通过New()得到Hash接口。
func NewDigest() *digest {
	d := new(digest)
	d.Reset()
	return d
}

// New returns a new hash.Hash computing the SM3 checksum. The Hash also
// implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler to
// marshal and unmarshal the internal state of the hash.
func New() hash.Hash {
	return NewDigest()
}

// Reset reset the states
func (d *digest) Reset() {
	d.h[0] = init0
	d.h[1] = init1
	d.h[2] = init2
	d.h[3] = init3
	d.h[4] = init4
	d.h[5] = init5
	d.h[6] = init6
	d.h[7] = init7
	d.nx = 0
	d.len = 0
}

/*
MarshalBinary和UnmarshalBinary实现了encoding.BinaryMarshaler和
encoding.BinaryMarshaler接口，可以对Sm3的中间状态转化为byte序列存储。
比如kdf函数要计算SM3(z||cnt)， cnt = 1,2,3,4...，可以先update(z),保存此时
状态。

用法：
dig := New()
io.WriteString(dig, "a")
// New 返回的是hash接口, 转换为BinaryMarshaler接口
state, _ := dig.(encoding.BinaryMarshaler).MarshalBinary()
// dig do something
dig.(encoding.BinaryUnmarshaler).UnmarshalBinary(state)

补充：我们增加了NewDigest(), 返回*digest，因此可以直接用赋值保存。
d1 := NewDigest()
// ...
*d2 := *d1
*/

// MarshalBinary implements the encoding.BinaryMarshaler interface
// marshal a digest to []byte to save
// no error return.
func (d *digest) MarshalBinary() ([]byte, error) {
	b := make([]byte, 0, marshaledSize)
	b = append(b, magic...)
	b = appendUint32(b, d.h[0])
	b = appendUint32(b, d.h[1])
	b = appendUint32(b, d.h[2])
	b = appendUint32(b, d.h[3])
	b = appendUint32(b, d.h[4])
	b = appendUint32(b, d.h[5])
	b = appendUint32(b, d.h[6])
	b = appendUint32(b, d.h[7])
	b = append(b, d.x[:]...)
	b = appendUint64(b, d.len)
	return b, nil
}

// UnmarshalBinary implements the encoding.BinaryMarshaler interface
// recover a digest from []byte
func (d *digest) UnmarshalBinary(b []byte) error {
	if len(b) < marshaledSize || string(b[:len(magic)]) != magic {
		return ErrInvalidInput
	}

	b = b[len(magic):]
	b, d.h[0] = consumeUint32(b)
	b, d.h[1] = consumeUint32(b)
	b, d.h[2] = consumeUint32(b)
	b, d.h[3] = consumeUint32(b)
	b, d.h[4] = consumeUint32(b)
	b, d.h[5] = consumeUint32(b)
	b, d.h[6] = consumeUint32(b)
	b, d.h[7] = consumeUint32(b)
	b = b[copy(d.x[:], b):]
	_, d.len = consumeUint64(b)
	d.nx = int(d.len) % chunk
	return nil
}

// Size returns the size of hash digest
func (d *digest) Size() int { return Size }

// BlockSize return the bytes of one block
func (d *digest) BlockSize() int { return BlockSize }

func (d *digest) Write(p []byte) (nn int, err error) {
	nn = len(p)
	d.len += uint64(nn)
	// try to clear d.x
	if d.nx > 0 {
		n := copy(d.x[d.nx:], p)
		d.nx += n
		if d.nx == chunk {
			block(d, d.x[:])
			d.nx = 0
		}
		p = p[n:]
	}

	if len(p) >= chunk {
		// n is multiple of chunk
		n := len(p) &^ (chunk - 1)
		block(d, p[:n])
		p = p[n:]
	}
	if len(p) > 0 {
		d.nx = copy(d.x[:], p)
	}
	return
}

// Sum returns the digest without change the intenal states
func (d *digest) Sum(in []byte) []byte {
	// make a copy
	d0 := *d
	hash := d0.checkSum()
	return append(in, hash[:]...)
}

// Sum returns the SM3 checksum of the data.
func Sum(data ...[]byte) [Size]byte {
	var d digest
	d.Reset()
	for _, x := range data {
		d.Write(x)
	}
	return d.checkSum()
}
