// File: /workspace/github_cpp_makefile_star2_O2/adamjanovsky[P]PA193_test_parser_monero/src/tools.cpp
// Function: tools::tree_hash(std::vector<unsigned char, std::allocator<unsigned char>> const&, std::array<unsigned char, 32ul>&)
int tools::tree_hash(const std::vector<unsigned char> & hash_vect, hash::hash_t & root_hash) {
    
    // Check buf_len
    // - each hash is 32 bytes-long => buf_len must be a multiple of 32
    if (hash_vect.size() == 0 || hash_vect.size() % 32 != 0) {
        std::cerr << "TreeHash: Invalid input length." << std::endl;
        return ERR_THASH_INVALID_IN_LEN;
    }
        
    // Calculate the hash count and check it for sanity
    // - bound of 0x10000000 is included in the original Monero function
    size_t hash_count = hash_vect.size() / 32;
    if (hash_count == 0 || hash_count > 0x10000000) {
        std::cerr << "TreeHash: Invalid input length." << std::endl;
        return ERR_THASH_INVALID_IN_LEN;
    }
    
    // Reintepret the hash_vect as array of 32 byte-long hashes
    const unsigned char (* hashes) [hash::HASH_SIZE] = reinterpret_cast<const unsigned char (*)[32]>(hash_vect.data());
    
    switch (hash_count) {
        case 1:
            // Just copy the single hash into the root_hash
            memcpy(&root_hash[0], hashes, root_hash.size());
            return OK;
        case 2:
            // Just hash the two hashes and put the result into root_hash
            hash(hashes[0], 2 * hash::HASH_SIZE, &root_hash[0]);
            return OK;
            
        default:
            // Hashes passed to this function are treated as tree leaves
            
            // How many nodes do we need in the layer above the leaves?
            // => nearest lower power of two to count of leaves
            // we will use this same array as we progress up the tree, using less and less from the reserved nodes
            size_t int_nodes_count = 1;
            while (int_nodes_count * 2 < hash_count) int_nodes_count *= 2;
            
            // Initialize the tree nodes
            unsigned char (*nodes)[hash::HASH_SIZE];
            size_t nodes_size = int_nodes_count * hash::HASH_SIZE;
            std::vector<unsigned char> nodes_vect;
            nodes_vect.resize(nodes_size, 0x00);
            // Reinterpret the data as array of 32 bit arrays
            nodes = reinterpret_cast<unsigned char (*)[32]>(nodes_vect.data());
            
            // We rarely have exactly 2^x leaves (= hashes)
            // => some do not have a partner for hashing in the lowest layer
            // => we just copy these to the layer above
            size_t singles_count = int_nodes_count * 2 - hash_count;
            memcpy(nodes, hashes, singles_count * hash::HASH_SIZE);
            
            // We will proceed and hash the remaining pairs of hashes
            size_t h_src; // points to the two concatenated hashes that will be hashed
            size_t h_dst; // points to where the resulting hash will be stored
            
            // We start after the singles which were copied in the previous step
            // and hash source hashes by two (from h_src)
            // and put the result into the nodes "in layer above" h_dst
            for (h_src = singles_count, h_dst = singles_count;
                 h_dst < int_nodes_count;
                 h_src += 2, h_dst += 1) {
                hash(hashes[h_src], 2 * hash::HASH_SIZE, nodes[h_dst]);
            }
            
            // Next we perform the same thing until we are left with only 2 hashes (nodes)
            // - but now we do it in place in nodes array
            // => in every iteration, we divide int_nodes_count by 2 as we are moving one layer up in the tree
            // - we do that until we are left with just two nodes (= last layer under the root)
            for ( ; int_nodes_count > 2; int_nodes_count /= 2) {
                for (h_src = 0, h_dst = 0;
                     h_dst < int_nodes_count;
                     h_src += 2, ++h_dst) {
                    hash(nodes[h_src], 2 * hash::HASH_SIZE, nodes[h_dst]);
                }
            }
            
            // The last two hashes (= first two fields of ints)
            // are hashed and the result is put into the root hash
            hash(nodes[0], 2 * hash::HASH_SIZE, &root_hash[0]);
            
            return OK;
    }
}

// Assembly:
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
subq $0x38, %rsp
movq (%rdi), %r14
movq 0x8(%rdi), %r13
subq %r14, %r13
setne %al
testb $0x1f, %r13b
sete %cl
testb %cl, %al
je 0x3d6b
movabsq $-0x200000020, %rax     # imm = 0xFFFFFFFDFFFFFFE0
leaq (%rax,%r13), %rcx
addq $0x20, %rax
cmpq %rax, %rcx
jae 0x3d8e
movq 0x427e(%rip), %rdi      # 0x7ff0
leaq 0x18a0(%rip), %rsi      # 0x5619
callq 0x2120
movq %rax, %rdi
callq 0x20a0
pushq $-0x1
popq %rax
jmp 0x3ea4
movq %rsi, %rdx
shrq $0x5, %r13
cmpq $0x1, %r13
je 0x3db1
cmpq $0x2, %r13
jne 0x3dc6
pushq $0x40
popq %rsi
movq %r14, %rdi
callq 0x3cf9
jmp 0x3ea2
movups (%r14), %xmm0
movups 0x10(%r14), %xmm1
movups %xmm1, 0x10(%rdx)
movups %xmm0, (%rdx)
jmp 0x3ea2
movq %rdx, -0x40(%rbp)
pushq $0x1
popq %rax
movq %rax, %rbx
addq %rax, %rax
movq %rax, %r15
subq %r13, %r15
jb 0x3dcd
movq %rbx, %rsi
shlq $0x5, %rsi
leaq -0x60(%rbp), %rdi
andq $0x0, 0x10(%rdi)
xorps %xmm0, %xmm0
movaps %xmm0, (%rdi)
leaq -0x29(%rbp), %rdx
movb $0x0, (%rdx)
callq 0x31dc
movq -0x60(%rbp), %r12
movq %r15, %rdx
shlq $0x5, %rdx
movq %r12, %rdi
movq %r14, %rsi
callq 0x20e0
shlq $0x5, %r13
subq %r13, %r14
movq %rbx, %rax
shlq $0x6, %rax
addq %rax, %r14
movq %r12, -0x38(%rbp)
subq %r13, %r12
addq %rax, %r12
pushq $0x40
popq %r13
cmpq %rbx, %r15
jae 0x3e52
movq %r14, %rdi
movq %r13, %rsi
movq %r12, %rdx
callq 0x3cf9
incq %r15
addq $0x40, %r14
addq $0x20, %r12
jmp 0x3e32
pushq $0x40
popq %r14
cmpq $0x3, %rbx
jb 0x3e89
movq -0x38(%rbp), %r13
movq %r13, %r12
movq %rbx, %r15
subq $0x1, %r15
jb 0x3e84
movq %r13, %rdi
movq %r14, %rsi
movq %r12, %rdx
callq 0x3cf9
addq $0x40, %r13
addq $0x20, %r12
jmp 0x3e66
shrq %rbx
jmp 0x3e56
pushq $0x40
popq %rsi
movq -0x38(%rbp), %rdi
movq -0x40(%rbp), %rdx
callq 0x3cf9
leaq -0x60(%rbp), %rdi
callq 0x260a
xorl %eax, %eax
addq $0x38, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq
jmp 0x3eb9
jmp 0x3eb9
jmp 0x3eb9
movq %rax, %rbx
leaq -0x60(%rbp), %rdi
callq 0x260a
movq %rbx, %rdi
callq 0x2240
nop