package salsa20

import (
	"crypto/cipher"
	"encoding/binary"

	"golang.org/x/crypto/salsa20/salsa"
)

const BlockSize = 64

type salsaCipher struct {
	key     *[32]byte
	nonce   [8]byte
	x       [BlockSize]byte
	nx      int
	counter uint64
}

func New(key *[32]byte, nonce []byte) cipher.Stream {
	c := new(salsaCipher)

	if len(nonce) == 24 {
		var subKey [32]byte
		var hNonce [16]byte
		copy(hNonce[:], nonce[:16])
		salsa.HSalsa20(&subKey, &hNonce, key, &salsa.Sigma)
		copy(c.nonce[:], nonce[16:])
		c.key = &subKey
	} else if len(nonce) == 8 {
		c.key = key
		copy(c.nonce[:], nonce)
	} else {
		panic("salsa20: nonce must be 8 or 24 bytes")
	}
	return c
}

func (c *salsaCipher) XORKeyStream(dst, src []byte) {
	if len(dst) < len(src) {
		src = src[:len(dst)]
	}
	if c.nx > 0 {
		n := xorBytes(dst, src, c.x[c.nx:])
		c.nx += n
		if c.nx == BlockSize {
			c.nx = 0
		}
		src = src[n:]
		dst = dst[n:]
	}
	if len(src) > BlockSize {
		n := len(src) &^ (BlockSize - 1)
		c.blocks(dst, src[:n])
		src = src[n:]
		dst = dst[n:]
	}
	if len(src) > 0 {
		c.nx = copy(c.x[:], src)
		for i := c.nx; i < len(c.x); i++ {
			c.x[i] = 0
		}
		c.blocks(c.x[:], c.x[:])
		copy(dst, c.x[:c.nx])
	}
}

func (c *salsaCipher) blocks(dst, src []byte) {
	var nonce [16]byte
	copy(nonce[:], c.nonce[:])
	binary.LittleEndian.PutUint64(nonce[8:], c.counter)
	salsa.XORKeyStream(dst, src, &nonce, c.key)
	c.counter += uint64(len(src)) / 64
}

func xorBytes(dst, a, b []byte) int {
	n := len(a)
	if len(b) < n {
		n = len(b)
	}
	for i := 0; i < n; i++ {
		dst[i] = a[i] ^ b[i]
	}
	return n
}
