/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "block_cache.h"
#include "block_mac.h"

extern bool print_lookup; /* TODO: remove */

struct transaction;

#define BLOCK_TREE_MAX_DEPTH (9)

/**
 * struct block_tree - In-memory state of block backed B+ tree
 * @block_size:             Block size
 * @key_size:               Number of bytes used per key. 0 < @key_size <= 8.
 * @child_data_size:        Child or data size. The value at index 0 is the
 *                          number of bytes used for each child block_mac in
 *                          internal nodes, and the value at index 1 is number
 *                          of bytes stored in each data entry in leaf nodes.
 *                          0 < @child_data_size[n] <= sizeof(struct block_mac)
 *                          == 24.
 * @key_count:              Array with number of keys per node. The value at
 *                          index 0 applies to internal nodes and the value at
 *                          index 1 applied to leaf nodes.
 * @root:                   Block number and mac value for root block in tree.
 * @inserting:              Data currently beeing added to full node in the
 *                          tree. Allows the tree to be read while it is
 *                          updating.
 * @inserting.block:        Block number of node that is updating.
 * @inserting.key:          Key of entry that should be added.
 * @inserting.child:        Child that should be added to an internal node.
 * @inserting.data:         Data that should be added to a leaf node.
 * @update_count:           Update counter used to check that the three has not
 *                          been modified after a block_tree_path was created.
 * @root_block_changed:     %true if root block was allocated or copied after
 *                          this tree struct was initialized. %false otherwise.
 * @updating:               Used to relax some debug check while the tree is
 *                          updating, and to detect reentrant updates.
 * @copy_on_write:          %true if tree is persistent, %false if tree should
 *                          be discarded when completing transaction. Affects
 *                          which set block are allocated from, and if
 *                          copy-on-write opearation should be enabled.
 * @allow_copy_on_write:    %false if @allow_copy_on_write is %false or if
 *                          tree is read-only, %true otherwise.
 */
struct block_tree {
    size_t block_size;
    size_t key_size;
    size_t child_data_size[2]; /* 0: internal/child, 1: leaf/data */
    size_t key_count[2];       /* 0: internal, 1: leaf */
    struct block_mac root;
    struct {
        data_block_t block;
        data_block_t key;
        struct block_mac child;
        struct block_mac data;
    } inserting;
    int update_count;
    bool root_block_changed;
    bool updating;
    bool copy_on_write;
    bool allow_copy_on_write;
};

#define BLOCK_TREE_INITIAL_VALUE(block_tree)                                \
    {                                                                       \
        0, 0, {0, 0}, {0, 0}, BLOCK_MAC_INITIAL_VALUE(block_tree.root),     \
                {0, 0, BLOCK_MAC_INITIAL_VALUE(block_tree.inserting.child), \
                 BLOCK_MAC_INITIAL_VALUE(block_tree.inserting.data)},       \
                0, 0, 0, 0, 0                                               \
    }

/**
 * struct block_tree_path_entry - block tree path entry
 * @block_mac:              Block number and mac of tree node
 * @index:                  Child or data index
 * @prev_key:               Key at @index - 1, or left key in parent when
 *                          @index == 0.
 * @next_key:               Key at @index. Or, right key in parent if key at
 *                          @index is not valid (0 or out of range).
 */
struct block_tree_path_entry {
    struct block_mac block_mac;
    unsigned int index;
    data_block_t prev_key;
    data_block_t next_key;
};

/**
 * struct block_tree_path - block tree
 * @entry:              Array of block tree path entries.
 * @count:              Number of entries in @entry.
 * @data:               Data found in leaf node at @entry[@count-1].index.
 * @tr:                 Transaction object.
 * @tree:               Tree object.
 * @tree_update_count:  @tree.update_count at time of walk.
 */
struct block_tree_path {
    struct block_tree_path_entry entry[BLOCK_TREE_MAX_DEPTH];
    unsigned int count;
    struct block_mac data;
    struct transaction* tr;
    struct block_tree* tree;
    int tree_update_count;
};

void block_tree_print(struct transaction* tr, const struct block_tree* tree);
bool block_tree_check(struct transaction* tr, const struct block_tree* tree);

void block_tree_walk(struct transaction* state,
                     struct block_tree* tree,
                     data_block_t key,
                     bool key_is_max,
                     struct block_tree_path* path);

void block_tree_path_next(struct block_tree_path* path);

static inline data_block_t block_tree_path_get_key(
        struct block_tree_path* path) {
    return (path->count > 0) ? path->entry[path->count - 1].next_key : 0;
}

static inline data_block_t block_tree_path_get_data(
        struct block_tree_path* path) {
    return block_mac_to_block(path->tr, &path->data);
}

static inline struct block_mac block_tree_path_get_data_block_mac(
        struct block_tree_path* path) {
    return path->data;
}

void block_tree_path_put_dirty(struct transaction* tr,
                               struct block_tree_path* path,
                               int path_index,
                               void* data,
                               struct obj_ref* data_ref);

void block_tree_insert(struct transaction* state,
                       struct block_tree* tree,
                       data_block_t key,
                       data_block_t data);

void block_tree_insert_block_mac(struct transaction* state,
                                 struct block_tree* tree,
                                 data_block_t key,
                                 struct block_mac data);

void block_tree_update(struct transaction* state,
                       struct block_tree* tree,
                       data_block_t old_key,
                       data_block_t old_data,
                       data_block_t new_key,
                       data_block_t new_data);

void block_tree_update_block_mac(struct transaction* state,
                                 struct block_tree* tree,
                                 data_block_t old_key,
                                 struct block_mac old_data,
                                 data_block_t new_key,
                                 struct block_mac new_data);

void block_tree_remove(struct transaction* state,
                       struct block_tree* tree,
                       data_block_t key,
                       data_block_t data);

void block_tree_init(struct block_tree* tree,
                     size_t block_size,
                     size_t key_size,
                     size_t child_size,
                     size_t data_size);

void block_tree_copy(struct block_tree* dst, const struct block_tree* src);

#if BUILD_STORAGE_TEST
void block_tree_check_config(struct block_device* dev);
void block_tree_check_config_done(void);
#endif