// File: 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
subq $0xc0, %rsp
movq %rdi, -0x10(%rbp)
movq %rsi, -0x18(%rbp)
movq -0x10(%rbp), %rdi
callq 0x46b0
cmpq $0x0, %rax
je 0x7dc5
movq -0x10(%rbp), %rdi
callq 0x46b0
andq $0x1f, %rax
cmpq $0x0, %rax
je 0x7df3
movq 0x5224(%rip), %rdi      # 0xcff0
leaq 0x1848(%rip), %rsi      # 0x961b
callq 0x2160
movq %rax, %rdi
movq 0x51de(%rip), %rsi      # 0xcfc0
callq 0x2190
movl $0xffffffff, -0x4(%rbp) # imm = 0xFFFFFFFF
jmp 0x80d4
movq -0x10(%rbp), %rdi
callq 0x46b0
shrq $0x5, %rax
movq %rax, -0x20(%rbp)
cmpq $0x0, -0x20(%rbp)
je 0x7e15
cmpq $0x10000000, -0x20(%rbp) # imm = 0x10000000
jbe 0x7e43
movq 0x51d4(%rip), %rdi      # 0xcff0
leaq 0x17f8(%rip), %rsi      # 0x961b
callq 0x2160
movq %rax, %rdi
movq 0x518e(%rip), %rsi      # 0xcfc0
callq 0x2190
movl $0xffffffff, -0x4(%rbp) # imm = 0xFFFFFFFF
jmp 0x80d4
movq -0x10(%rbp), %rdi
callq 0x46d0
movq %rax, -0x28(%rbp)
movq -0x20(%rbp), %rax
movq %rax, -0x90(%rbp)
subq $0x1, %rax
je 0x7e75
jmp 0x7e63
movq -0x90(%rbp), %rax
subq $0x2, %rax
je 0x7ebf
jmp 0x7ef7
movq -0x18(%rbp), %rdi
xorl %eax, %eax
movl %eax, %esi
callq 0x2f00
movq %rax, -0xa0(%rbp)
movq -0x28(%rbp), %rax
movq %rax, -0x98(%rbp)
movq -0x18(%rbp), %rdi
callq 0x2ef0
movq -0xa0(%rbp), %rdi
movq -0x98(%rbp), %rsi
movq %rax, %rdx
callq 0x2120
movl $0x0, -0x4(%rbp)
jmp 0x80d4
movq -0x28(%rbp), %rax
movq %rax, -0xa8(%rbp)
movq -0x18(%rbp), %rdi
xorl %eax, %eax
movl %eax, %esi
callq 0x2f00
movq -0xa8(%rbp), %rdi
movq %rax, %rdx
movl $0x40, %esi
callq 0x7d30
movl $0x0, -0x4(%rbp)
jmp 0x80d4
movq $0x1, -0x30(%rbp)
movq -0x30(%rbp), %rax
shlq %rax
cmpq -0x20(%rbp), %rax
jae 0x7f19
movq -0x30(%rbp), %rax
shlq %rax
movq %rax, -0x30(%rbp)
jmp 0x7eff
movq -0x30(%rbp), %rax
shlq $0x5, %rax
movq %rax, -0x40(%rbp)
leaq -0x58(%rbp), %rdi
movq %rdi, -0xb0(%rbp)
callq 0x2860
movq -0xb0(%rbp), %rdi
movq -0x40(%rbp), %rsi
movb $0x0, -0x59(%rbp)
leaq -0x59(%rbp), %rdx
callq 0x45e0
jmp 0x7f4f
leaq -0x58(%rbp), %rdi
callq 0x4b40
movq %rax, -0x38(%rbp)
movq -0x30(%rbp), %rax
shlq %rax
subq -0x20(%rbp), %rax
movq %rax, -0x78(%rbp)
movq -0x38(%rbp), %rdi
movq -0x28(%rbp), %rsi
movq -0x78(%rbp), %rdx
shlq $0x5, %rdx
callq 0x2120
movq -0x78(%rbp), %rax
movq %rax, -0x80(%rbp)
movq -0x78(%rbp), %rax
movq %rax, -0x88(%rbp)
movq -0x88(%rbp), %rax
cmpq -0x30(%rbp), %rax
jae 0x8009
movq -0x28(%rbp), %rdi
movq -0x80(%rbp), %rax
shlq $0x5, %rax
addq %rax, %rdi
movq -0x38(%rbp), %rdx
movq -0x88(%rbp), %rax
shlq $0x5, %rax
addq %rax, %rdx
movl $0x40, %esi
callq 0x7d30
jmp 0x7fcd
jmp 0x7fcf
movq -0x80(%rbp), %rax
addq $0x2, %rax
movq %rax, -0x80(%rbp)
movq -0x88(%rbp), %rax
addq $0x1, %rax
movq %rax, -0x88(%rbp)
jmp 0x7f93
movq %rax, %rcx
movl %edx, %eax
movq %rcx, -0x68(%rbp)
movl %eax, -0x6c(%rbp)
leaq -0x58(%rbp), %rdi
callq 0x2930
jmp 0x80e0
jmp 0x800b
cmpq $0x2, -0x30(%rbp)
jbe 0x8096
movq $0x0, -0x80(%rbp)
movq $0x0, -0x88(%rbp)
movq -0x88(%rbp), %rax
cmpq -0x30(%rbp), %rax
jae 0x8084
movq -0x38(%rbp), %rdx
movq -0x80(%rbp), %rax
shlq $0x5, %rax
movq %rdx, %rdi
addq %rax, %rdi
movq -0x88(%rbp), %rax
shlq $0x5, %rax
addq %rax, %rdx
movl $0x40, %esi
callq 0x7d30
jmp 0x8062
jmp 0x8064
movq -0x80(%rbp), %rax
addq $0x2, %rax
movq %rax, -0x80(%rbp)
movq -0x88(%rbp), %rax
addq $0x1, %rax
movq %rax, -0x88(%rbp)
jmp 0x8029
jmp 0x8086
movq -0x30(%rbp), %rax
shrq %rax
movq %rax, -0x30(%rbp)
jmp 0x800b
movq -0x38(%rbp), %rax
movq %rax, -0xb8(%rbp)
movq -0x18(%rbp), %rdi
xorl %eax, %eax
movl %eax, %esi
callq 0x2f00
movq -0xb8(%rbp), %rdi
movq %rax, %rdx
movl $0x40, %esi
callq 0x7d30
jmp 0x80c4
movl $0x0, -0x4(%rbp)
leaq -0x58(%rbp), %rdi
callq 0x2930
movl -0x4(%rbp), %eax
addq $0xc0, %rsp
popq %rbp
retq
movq -0x68(%rbp), %rdi
callq 0x22d0
nopl (%rax)