// Copyright (c) 2023 Kim Shrier. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// Package blake3 implements the Blake3 cryptographic hash
// as described in:
// https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf
// Version 20211102173700

module blake3

import encoding.binary

// size256 is the size, in bytes, of a Blake3 256 checksum.
pub const size256 = 32

// key_length is the length, in bytes, of a Blake3 key
pub const key_length = 32

// block_size is the block size, in bytes, of the Blake3 hash functions.
pub const block_size = 64

// chunk_size is the chunk size, in bytes, of the Blake3 hash functions.
// A chunk consists of 16 blocks.
pub const chunk_size = 1024

// G rotation constants
const r1 = 16
const r2 = 12
const r3 = 8
const r4 = 7

// negative G rotation constants so we can rotate right.
const nr1 = -1 * r1
const nr2 = -1 * r2
const nr3 = -1 * r3
const nr4 = -1 * r4

// initialization vector
const iv = [
	u32(0x6a09e667),
	u32(0xbb67ae85),
	u32(0x3c6ef372),
	u32(0xa54ff53a),
	u32(0x510e527f),
	u32(0x9b05688c),
	u32(0x1f83d9ab),
	u32(0x5be0cd19),
]

// message word schedule permutations
const sigma = [
	[u8(0), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
	[u8(2), 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8],
	[u8(3), 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1],
	[u8(10), 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6],
	[u8(12), 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4],
	[u8(9), 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7],
	[u8(11), 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13],
]

// internal flags
enum Flags as u32 {
	chunk_start         = 1 << 0
	chunk_end           = 1 << 1
	parent              = 1 << 2
	root                = 1 << 3
	keyed_hash          = 1 << 4
	derive_key_context  = 1 << 5
	derive_key_material = 1 << 6
}

struct KeyWordsLengthError {
	Error
	length u32
}

fn (err KeyWordsLengthError) msg() string {
	return 'key_words length, ${err.length} bytes, must be 8 32-bit words'
}

struct DigestFlagsError {
	Error
	flags u32
}

fn (err DigestFlagsError) msg() string {
	lines := [
		'Digest flags ${err.flags:08x} must either be 0 or have only one bit set.',
		'legal bits are:',
		'    keyed_hash          ${u32(Flags.keyed_hash):08x}',
		'    derive_key_context  ${u32(Flags.derive_key_context):08x}',
		'    derive_key_material ${u32(Flags.derive_key_material):08x}',
	]

	return lines.join('\n')
}

// empty tree node
struct Empty {}

// parent node containing the propagated chaining value
struct Node {
	chaining_value []u32
}

fn (n Node) str() string {
	return 'Node chaining_value: ${n.chaining_value[0]:08x} ${n.chaining_value[1]:08x} ${n.chaining_value[2]:08x} ${n.chaining_value[3]:08x}   ${n.chaining_value[4]:08x} ${n.chaining_value[5]:08x} ${n.chaining_value[6]:08x} ${n.chaining_value[7]:08x}'
}

type TreeNode = Empty | Node

// data needed to generate arbitrary amounts of hash output
struct HashState {
mut:
	words          []u32
	chaining_value []u32
	block_words    []u32
	block_len      u32
	flags          u32
}

// Digest holds the state needed to compute a Blake3 hash
struct Digest {
	key_words []u32 // these form the initial chaining value of a chunk
	flags     u32   // only the keyed_hash, derive_key_context, or derive_key_material bits
mut:
	chunk_counter u64        // number of the next chunk to be created
	input         []u8       // unconsumed input
	binary_edge   []TreeNode // the right-hand edge of the binary tree
}

// Digest.new_hash initializes a Digest structure for a Blake3 hash
pub fn Digest.new_hash() !Digest {
	return Digest.new(blake3.iv, 0)
}

// Digest.new_keyed_hash initializes a Digest structure for a Blake3 keyed hash
pub fn Digest.new_keyed_hash(key []u8) !Digest {
	// treat the key bytes as little endian u32 values
	mut key_words := []u32{len: 8, cap: 8}
	for i in 0 .. 8 {
		key_words[i] = binary.little_endian_u32_at(key, i * 4)
	}

	return Digest.new(key_words, u32(Flags.keyed_hash))
}

// Digest.new_derive_key_hash initializes a Digest structure for deriving a Blake3 key
pub fn Digest.new_derive_key_hash(context []u8) !Digest {
	mut context_digest := Digest.new(blake3.iv, u32(Flags.derive_key_context))!

	context_digest.write(context)!
	context_key := context_digest.checksum_internal(blake3.key_length)

	// treat the context key bytes as little endian u32 values
	mut key_words := []u32{len: 8, cap: 8}
	for i in 0 .. 8 {
		key_words[i] = binary.little_endian_u32_at(context_key, i * 4)
	}

	return Digest.new(key_words, u32(Flags.derive_key_material))
}

fn Digest.new(key_words []u32, flags u32) !Digest {
	if key_words.len != 8 {
		return KeyWordsLengthError{
			length: u32(key_words.len)
		}
	}

	// flags must be 0 for performing a blake3 hash.  Other bits modify
	// the type of hash performed.  Only 1 of the keyed_hash,
	// derive_key_context, or derive_key_material bits can be set.
	// Having other bits or multiple bits set is invalid at the
	// Digest level.
	match flags {
		u32(0) {}
		u32(Flags.keyed_hash) {}
		u32(Flags.derive_key_context) {}
		u32(Flags.derive_key_material) {}
		else {
			return DigestFlagsError{
				flags: flags
			}
		}
	}

	return Digest{
		key_words: key_words
		flags: flags
		chunk_counter: 0
		input: []u8{}
		binary_edge: []TreeNode{}
	}
}

// write adds bytes to the hash
pub fn (mut d Digest) write(data []u8) ! {
	// if no data is being added to the hash, just return.
	if data.len == 0 {
		return
	}

	d.input << data

	// if we have more than 1024 bytes in the input,
	// process it in chunks.

	for d.input.len > blake3.chunk_size {
		mut chunk := Chunk{}
		words := chunk.process_input(d.input[..blake3.chunk_size], d.key_words, d.chunk_counter,
			d.flags, false)

		d.add_node(Node{ chaining_value: words[..8] }, 0)

		d.chunk_counter += 1
		d.input = d.input[blake3.chunk_size..]
	}
}

// checksum finalizes the hash and returns the generated bytes.
//
// This is the point in the hashing operation that we need to know
// how many bytes of hash to generate.  Normally this is 32 but can
// be any size up to 2**64.
pub fn (mut d Digest) checksum(size u64) []u8 {
	return d.checksum_internal(size)
}

fn (mut d Digest) checksum_internal(size u64) []u8 {
	// process the last of the input
	mut chunk := Chunk{}

	root := d.chunk_counter == 0
	mut words := chunk.process_input(d.input, d.key_words, d.chunk_counter, d.flags, root)

	// starting with the current (last) chunk, work your way up to the
	// top of the tree.

	mut state := HashState{
		words: words
		chaining_value: chunk.chaining_value
		block_words: chunk.block_words
		block_len: chunk.block_len
		flags: chunk.flags
	}

	mut right_node := Node{
		chaining_value: words[..8].clone()
	}

	for i, left_node in d.binary_edge {
		match left_node {
			Empty {
				// nothing to do here, just skip it
			}
			Node {
				mut block_words := left_node.chaining_value.clone()
				block_words << right_node.chaining_value

				mut flags := d.flags | u32(Flags.parent)
				flags |= if i == d.binary_edge.len - 1 { u32(Flags.root) } else { u32(0) }

				words = f(d.key_words, block_words, u64(0), blake3.block_size, flags)

				state.words = words
				state.chaining_value = d.key_words
				state.block_words = block_words
				state.block_len = blake3.block_size
				state.flags = flags

				right_node = Node{
					chaining_value: words[..8].clone()
				}
			}
		}
	}

	return root_output_bytes(state, size)
}

fn root_output_bytes(state HashState, size u64) []u8 {
	mut output := []u8{cap: int(size)}
	mut bytes_needed := size

	mut block_number := u64(0)
	mut words := state.words.clone()

	for bytes_needed > 0 {
		for word in words {
			mut hash_bytes := []u8{len: 4, cap: 4}
			binary.little_endian_put_u32(mut hash_bytes, word)

			for hash_byte in hash_bytes {
				output << hash_byte
				bytes_needed -= 1

				if bytes_needed == 0 {
					return output
				}
			}
		}

		block_number += 1
		words = f(state.chaining_value, state.block_words, block_number, state.block_len,
			state.flags)
	}

	return output
}

fn (mut d Digest) add_node(node Node, level u8) {
	// if we are above the highst level,
	// just add the node at the top
	if d.binary_edge.len == level {
		d.binary_edge << node
		return
	}

	edge_node := d.binary_edge[level]

	match edge_node {
		Empty {
			d.binary_edge[level] = node
		}
		Node {
			mut block_words := edge_node.chaining_value.clone()
			block_words << node.chaining_value

			words := f(d.key_words, block_words, u64(0), blake3.block_size, d.flags | u32(Flags.parent))
			parent_node := Node{
				chaining_value: words[..8]
			}

			d.binary_edge[level] = Empty{}

			d.add_node(parent_node, level + 1)
		}
	}

	return
}

// sum256 returns the Blake3 256 bit hash of the data.
pub fn sum256(data []u8) []u8 {
	mut d := Digest.new_hash() or { panic(err) }
	d.write(data) or { panic(err) }
	return d.checksum_internal(blake3.size256)
}

// sum_keyed256 returns the Blake3 256 bit keyed hash of the data.
pub fn sum_keyed256(data []u8, key []u8) []u8 {
	mut d := Digest.new_keyed_hash(key) or { panic(err) }
	d.write(data) or { panic(err) }
	return d.checksum_internal(blake3.size256)
}

// sum_derived_key256 returns the Blake3 256 bit derived key hash of the key material
pub fn sum_derive_key256(context []u8, key_material []u8) []u8 {
	mut d := Digest.new_derive_key_hash(context) or { panic(err) }
	d.write(key_material) or { panic(err) }
	return d.checksum_internal(blake3.size256)
}
