// Copyright 2024 Sun Yimin. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

//go:build !purego

package sm3

import "encoding/binary"

// p || state || words
// p = 64 * 8 * 2 = 1024
// state = 8 * 32 = 256
// words = 68 * 32 = 2176
const preallocSizeBy8 = 3456

const parallelSize8 = 8

func kdfBy8(baseMD *digest, keyLen int, limit int) []byte {
	var t uint64
	blocks := 1
	len := baseMD.len + 4
	remainlen := len % 64
	if remainlen < 56 {
		t = 56 - remainlen
	} else {
		t = 64 + 56 - remainlen
		blocks = 2
	}
	len <<= 3

	var ct uint32 = 1
	k := make([]byte, limit * Size)
	ret := k

	// prepare temporary buffer
	tmpStart := parallelSize8 * blocks * BlockSize
	buffer := make([]byte, preallocSizeBy8)
	tmp := buffer[tmpStart:]
	// prepare processing data
	var dataPtrs [parallelSize8]*byte
	var data [parallelSize8][]byte
	var digs [parallelSize8]*[8]uint32
	var states [parallelSize8][8]uint32
	
	for j := 0; j < parallelSize8; j++ {
		digs[j] = &states[j]
		p := buffer[blocks*BlockSize*j:]
		data[j] = p
		dataPtrs[j] = &p[0]
		if j == 0 {
			prepareInitData(baseMD, p, len, t)
		} else {
			copy(p, data[0])
		}
	}
	
	times := limit / parallelSize8
	for i := 0; i < times; i++ {
		for j := 0; j < parallelSize8; j++ {
			// prepare states
			states[j] = baseMD.h
			// prepare data
			binary.BigEndian.PutUint32(data[j][baseMD.nx:], ct)
			ct++
		}
		blockMultBy8(&digs[0], &dataPtrs[0], &tmp[0], blocks)
		copyResultsBy8(&states[0][0], &ret[0])
		ret = ret[Size*parallelSize8:]
	}

	remain := limit % parallelSize8
	if remain >= parallelSize4 {
		for j := 0; j < parallelSize4; j++ {
			// prepare states
			states[j] = baseMD.h
			// prepare data
			binary.BigEndian.PutUint32(data[j][baseMD.nx:], ct)
			ct++
		}
		blockMultBy4(&digs[0], &dataPtrs[0], &tmp[0], blocks)
		copyResultsBy4(&states[0][0], &ret[0])
		ret = ret[Size*parallelSize4:]
		remain -= parallelSize4
	}

	for i := 0; i < remain; i++ {
		binary.BigEndian.PutUint32(tmp[:], ct)
		md := *baseMD
		md.Write(tmp[:4])
		h := md.checkSum()
		copy(ret[i*Size:], h[:])
		ct++
	}

	return k[:keyLen]
}

//go:noescape
func blockMultBy8(dig **[8]uint32, p **byte, buffer *byte, blocks int)

//go:noescape
func transposeMatrix8x8(dig **[8]uint32)

//go:noescape
func copyResultsBy8(dig *uint32, p *byte)
