// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)

#include <limits.h>
#include "blockstore_impl.h"
#include "blockstore_internal.h"

int blockstore_impl_t::dequeue_read(blockstore_op_t *op)
{
    heap_entry_t *obj = heap->lock_and_read_entry(op->oid);
    if (!obj)
    {
        op->version = 0;
        op->retval = -ENOENT;
        FINISH_OP(op);
        return 2;
    }
    uint32_t fulfilled = 0;
    PRIV(op)->pending_ops = 0;
    PRIV(op)->lsn = 0;
    auto & rv = PRIV(op)->read_vec;
    uint64_t result_version = 0;
    bool found = false;
    uint32_t skip_csum = 0;
    uint32_t blk_start = op->offset, blk_end = op->offset+op->len;
    bool need_skip = dsk.csum_block_size > dsk.bitmap_granularity && !perfect_csum_update;
    if (need_skip)
    {
        PRIV(op)->lsn = obj->lsn;
        blk_start = op->offset - op->offset%dsk.csum_block_size;
        blk_end = op->offset + op->len;
        if (blk_end % dsk.csum_block_size)
            blk_end += dsk.csum_block_size - (blk_end % dsk.csum_block_size);
    }
    bool need_wait = false;
    heap->iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
    {
        if (wr->type() == BS_HEAP_DELETE)
        {
            return false;
        }
        if (!heap->is_lsn_completed(wr->lsn))
        {
            if (wr->type() == BS_HEAP_BIG_INTENT && wr->big_intent().offset < blk_end && wr->big_intent().offset+wr->big_intent().len > blk_start ||
                wr->type() == BS_HEAP_INTENT_WRITE && wr->small().offset < blk_end && wr->small().offset+wr->small().len > blk_start)
            {
                // Wait until intent write is completed
                need_wait = true;
                return false;
            }
            else if (wr->type() == BS_HEAP_SMALL_WRITE && wr->small().offset < blk_end && wr->small().offset+wr->small().len > blk_start)
            {
                // Skip entry and read the previous one
                return true;
            }
        }
        if (op->version >= wr->version && !found)
        {
            found = true;
            result_version = wr->version;
            if (op->bitmap)
            {
                memcpy(op->bitmap, wr->get_ext_bitmap(heap), dsk.clean_entry_bitmap_size);
            }
        }
        if (op->version >= wr->version)
        {
            fulfilled += prepare_read(PRIV(op)->read_vec, obj, wr, op->offset, op->offset+op->len,
                wr->type() != BS_HEAP_SMALL_WRITE ? skip_csum : 0);
            if (fulfilled == op->len ||
                wr->type() == BS_HEAP_BIG_WRITE ||
                wr->type() == BS_HEAP_BIG_INTENT ||
                wr->type() == BS_HEAP_DELETE)
            {
                return false;
            }
        }
        if (need_skip && wr->type() == BS_HEAP_SMALL_WRITE &&
            wr->small().offset < blk_end && wr->small().offset+wr->small().len > blk_start)
        {
            // Small write may mutate big write checksums during flush
            skip_csum = COPY_BUF_SKIP_CSUM;
        }
        return true;
    });
    if (need_wait)
    {
undo_wait:
        // Need to wait. undo added requests, unlock lsn
        heap->unlock_entry(op->oid);
        free_read_buffers(rv);
        rv.clear();
        return 0;
    }
    if (!found)
    {
        // May happen if there are entries but all of them are > requested version
        heap->unlock_entry(op->oid);
        op->version = 0;
        op->retval = -ENOENT;
        FINISH_OP(op);
        return 2;
    }
    assert(fulfilled == op->len);
    if (!fulfill_read(op))
    {
        goto undo_wait;
    }
    op->version = result_version;
    if (!PRIV(op)->pending_ops)
    {
        // everything is fulfilled from memory
        heap->unlock_entry(op->oid);
        op->retval = op->len;
        free_read_buffers(rv);
        FINISH_OP(op);
        return 2;
    }
    op->retval = 0;
    return 2;
}

int blockstore_impl_t::fulfill_read(blockstore_op_t *op)
{
    for (auto & vec: PRIV(op)->read_vec)
    {
        if (vec.copy_flags & (COPY_BUF_COALESCED|COPY_BUF_CSUM_FILL))
        {
            // This buffer references another one
        }
        else if (vec.copy_flags == COPY_BUF_ZERO)
        {
            memset(op->buf + vec.offset - op->offset, 0, vec.len);
        }
        else if ((vec.copy_flags & COPY_BUF_JOURNAL) && dsk.inmemory_journal)
        {
            memcpy(op->buf + vec.offset - op->offset, buffer_area + vec.disk_loc + vec.disk_offset, vec.len);
        }
        else
        {
            BS_SUBMIT_GET_SQE(sqe, data);
            data->iov = (struct iovec){ vec.buf ? vec.buf : (op->buf + vec.offset - op->offset), (size_t)vec.disk_len };
            PRIV(op)->pending_ops++;
            io_uring_prep_readv(
                sqe,
                (vec.copy_flags & COPY_BUF_JOURNAL) ? dsk.journal_fd : dsk.data_fd,
                &data->iov, 1,
                ((vec.copy_flags & COPY_BUF_JOURNAL) ? dsk.journal_offset : dsk.data_offset) + vec.disk_loc + vec.disk_offset
            );
            data->callback = [this, op](ring_data_t *data) { handle_read_event(data, op); };
        }
    }
    return 1;
}

uint32_t blockstore_impl_t::prepare_read(std::vector<copy_buffer_t> & read_vec, heap_entry_t *obj, heap_entry_t *wr, uint32_t start, uint32_t end, uint32_t skip_csum)
{
    if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
    {
        return prepare_read_with_bitmaps(read_vec, obj, wr, start, end, skip_csum);
    }
    if (wr->type() == BS_HEAP_DELETE)
    {
        return prepare_read_zero(read_vec, start, end);
    }
    return prepare_read_simple(read_vec, obj, wr, start, end, skip_csum);
}

uint32_t blockstore_impl_t::prepare_read_with_bitmaps(std::vector<copy_buffer_t> & read_vec, heap_entry_t *obj, heap_entry_t *wr, uint32_t start, uint32_t end, uint32_t skip_csum)
{
    // BIG_WRITEs contain a bitmap and we have to handle its holes
    uint32_t res = 0;
    uint8_t *bmp = wr->get_int_bitmap(heap);
    uint32_t bmp_start = start/dsk.bitmap_granularity, bmp_end = bmp_start, bmp_size = end/dsk.bitmap_granularity;
    while (bmp_start < bmp_size)
    {
        while (bmp_end < bmp_size && !(bmp[bmp_end >> 3] & (1 << (bmp_end & 0x7))))
        {
            bmp_end++;
        }
        if (bmp_end > bmp_start)
        {
            res += prepare_read_zero(read_vec, bmp_start * dsk.bitmap_granularity, bmp_end * dsk.bitmap_granularity);
        }
        bmp_start = bmp_end;
        while (bmp_end < bmp_size && (bmp[bmp_end >> 3] & (1 << (bmp_end & 0x7))))
        {
            bmp_end++;
        }
        if (bmp_end > bmp_start)
        {
            res += prepare_read_simple(read_vec, obj, wr, bmp_start * dsk.bitmap_granularity, bmp_end * dsk.bitmap_granularity, skip_csum);
            bmp_start = bmp_end;
        }
    }
    return res;
}

uint32_t blockstore_impl_t::prepare_read_zero(std::vector<copy_buffer_t> & read_vec, uint32_t start, uint32_t end)
{
    uint32_t res = 0;
    find_holes(read_vec, start, end, [&](int & pos, uint32_t start, uint32_t end)
    {
        res += end-start;
        read_vec.insert(read_vec.begin() + (pos++), (copy_buffer_t){
            .copy_flags = COPY_BUF_ZERO,
            .offset = start,
            .len = end-start,
        });
    });
    return res;
}

uint32_t blockstore_impl_t::prepare_read_simple(std::vector<copy_buffer_t> & read_vec, heap_entry_t *obj, heap_entry_t *wr, uint32_t start, uint32_t end, uint32_t skip_csum)
{
    uint32_t res = 0;
    if (wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE)
    {
        if (wr->small().offset >= end || wr->small().offset+wr->small().len <= start)
            return 0;
        start = start < wr->small().offset ? wr->small().offset : start;
        end = end > wr->small().offset+wr->small().len ? wr->small().offset+wr->small().len : end;
    }
    find_holes(read_vec, start, end, [&](int & pos, uint32_t start, uint32_t end)
    {
        res += end-start;
        if (wr->type() == BS_HEAP_SMALL_WRITE && dsk.inmemory_journal)
        {
            // read buffered data from memory
            read_vec.insert(read_vec.begin() + (pos++), (copy_buffer_t){
                .copy_flags = COPY_BUF_JOURNAL | COPY_BUF_SKIP_CSUM,
                .offset = start,
                .len = end-start,
                .disk_loc = wr->small().location - wr->small().offset,
                .disk_offset = start,
                .disk_len = end-start,
                .buf = buffer_area + wr->small().location + start - wr->small().offset,
                .wr = wr,
            });
        }
        else if (dsk.csum_block_size <= dsk.bitmap_granularity)
        {
            // simple disk read
            prepare_disk_read(read_vec, pos++, obj, wr, start, end, start, end, 0);
        }
        else
        {
            // the most complex case: read data from disk with padding
            uint32_t blk_start = start, blk_end = end;
            blk_start = (start/dsk.csum_block_size) * dsk.csum_block_size;
            blk_end = ((end-1) / dsk.csum_block_size + 1) * dsk.csum_block_size;
            if (wr->type() == BS_HEAP_INTENT_WRITE || wr->type() == BS_HEAP_SMALL_WRITE)
            {
                blk_start = blk_start < wr->small().offset ? wr->small().offset : blk_start;
                blk_end = blk_end > wr->small().offset+wr->small().len ? wr->small().offset+wr->small().len : blk_end;
            }
            if ((blk_end-1)/dsk.csum_block_size == blk_start/dsk.csum_block_size ||
                blk_end/dsk.csum_block_size == blk_start/dsk.csum_block_size+1 && blk_end != end && blk_start != start ||
                blk_end == end && blk_start == start)
            {
                // single block, exactly two partial blocks, or any number of full blocks
                // i.e. [..X.] or [..XX][XX..] or [XXXX]..[XXXX]
                prepare_disk_read(read_vec, pos++, obj, wr, blk_start, blk_end, start, end, skip_csum);
            }
            else
            {
                // one or two partial blocks plus any number of full blocks
                // i.e. [..XX][XXXX][X...]
                uint32_t full_start = (blk_start != start ? (blk_start/dsk.csum_block_size+1)*dsk.csum_block_size : blk_start);
                uint32_t full_end = (blk_end != end ? (blk_end % dsk.csum_block_size ? blk_end-blk_end%dsk.csum_block_size : blk_end - dsk.csum_block_size) : blk_end);
                if (blk_start != start) // starting padded block
                    prepare_disk_read(read_vec, pos++, obj, wr, blk_start, full_start, start, full_start, skip_csum);
                if (full_end > full_start) // full non-padded blocks
                    prepare_disk_read(read_vec, pos++, obj, wr, full_start, full_end, full_start, full_end, skip_csum);
                if (blk_end != end) // ending padded block
                    prepare_disk_read(read_vec, pos++, obj, wr, full_end, blk_end, full_end, end, skip_csum);
            }
        }
    });
    return res;
}

void blockstore_impl_t::prepare_disk_read(std::vector<copy_buffer_t> & read_vec, int pos, heap_entry_t *obj, heap_entry_t *wr,
    uint32_t blk_start, uint32_t blk_end, uint32_t start, uint32_t end, uint32_t copy_flags)
{
    uint64_t loc = 0;
    if (wr->type() == BS_HEAP_INTENT_WRITE)
    {
        heap_entry_t *big_wr = wr;
        while (big_wr && big_wr->type() == BS_HEAP_INTENT_WRITE)
        {
            big_wr = heap->prev(big_wr);
        }
        assert(big_wr->type() == BS_HEAP_BIG_WRITE || big_wr->type() == BS_HEAP_BIG_INTENT);
        loc = big_wr->big_location(heap);
    }
    else if (wr->type() == BS_HEAP_SMALL_WRITE)
    {
        loc = wr->small().location-wr->small().offset;
    }
    else
    {
        assert(wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT);
        loc = wr->big_location(heap);
    }
    copy_buffer_t vec = {
        .copy_flags = (wr->type() == BS_HEAP_SMALL_WRITE ? COPY_BUF_JOURNAL : COPY_BUF_DATA) | copy_flags,
        .offset = start,
        .len = end-start,
        .disk_loc = loc,
        .disk_offset = blk_start,
        .disk_len = blk_end - blk_start,
        .wr = wr,
    };
    if (blk_start != start || blk_end != end)
    {
        assert(!(copy_flags & COPY_BUF_CSUM_FILL));
        vec.copy_flags |= COPY_BUF_PADDED;
        if (pos > 0 && read_vec.size() >= pos &&
            read_vec[pos-1].copy_flags == vec.copy_flags &&
            read_vec[pos-1].wr == vec.wr &&
            read_vec[pos-1].disk_offset <= vec.disk_offset &&
            read_vec[pos-1].disk_offset+read_vec[pos-1].disk_len >= blk_end)
        {
            // This is the same block as the previous one, we can read it only once
            vec.copy_flags |= COPY_BUF_COALESCED;
            vec.buf = read_vec[pos-1].buf + vec.disk_offset - read_vec[pos-1].disk_offset;
        }
        else
        {
            vec.buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, vec.disk_len);
        }
    }
    read_vec.insert(read_vec.begin() + pos, vec);
}

void blockstore_impl_t::find_holes(std::vector<copy_buffer_t> & read_vec,
    uint32_t item_start, uint32_t item_end,
    std::function<void(int&, uint32_t, uint32_t)> callback)
{
    auto cur_start = item_start;
    int i = 0;
    while (cur_start < item_end)
    {
        // COPY_BUF_CSUM_FILL items are fake items inserted in the end, their offsets aren't in order
        if (i >= read_vec.size() || (read_vec[i].copy_flags & COPY_BUF_CSUM_FILL) || read_vec[i].offset >= item_end)
        {
            // Hole (at end): cur_start .. item_end
            callback(i, cur_start, item_end);
            break;
        }
        else if (read_vec[i].offset > cur_start)
        {
            // Hole: cur_start .. min(read_vec[i].offset, item_end)
            auto cur_end = read_vec[i].offset > item_end ? item_end : read_vec[i].offset;
            callback(i, cur_start, cur_end);
            cur_start = cur_end;
        }
        else if (read_vec[i].offset + read_vec[i].len > cur_start)
        {
            // Allocated: cur_start .. min(read_vec[i].offset + read_vec[i].len, item_end)
            auto cur_end = read_vec[i].offset + read_vec[i].len;
            cur_end = cur_end > item_end ? item_end : cur_end;
            //callback(i, true, cur_start, cur_end);
            cur_start = cur_end;
            i++;
        }
        else
            i++;
    }
}

void blockstore_impl_t::free_read_buffers(std::vector<copy_buffer_t> & rv)
{
    if (dsk.csum_block_size > dsk.bitmap_granularity)
    {
        for (auto & vec: rv)
        {
            if (!(vec.copy_flags & COPY_BUF_COALESCED) && vec.buf &&
                (!buffer_area || vec.buf < buffer_area || vec.buf >= (uint8_t*)buffer_area + dsk.journal_len))
            {
                free(vec.buf);
                vec.buf = NULL;
            }
        }
    }
}

void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op)
{
    live = true;
    PRIV(op)->pending_ops--;
    if (data->res != data->iov.iov_len)
    {
        // read error
        op->retval = data->res;
    }
    if (PRIV(op)->pending_ops == 0)
    {
        // verify checksums if required
        if (dsk.csum_block_size && !verify_read_checksums(op))
            op->retval = -EDOM;
        else if (op->retval == 0)
            op->retval = op->len;
        heap->unlock_entry(op->oid);
        free_read_buffers(PRIV(op)->read_vec);
        FINISH_OP(op);
    }
}

bool blockstore_impl_t::verify_read_checksums(blockstore_op_t *op)
{
    bool skip_all = false;
    if (PRIV(op)->lsn)
    {
        heap_entry_t *obj = heap->read_entry(op->oid);
        if (obj->lsn != PRIV(op)->lsn) // check top lsn
        {
            skip_all = true;
        }
    }
    auto & rv = PRIV(op)->read_vec;
    for (auto & vec: rv)
    {
        if (vec.copy_flags & COPY_BUF_ZERO)
            continue;
        if (vec.copy_flags & COPY_BUF_PADDED)
            memcpy(op->buf + vec.offset - op->offset, vec.buf + vec.offset - vec.disk_offset, vec.len);
        if (vec.copy_flags & (COPY_BUF_COALESCED|COPY_BUF_SKIP_CSUM) ||
            skip_all && vec.wr->type() != BS_HEAP_SMALL_WRITE)
            continue;
        uint8_t *buf = vec.buf ? vec.buf : (op->buf + vec.offset - op->offset);
        uint32_t *csums = (uint32_t*)(vec.wr->get_checksums(heap)
            + (vec.disk_offset/dsk.csum_block_size)*(dsk.data_csum_type & 0xFF)
            - ((vec.wr->type() == BS_HEAP_BIG_WRITE || vec.wr->type() == BS_HEAP_BIG_INTENT)
                ? 0 : (vec.wr->small().offset/dsk.csum_block_size)*(dsk.data_csum_type & 0xFF)));
        if (!heap->calc_block_checksums(csums, buf, vec.wr->get_int_bitmap(heap),
            vec.disk_offset, vec.disk_offset+vec.disk_len, false, [&](uint32_t mismatch_pos, uint32_t expected_csum, uint32_t real_csum)
            {
                printf(
                    "Checksum mismatch in object %jx:%jx v%ju, offset 0x%x in %s area at offset 0x%jx during read %x+%x: %08x expected vs %08x actual\n",
                    op->oid.inode, op->oid.stripe, op->version, mismatch_pos,
                    (vec.copy_flags & COPY_BUF_JOURNAL) ? "buffer" : "data", vec.disk_loc + vec.disk_offset,
                    op->offset, op->len,
                    expected_csum, real_csum
                );
            }))
        {
            return false;
        }
    }
    return true;
}

int blockstore_impl_t::read_bitmap(object_id oid, uint64_t target_version, void *bitmap, uint64_t *result_version)
{
    heap_entry_t *obj = heap->read_entry(oid);
    if (obj)
    {
        bool found = false;
        heap->iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
        {
            if (target_version >= wr->version)
            {
                if (wr->type() == BS_HEAP_DELETE)
                {
                    return false;
                }
                found = true;
                if (result_version)
                {
                    *result_version = wr->version;
                }
                if (bitmap)
                {
                    memcpy(bitmap, wr->get_ext_bitmap(heap), dsk.clean_entry_bitmap_size);
                }
                return false;
            }
            return true;
        });
        if (found)
        {
            return 0;
        }
    }
    if (result_version)
    {
        *result_version = 0;
    }
    if (bitmap)
    {
        memset(bitmap, 0, dsk.clean_entry_bitmap_size);
    }
    return -ENOENT;
}
