// package drng implements the SM3Rng and SM4Rng which specified in GM/T 0115.
//
// ctrrng and hashrng implements the low-level Drng interface.
// Combine entropy(pool) and xxxrng we got the Drng as a ReadWriter.
// The Read method generates random bits, and the Write method
// add outter entropy into the Drng.
//
// Use drng.SM3Rng as an ordinary io.Reader to generate random bits.
// If necessary, it could add extra entropy into the Drng.
// For example, add user's scratch points on mobile, get random bits
// from the cryptography server, etc..
package drng

import (
	"io"
	"sync"

	"xdx.jelly/xgcl/grand/drng/ctrrng"
	"xdx.jelly/xgcl/grand/drng/entropy"
	"xdx.jelly/xgcl/grand/drng/hashrng"
	"xdx.jelly/xgcl/grand/drng/internal"
	"xdx.jelly/xgcl/sm"
)

// PERSONALIZATIONSTRING = "粤信签协同签名软件Android客户端密码模块YXQXTQM-Y"
var PERSONALIZATIONSTRING = []byte("xdx")

// SM3Rng implements the io.ReadWriter interface.
//
// SM3Rng.Read reads random bytes from the DRNG.
// SM3rng.Write add extra entropy to the DRNG.
var SM3Rng io.ReadWriter
var SM4Rng io.ReadWriter

func init() {
	raw, _ := hashrng.NewHashDrng(sm.SM3, hashrng.Config(internal.SecureLevel2))
	rng, _ := raw.Instantiate(PERSONALIZATIONSTRING, internal.Nonce(), entropy.GetEntropyPool())
	SM3Rng = &DrngUtil{
		Drng:         rng,
		ReseedSource: entropy.GetEntropyPool(),
	}

	raw, _ = ctrrng.NewCtrDrng(ctrrng.SM4BlockNewer{}, ctrrng.Config(internal.SecureLevel2))
	rng, _ = raw.Instantiate(PERSONALIZATIONSTRING, internal.Nonce(), entropy.GetEntropyPool())
	SM4Rng = &DrngUtil{
		Drng:         rng,
		ReseedSource: entropy.GetEntropyPool(),
	}
}

// DrngUtil implements the io.Reader interface
type DrngUtil struct {
	mu sync.Mutex
	internal.Drng
	ReseedSource io.Reader // 重播种熵源

	additionalInput io.Reader // TODO: 额外输入, 可为空
}

// Write implements io.ReadWriter.
// The returns can be ignored.
func (du *DrngUtil) Write(p []byte) (n int, err error) {
	du.mu.Lock()
	defer du.mu.Unlock()

	du.Drng.Reseed(p, nil)
	return len(p), nil
}

// Read implements io.Reader
func (du *DrngUtil) Read(p []byte) (n int, err error) {
	du.mu.Lock()
	defer du.mu.Unlock()

	outlen := du.OutLen()
	for len(p) > 0 {
		generateLen := len(p)
		if generateLen > outlen {
			generateLen = outlen
		}
		err := du.Generate(p[:generateLen], nil)
		if err != nil {
			// 需要重播种
			config := du.Config()
			entropyInput := make([]byte, config.MinEntropy)
			n, err := du.ReseedSource.Read(entropyInput)
			if err != nil {
				return n, err
			}
			du.Drng.Reseed(entropyInput[:n], nil)
			err = du.Generate(p[:generateLen], nil)
			if err != nil {
				return n, err
			}
		}
		n += generateLen
		p = p[generateLen:]
	}
	return n, nil
}
