package concatkdf

import (
	"crypto"
	"encoding/binary"
	"fmt"
)

type KDF struct {
	buf       []byte
	otherinfo []byte
	z         []byte
	hash      crypto.Hash
}

func ndata(src []byte) []byte {
	buf := make([]byte, 4+len(src))
	binary.BigEndian.PutUint32(buf, uint32(len(src)))
	copy(buf[4:], src)
	return buf
}

func New(hash crypto.Hash, alg, Z, apu, apv, pubinfo, privinfo []byte) *KDF {
	algbuf := ndata(alg)
	apubuf := ndata(apu)
	apvbuf := ndata(apv)

	concat := make([]byte, len(algbuf)+len(apubuf)+len(apvbuf)+len(pubinfo)+len(privinfo))
	n := copy(concat, algbuf)
	n += copy(concat[n:], apubuf)
	n += copy(concat[n:], apvbuf)
	n += copy(concat[n:], pubinfo)
	copy(concat[n:], privinfo)

	return &KDF{
		hash:      hash,
		otherinfo: concat,
		z:         Z,
	}
}

func (k *KDF) Read(out []byte) (int, error) {
	var round uint32 = 1
	h := k.hash.New()

	for len(out) > len(k.buf) {
		h.Reset()

		if err := binary.Write(h, binary.BigEndian, round); err != nil {
			return 0, fmt.Errorf(`failed to write round using kdf: %w`, err)
		}
		if _, err := h.Write(k.z); err != nil {
			return 0, fmt.Errorf(`failed to write z using kdf: %w`, err)
		}
		if _, err := h.Write(k.otherinfo); err != nil {
			return 0, fmt.Errorf(`failed to write other info using kdf: %w`, err)
		}

		k.buf = append(k.buf, h.Sum(nil)...)
		round++
	}

	n := copy(out, k.buf[:len(out)])
	k.buf = k.buf[len(out):]
	return n, nil
}
