package entropy

import (
	"crypto/rand"
	"encoding/binary"
	"errors"
	"sync"
	"time"

	"xdx.jelly/xgcl/grand/drng/internal"
)

var OutOfEntropyErr = errors.New("out of entropy")

// Read reads at most 32 bytes into p.
// For efficiency, reads bytes are always multiples of 4.
func (e *EntropyPool) Read(p []byte) (int, error) {
	entropyPool.mu.Lock()

	if entropyPool.entropySize <= 0 {
		return 0, OutOfEntropyErr
	}
	// n = min(8, entropyPool.entropySize, len(p) / 4)
	n := 8
	if entropyPool.entropySize < 8 {
		n = entropyPool.entropySize
	}

	if (len(p) / 4) < n {
		n = len(p) / 4
	}

	for i := 0; i < n; i++ {
		x, _ := entropyPool.get() // err should not happen
		binary.BigEndian.PutUint32(p[4*i:], x)
	}

	entropyPool.mu.Unlock()

	go e.update() // update the entropy pool for next reading.

	return 4 * n, nil
}

// EntropyPool 系统熵池
type EntropyPool struct {
	mu   sync.Mutex  //使用互斥锁保证独占性
	pool [128]uint32 //熵池，128个字（512字节）
	// i - j 之间是未取的熵(mod 128)
	i           int //熵索引开始
	j           int //熵索引结束
	entropySize int //熵池中可用熵值
	source      []EntropySource
}

// entropySource 熵源
type EntropySource interface {
	GetEntropy(minEntropy int64, minEntropyInputLength int64, maxEntropyInputLength int64) ([]byte, error)
}

var entropyPool *EntropyPool
var initEntropyPool sync.Once

func init() {
	initEntropyPool.Do(
		func() {
			entropyPool = newEntropyPool()
		})
	go func() {
		// 定时从系统中获取
		tickle := time.NewTicker(10 * time.Second)
		for {
			select {
			case <-tickle.C:
				entropyPool.update()
			}
		}
	}()
}

func GetEntropyPool() *EntropyPool {
	return entropyPool
}

func newEntropyPool() *EntropyPool {
	// FIXME: SysTimeEntropySource？
	p := &EntropyPool{
		source: []EntropySource{&OSEntropySource{}, &SysTimeEntropySource{}},
		// source: []drng.EntropySource{&OSEntropySource{}},
	}

	buf := make([]byte, 128*4)
	_, _ = rand.Reader.Read(buf)
	for i := 0; i < 128; i++ {
		p.pool[i] = binary.BigEndian.Uint32(buf[4*i:])
	}

	// 从每个熵源获取至少256比特
	p.update()

	// 对buf置0
	for i := range buf {
		buf[i] = 0
	}
	// for i := range e {
	// 	e[i] = 0
	// }
	return p
}

func (e *EntropyPool) get() (uint32, error) {
	if e.entropySize <= 0 {
		return 0, OutOfEntropyErr
	}
	n := e.pool[e.i]
	e.i = (e.i + 1) & 127
	e.entropySize--
	return n, nil
}

// lshr 添加新的熵数据g到熵池中。按照GM/T 0105 A.3的方式
func (e *EntropyPool) lshr(g uint32) {
	var table = [8]uint32{0, 0x3b6e20c8, 0x76dc4190, 0x4db26158, 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278}
	temp := g ^ e.pool[e.j]
	for _, i := range []int{1, 25, 51, 76, 103} {
		temp ^= e.pool[(e.j+i)&127]
	}
	temp = (temp >> 3) ^ table[temp&7]
	e.pool[e.j] = temp
	e.j = (e.j + 1) & 127

	// 置0
	temp = 0
	_ = temp
}

// Update 熵池更新
func (e *EntropyPool) update() {
	e.mu.Lock()
	defer e.mu.Unlock()

	// 设置超时时间
	tickle := time.NewTicker(100 * time.Millisecond)
	defer tickle.Stop()

	for i := 0; e.entropySize < 128; i = (i + 1) % len(e.source) {
		select {
		case <-tickle.C:
			break
		default:
			s := e.source[i]
			b, _ := s.GetEntropy(4, 4, internal.MaxEntropyInputLength)
			if len(b) >= 4 {
				e.lshr(binary.BigEndian.Uint32(b))
				e.entropySize++
			}
		}
	}
}

func (e *EntropyPool) GetEntropy(minEntropy int64, minEntropyInputLength int64, maxEntropyInputLength int64) ([]byte, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if minEntropyInputLength < internal.MinEntropyInputLength || maxEntropyInputLength > internal.MaxEntropyInputLength {
		return nil, internal.ErrEntropyLength
	}

	entropy := make([]byte, minEntropyInputLength)
	buf := make([]byte, 4)
	var i int
	for i = 0; int64(i) < minEntropyInputLength && e.entropySize > 0; i += 4 {
		binary.BigEndian.PutUint32(buf, e.pool[e.i])
		copy(entropy[i:], buf)
		e.i = (e.i + 1) & 127
		e.entropySize--
	}
	return entropy[:i], nil
}
