// File: /workspace/github_cpp_makefile_star2_O1/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 $0x48, %rsp
movq (%rdi), %r14
movq 0x8(%rdi), %r15
subq %r14, %r15
setne %al
testb $0x1f, %r15b
sete %cl
testb %cl, %al
je 0x4597
movabsq $-0x200000020, %rax     # imm = 0xFFFFFFFDFFFFFFE0
leaq (%r15,%rax), %rcx
addq $0x20, %rax
cmpq %rax, %rcx
jae 0x45e3
movq 0x3a52(%rip), %rbx      # 0x7ff0
leaq 0x10bf(%rip), %rsi      # 0x5664
movl $0x1f, %edx
movq %rbx, %rdi
callq 0x2160
movq (%rbx), %rax
movq -0x18(%rax), %rdi
addq %rbx, %rdi
movl $0xa, %esi
callq 0x2120
movsbl %al, %esi
movq %rbx, %rdi
callq 0x2040
movq %rax, %rdi
callq 0x20d0
movl $0xffffffff, %eax       # imm = 0xFFFFFFFF
jmp 0x477d
movq %rsi, %r9
shrq $0x5, %r15
cmpq $0x1, %r15
je 0x4620
cmpq $0x2, %r15
jne 0x4637
movq $0x20, (%rsp)
movl $0x40, %ecx
movl $0x440, %edi            # imm = 0x440
movl $0x200, %esi            # imm = 0x200
movq %r14, %rdx
movl $0x1, %r8d
callq 0x48d5
jmp 0x477b
movups (%r14), %xmm0
movups 0x10(%r14), %xmm1
movups %xmm1, 0x10(%r9)
movups %xmm0, (%r9)
jmp 0x477b
movq %r9, -0x40(%rbp)
movl $0x1, %eax
movq %rax, %r13
addq %rax, %rax
movq %rax, %rbx
subq %r15, %rbx
jb 0x4640
movq %r13, %rsi
shlq $0x5, %rsi
xorps %xmm0, %xmm0
leaq -0x60(%rbp), %rdi
movaps %xmm0, (%rdi)
movq $0x0, 0x10(%rdi)
leaq -0x29(%rbp), %rdx
movb $0x0, (%rdx)
callq 0x37b0
movq -0x60(%rbp), %rdi
movq %rbx, %rdx
shlq $0x5, %rdx
movq %rdi, -0x38(%rbp)
movq %r14, %rsi
callq 0x20f0
cmpq %r13, %rbx
jae 0x46e5
movq %r15, %rbx
subq %r13, %rbx
shlq $0x5, %r15
movq -0x38(%rbp), %r12
subq %r15, %r12
movq %r13, %rax
shlq $0x6, %rax
addq %rax, %r12
subq %r15, %r14
addq %rax, %r14
movq $0x20, (%rsp)
movl $0x40, %ecx
movl $0x440, %edi            # imm = 0x440
movl $0x200, %esi            # imm = 0x200
movq %r14, %rdx
movl $0x1, %r8d
movq %r12, %r9
callq 0x48d5
addq $0x20, %r12
addq $0x40, %r14
decq %rbx
jne 0x46b0
cmpq $0x3, %r13
movq -0x38(%rbp), %r15
jb 0x4744
cmpq $0x1, %r13
movq %r13, %rbx
adcq $0x0, %rbx
movq %r15, %r14
movq %r15, %r12
movq $0x20, (%rsp)
movl $0x40, %ecx
movl $0x440, %edi            # imm = 0x440
movl $0x200, %esi            # imm = 0x200
movq %r14, %rdx
movl $0x1, %r8d
movq %r12, %r9
callq 0x48d5
addq $0x20, %r12
addq $0x40, %r14
decq %rbx
jne 0x4700
movq %r13, %rax
shrq %rax
cmpq $0x5, %r13
movq %rax, %r13
ja 0x46ef
movq $0x20, (%rsp)
movl $0x40, %ecx
movl $0x440, %edi            # imm = 0x440
movl $0x200, %esi            # imm = 0x200
movq %r15, %rdx
movl $0x1, %r8d
movq -0x40(%rbp), %r9
callq 0x48d5
movq -0x60(%rbp), %rdi
testq %rdi, %rdi
je 0x477b
callq 0x2110
xorl %eax, %eax
addq $0x48, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq
jmp 0x4792
jmp 0x4792
jmp 0x4792
movq %rax, %rbx
movq -0x60(%rbp), %rdi
testq %rdi, %rdi
je 0x47a3
callq 0x2110
movq %rbx, %rdi
callq 0x2220
nop