package aegis

import "core:crypto"
import aes "core:crypto/_aes/ct64"
import "core:encoding/endian"

// This uses the bitlsiced 64-bit general purpose register SWAR AES
// round function.  The intermediate state is stored in interleaved
// but NOT orthogonalized form, as leaving things in the orthgonalized
// format would overly complicate the update implementation.
//
// Note/perf: Per Frank Denis and a review of the specification, it is
// possible to gain slightly more performance by leaving the state in
// orthogonalized form while doing initialization, finalization, and
// absorbing AAD.  This implementation opts out of those optimizations
// for the sake of simplicity.
//
// The update function leverages the paralleism (4xblocks) at once.

@(private)
State_SW :: struct {
	s0_0, s0_1: u64,
	s1_0, s1_1: u64,
	s2_0, s2_1: u64,
	s3_0, s3_1: u64,
	s4_0, s4_1: u64,
	s5_0, s5_1: u64,
	s6_0, s6_1: u64,
	s7_0, s7_1: u64,
	q_k, q_b:   [8]u64,
	rate:       int,
}

@(private)
init_sw :: proc "contextless" (ctx: ^Context, st: ^State_SW, iv: []byte) {
	switch ctx._key_len {
	case KEY_SIZE_128L:
		key_0, key_1 := aes.load_interleaved(ctx._key[:16])
		iv_0, iv_1 := aes.load_interleaved(iv)

		st.s0_0, st.s0_1 = aes.xor_interleaved(key_0, key_1, iv_0, iv_1)
		st.s1_0, st.s1_1 = aes.load_interleaved(_C1[:])
		st.s2_0, st.s2_1 = aes.load_interleaved(_C0[:])
		st.s3_0, st.s3_1 = st.s1_0, st.s1_1
		st.s4_0, st.s4_1 = st.s0_0, st.s0_1
		st.s5_0, st.s5_1 = aes.xor_interleaved(key_0, key_1, st.s2_0, st.s2_1)
		st.s6_0, st.s6_1 = aes.xor_interleaved(key_0, key_1, st.s1_0, st.s1_1)
		st.s7_0, st.s7_1 = st.s5_0, st.s5_1
		st.rate = _RATE_128L

		for _ in 0 ..< 10 {
			update_sw_128l(st, iv_0, iv_1, key_0, key_1)
		}
	case KEY_SIZE_256:
		k0_0, k0_1 := aes.load_interleaved(ctx._key[:16])
		k1_0, k1_1 := aes.load_interleaved(ctx._key[16:])
		n0_0, n0_1 := aes.load_interleaved(iv[:16])
		n1_0, n1_1 := aes.load_interleaved(iv[16:])

		st.s0_0, st.s0_1 = aes.xor_interleaved(k0_0, k0_1, n0_0, n0_1)
		st.s1_0, st.s1_1 = aes.xor_interleaved(k1_0, k1_1, n1_0, n1_1)
		st.s2_0, st.s2_1 = aes.load_interleaved(_C1[:])
		st.s3_0, st.s3_1 = aes.load_interleaved(_C0[:])
		st.s4_0, st.s4_1 = aes.xor_interleaved(k0_0, k0_1, st.s3_0, st.s3_1)
		st.s5_0, st.s5_1 = aes.xor_interleaved(k1_0, k1_1, st.s2_0, st.s2_1)
		st.rate = _RATE_256

		u0_0, u0_1, u1_0, u1_1 := st.s0_0, st.s0_1, st.s1_0, st.s1_1
		for _ in 0 ..< 4 {
			update_sw_256(st, k0_0, k0_1)
			update_sw_256(st, k1_0, k1_1)
			update_sw_256(st, u0_0, u0_1)
			update_sw_256(st, u1_0, u1_1)
		}
	}
}

@(private = "file")
update_sw_128l :: proc "contextless" (st: ^State_SW, m0_0, m0_1, m1_0, m1_1: u64) {
	st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s0_0, st.s0_1, m0_0, m0_1)
	st.q_k[1], st.q_k[5] = st.s1_0, st.s1_1
	st.q_k[2], st.q_k[6] = st.s2_0, st.s2_1
	st.q_k[3], st.q_k[7] = st.s3_0, st.s3_1
	aes.orthogonalize(&st.q_k)

	st.q_b[0], st.q_b[4] = st.s7_0, st.s7_1
	st.q_b[1], st.q_b[5] = st.s0_0, st.s0_1
	st.q_b[2], st.q_b[6] = st.s1_0, st.s1_1
	st.q_b[3], st.q_b[7] = st.s2_0, st.s2_1
	aes.orthogonalize(&st.q_b)

	aes.sub_bytes(&st.q_b)
	aes.shift_rows(&st.q_b)
	aes.mix_columns(&st.q_b)
	aes.add_round_key(&st.q_b, st.q_k[:])
	aes.orthogonalize(&st.q_b)

	st.s0_0, st.s0_1 = st.q_b[0], st.q_b[4]
	st.s1_0, st.s1_1 = st.q_b[1], st.q_b[5]
	st.s2_0, st.s2_1 = st.q_b[2], st.q_b[6]
	s3_0, s3_1 := st.q_b[3], st.q_b[7]

	st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s4_0, st.s4_1, m1_0, m1_1)
	st.q_k[1], st.q_k[5] = st.s5_0, st.s5_1
	st.q_k[2], st.q_k[6] = st.s6_0, st.s6_1
	st.q_k[3], st.q_k[7] = st.s7_0, st.s7_1
	aes.orthogonalize(&st.q_k)

	st.q_b[0], st.q_b[4] = st.s3_0, st.s3_1
	st.q_b[1], st.q_b[5] = st.s4_0, st.s4_1
	st.q_b[2], st.q_b[6] = st.s5_0, st.s5_1
	st.q_b[3], st.q_b[7] = st.s6_0, st.s6_1
	aes.orthogonalize(&st.q_b)

	aes.sub_bytes(&st.q_b)
	aes.shift_rows(&st.q_b)
	aes.mix_columns(&st.q_b)
	aes.add_round_key(&st.q_b, st.q_k[:])
	aes.orthogonalize(&st.q_b)

	st.s3_0, st.s3_1 = s3_0, s3_1
	st.s4_0, st.s4_1 = st.q_b[0], st.q_b[4]
	st.s5_0, st.s5_1 = st.q_b[1], st.q_b[5]
	st.s6_0, st.s6_1 = st.q_b[2], st.q_b[6]
	st.s7_0, st.s7_1 = st.q_b[3], st.q_b[7]
}

@(private = "file")
update_sw_256 :: proc "contextless" (st: ^State_SW, m_0, m_1: u64) {
	st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s0_0, st.s0_1, m_0, m_1)
	st.q_k[1], st.q_k[5] = st.s1_0, st.s1_1
	st.q_k[2], st.q_k[6] = st.s2_0, st.s2_1
	st.q_k[3], st.q_k[7] = st.s3_0, st.s3_1
	aes.orthogonalize(&st.q_k)

	st.q_b[0], st.q_b[4] = st.s5_0, st.s5_1
	st.q_b[1], st.q_b[5] = st.s0_0, st.s0_1
	st.q_b[2], st.q_b[6] = st.s1_0, st.s1_1
	st.q_b[3], st.q_b[7] = st.s2_0, st.s2_1
	aes.orthogonalize(&st.q_b)

	aes.sub_bytes(&st.q_b)
	aes.shift_rows(&st.q_b)
	aes.mix_columns(&st.q_b)
	aes.add_round_key(&st.q_b, st.q_k[:])
	aes.orthogonalize(&st.q_b)

	st.s0_0, st.s0_1 = st.q_b[0], st.q_b[4]
	st.s1_0, st.s1_1 = st.q_b[1], st.q_b[5]
	st.s2_0, st.s2_1 = st.q_b[2], st.q_b[6]
	s3_0, s3_1 := st.q_b[3], st.q_b[7]

	st.q_k[0], st.q_k[4] = st.s4_0, st.s4_1
	st.q_k[1], st.q_k[5] = st.s5_0, st.s5_1
	aes.orthogonalize(&st.q_k)

	st.q_b[0], st.q_b[4] = st.s3_0, st.s3_1
	st.q_b[1], st.q_b[5] = st.s4_0, st.s4_1
	aes.orthogonalize(&st.q_b)

	aes.sub_bytes(&st.q_b)
	aes.shift_rows(&st.q_b)
	aes.mix_columns(&st.q_b)
	aes.add_round_key(&st.q_b, st.q_k[:])
	aes.orthogonalize(&st.q_b)

	st.s3_0, st.s3_1 = s3_0, s3_1
	st.s4_0, st.s4_1 = st.q_b[0], st.q_b[4]
	st.s5_0, st.s5_1 = st.q_b[1], st.q_b[5]
}

@(private = "file")
absorb_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, ai: []byte) #no_bounds_check {
	t0_0, t0_1 := aes.load_interleaved(ai[:16])
	t1_0, t1_1 := aes.load_interleaved(ai[16:])
	update_sw_128l(st, t0_0, t0_1, t1_0, t1_1)
}

@(private = "file")
absorb_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, ai: []byte) {
	m_0, m_1 := aes.load_interleaved(ai)
	update_sw_256(st, m_0, m_1)
}

@(private)
absorb_sw :: proc "contextless" (st: ^State_SW, aad: []byte) #no_bounds_check {
	ai, l := aad, len(aad)

	switch st.rate {
	case _RATE_128L:
		for l >= _RATE_128L {
			absorb_sw_128l(st, ai)
			ai = ai[_RATE_128L:]
			l -= _RATE_128L
		}
	case _RATE_256:
		for l >= _RATE_256 {
			absorb_sw_256(st, ai)

			ai = ai[_RATE_256:]
			l -= _RATE_256
		}
	}

	// Pad out the remainder with `0`s till it is rate sized.
	if l > 0 {
		tmp: [_RATE_MAX]byte // AAD is not confidential.
		copy(tmp[:], ai)
		switch st.rate {
		case _RATE_128L:
			absorb_sw_128l(st, tmp[:])
		case _RATE_256:
			absorb_sw_256(st, tmp[:])
		}
	}
}

@(private = "file", require_results)
z_sw_128l :: proc "contextless" (st: ^State_SW) -> (u64, u64, u64, u64) {
	z0_0, z0_1 := aes.and_interleaved(st.s2_0, st.s2_1, st.s3_0, st.s3_1)
	z0_0, z0_1 = aes.xor_interleaved(st.s1_0, st.s1_1, z0_0, z0_1)
	z0_0, z0_1 = aes.xor_interleaved(st.s6_0, st.s6_1, z0_0, z0_1)

	z1_0, z1_1 := aes.and_interleaved(st.s6_0, st.s6_1, st.s7_0, st.s7_1)
	z1_0, z1_1 = aes.xor_interleaved(st.s5_0, st.s5_1, z1_0, z1_1)
	z1_0, z1_1 = aes.xor_interleaved(st.s2_0, st.s2_1, z1_0, z1_1)

	return z0_0, z0_1, z1_0, z1_1
}

@(private = "file", require_results)
z_sw_256 :: proc "contextless" (st: ^State_SW) -> (u64, u64) {
	z_0, z_1 := aes.and_interleaved(st.s2_0, st.s2_1, st.s3_0, st.s3_1)
	z_0, z_1 = aes.xor_interleaved(st.s5_0, st.s5_1, z_0, z_1)
	z_0, z_1 = aes.xor_interleaved(st.s4_0, st.s4_1, z_0, z_1)
	return aes.xor_interleaved(st.s1_0, st.s1_1, z_0, z_1)
}

@(private = "file")
enc_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, ci, xi: []byte) #no_bounds_check {
	z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st)

	t0_0, t0_1 := aes.load_interleaved(xi[:16])
	t1_0, t1_1 := aes.load_interleaved(xi[16:])
	update_sw_128l(st, t0_0, t0_1, t1_0, t1_1)

	out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1)
	out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1)
	aes.store_interleaved(ci[:16], out0_0, out0_1)
	aes.store_interleaved(ci[16:], out1_0, out1_1)
}

@(private = "file")
enc_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, ci, xi: []byte) #no_bounds_check {
	z_0, z_1 := z_sw_256(st)

	xi_0, xi_1 := aes.load_interleaved(xi)
	update_sw_256(st, xi_0, xi_1)

	ci_0, ci_1 := aes.xor_interleaved(xi_0, xi_1, z_0, z_1)
	aes.store_interleaved(ci, ci_0, ci_1)
}

@(private)
enc_sw :: proc "contextless" (st: ^State_SW, dst, src: []byte) #no_bounds_check {
	ci, xi, l := dst, src, len(src)

	switch st.rate {
	case _RATE_128L:
		for l >= _RATE_128L {
			enc_sw_128l(st, ci, xi)
			ci = ci[_RATE_128L:]
			xi = xi[_RATE_128L:]
			l -= _RATE_128L
		}
	case _RATE_256:
		for l >= _RATE_256 {
			enc_sw_256(st, ci, xi)
			ci = ci[_RATE_256:]
			xi = xi[_RATE_256:]
			l -= _RATE_256
		}
	}

	// Pad out the remainder with `0`s till it is rate sized.
	if l > 0 {
		tmp: [_RATE_MAX]byte // Ciphertext is not confidential.
		copy(tmp[:], xi)
		switch st.rate {
		case _RATE_128L:
			enc_sw_128l(st, tmp[:], tmp[:])
		case _RATE_256:
			enc_sw_256(st, tmp[:], tmp[:])
		}
		copy(ci, tmp[:l])
	}
}

@(private = "file")
dec_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, xi, ci: []byte) #no_bounds_check {
	z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st)

	t0_0, t0_1 := aes.load_interleaved(ci[:16])
	t1_0, t1_1 := aes.load_interleaved(ci[16:])
	out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1)
	out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1)

	update_sw_128l(st, out0_0, out0_1, out1_0, out1_1)
	aes.store_interleaved(xi[:16], out0_0, out0_1)
	aes.store_interleaved(xi[16:], out1_0, out1_1)
}

@(private = "file")
dec_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, xi, ci: []byte) #no_bounds_check {
	z_0, z_1 := z_sw_256(st)

	ci_0, ci_1 := aes.load_interleaved(ci)
	xi_0, xi_1 := aes.xor_interleaved(ci_0, ci_1, z_0, z_1)

	update_sw_256(st, xi_0, xi_1)
	aes.store_interleaved(xi, xi_0, xi_1)
}

@(private = "file")
dec_partial_sw_128l :: proc "contextless" (st: ^State_SW, xn, cn: []byte) #no_bounds_check {
	tmp: [_RATE_128L]byte
	defer crypto.zero_explicit(&tmp, size_of(tmp))

	z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st)
	copy(tmp[:], cn)

	t0_0, t0_1 := aes.load_interleaved(tmp[:16])
	t1_0, t1_1 := aes.load_interleaved(tmp[16:])
	out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1)
	out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1)

	aes.store_interleaved(tmp[:16], out0_0, out0_1)
	aes.store_interleaved(tmp[16:], out1_0, out1_1)
	copy(xn, tmp[:])

	for off := len(xn); off < _RATE_128L; off += 1 {
		tmp[off] = 0
	}
	out0_0, out0_1 = aes.load_interleaved(tmp[:16])
	out1_0, out1_1 = aes.load_interleaved(tmp[16:])
	update_sw_128l(st, out0_0, out0_1, out1_0, out1_1)
}

@(private = "file")
dec_partial_sw_256 :: proc "contextless" (st: ^State_SW, xn, cn: []byte) #no_bounds_check {
	tmp: [_RATE_256]byte
	defer crypto.zero_explicit(&tmp, size_of(tmp))

	z_0, z_1 := z_sw_256(st)
	copy(tmp[:], cn)

	cn_0, cn_1 := aes.load_interleaved(tmp[:])
	xn_0, xn_1 := aes.xor_interleaved(cn_0, cn_1, z_0, z_1)

	aes.store_interleaved(tmp[:], xn_0, xn_1)
	copy(xn, tmp[:])

	for off := len(xn); off < _RATE_256; off += 1 {
		tmp[off] = 0
	}
	xn_0, xn_1 = aes.load_interleaved(tmp[:])
	update_sw_256(st, xn_0, xn_1)
}

@(private)
dec_sw :: proc "contextless" (st: ^State_SW, dst, src: []byte) #no_bounds_check {
	xi, ci, l := dst, src, len(src)

	switch st.rate {
	case _RATE_128L:
		for l >= _RATE_128L {
			dec_sw_128l(st, xi, ci)
			xi = xi[_RATE_128L:]
			ci = ci[_RATE_128L:]
			l -= _RATE_128L
		}
	case _RATE_256:
		for l >= _RATE_256 {
			dec_sw_256(st, xi, ci)
			xi = xi[_RATE_256:]
			ci = ci[_RATE_256:]
			l -= _RATE_256
		}
	}

	// Process the remainder.
	if l > 0 {
		switch st.rate {
		case _RATE_128L:
			dec_partial_sw_128l(st, xi, ci)
		case _RATE_256:
			dec_partial_sw_256(st, xi, ci)
		}
	}
}

@(private)
finalize_sw :: proc "contextless" (st: ^State_SW, tag: []byte, ad_len, msg_len: int) {
	tmp: [16]byte
	endian.unchecked_put_u64le(tmp[0:], u64(ad_len) * 8)
	endian.unchecked_put_u64le(tmp[8:], u64(msg_len) * 8)

	t_0, t_1 := aes.load_interleaved(tmp[:])

	t0_0, t0_1, t1_0, t1_1: u64 = ---, ---, ---, ---
	switch st.rate {
	case _RATE_128L:
		t_0, t_1 = aes.xor_interleaved(st.s2_0, st.s2_1, t_0, t_1)
		for _ in 0 ..< 7 {
			update_sw_128l(st, t_0, t_1, t_0, t_1)
		}

		t0_0, t0_1 = aes.xor_interleaved(st.s0_0, st.s0_1, st.s1_0, st.s1_1)
		t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s2_0, st.s2_1)
		t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s3_0, st.s3_1)

		t1_0, t1_1 = aes.xor_interleaved(st.s4_0, st.s4_1, st.s5_0, st.s5_1)
		t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s6_0, st.s6_1)
		if len(tag) == TAG_SIZE_256 {
			t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s7_0, st.s7_1)
		}
	case _RATE_256:
		t_0, t_1 = aes.xor_interleaved(st.s3_0, st.s3_1, t_0, t_1)
		for _ in 0 ..< 7 {
			update_sw_256(st, t_0, t_1)
		}

		t0_0, t0_1 = aes.xor_interleaved(st.s0_0, st.s0_1, st.s1_0, st.s1_1)
		t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s2_0, st.s2_1)

		t1_0, t1_1 = aes.xor_interleaved(st.s3_0, st.s3_1, st.s4_0, st.s4_1)
		t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s5_0, st.s5_1)
	}
	switch len(tag) {
	case TAG_SIZE_128:
		t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, t1_0, t1_1)
		aes.store_interleaved(tag, t0_0, t0_1)
	case TAG_SIZE_256:
		aes.store_interleaved(tag[:16], t0_0, t0_1)
		aes.store_interleaved(tag[16:], t1_0, t1_1)
	}
}

@(private)
reset_state_sw :: proc "contextless" (st: ^State_SW) {
	crypto.zero_explicit(st, size_of(st^))
}
