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

#include "blockstore_impl.h"
#include "blockstore_internal.h"

int blockstore_impl_t::continue_sync(blockstore_op_t *op)
{
    if (!PRIV(op)->op_state)
    {
        op->retval = 0;
        PRIV(op)->lsn = 0;
    }
    int res = do_sync(op, 0);
    if (res == 2)
    {
        FINISH_OP(op);
    }
    return res;
}

bool blockstore_impl_t::has_unsynced()
{
    bool data = (!dsk.disable_data_fsync && unsynced_data_write_count);
    bool buffer = (!dsk.disable_journal_fsync && unsynced_buffer_write_count);
    bool meta = (!dsk.disable_meta_fsync && unsynced_meta_write_count);
    return data || buffer || meta;
}

bool blockstore_impl_t::submit_fsyncs(int & wait_count)
{
    int n = (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync) +
        (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.journal_fd != dsk.meta_fd) +
        (unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd);
    if (ringloop->space_left() < n)
    {
        return false;
    }
    if (!n)
    {
        return true;
    }
    auto cb = [this, & wait_count](ring_data_t *data)
    {
        if (data->res != 0)
            disk_error_abort("sync meta", data->res, 0);
        wait_count--;
        assert(wait_count >= 0);
        if (!wait_count)
            ringloop->wakeup();
    };
    if (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync)
    {
        // fsync meta
        io_uring_sqe *sqe = get_sqe();
        assert(sqe);
        ring_data_t *data = ((ring_data_t*)sqe->user_data);
        io_uring_prep_fsync(sqe, dsk.meta_fd, IORING_FSYNC_DATASYNC);
        data->iov = { 0 };
        data->callback = cb;
        wait_count++;
    }
    if (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.meta_fd != dsk.journal_fd)
    {
        // fsync buffer
        io_uring_sqe *sqe = get_sqe();
        assert(sqe);
        ring_data_t *data = ((ring_data_t*)sqe->user_data);
        io_uring_prep_fsync(sqe, dsk.journal_fd, IORING_FSYNC_DATASYNC);
        data->iov = { 0 };
        data->callback = cb;
        wait_count++;
    }
    if (unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd)
    {
        // fsync data
        io_uring_sqe *sqe = get_sqe();
        assert(sqe);
        ring_data_t *data = ((ring_data_t*)sqe->user_data);
        io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
        data->iov = { 0 };
        data->callback = cb;
        wait_count++;
    }
    unsynced_data_write_count = 0;
    unsynced_buffer_write_count = 0;
    unsynced_meta_write_count = 0;
    return true;
}

int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
{
    int op_state = PRIV(op)->op_state - base_state;
    if (op_state == 1) goto resume_1;
    if (op_state == 2) goto resume_2;
    assert(!op_state);
    if (flusher->get_syncing_buffer())
    {
        // Wait for flusher-initiated sync
        return 0;
    }
    if (!has_unsynced())
    {
        // We can return immediately because sync only syncs previous writes
        unsynced_data_write_count = unsynced_buffer_write_count = unsynced_meta_write_count = 0;
        return 2;
    }
    assert(!PRIV(op)->lsn);
    PRIV(op)->lsn = heap->get_completed_lsn();
    if (!submit_fsyncs(PRIV(op)->pending_ops))
    {
        PRIV(op)->wait_detail = 1;
        PRIV(op)->wait_for = WAIT_SQE;
        return 0;
    }
resume_1:
    if (PRIV(op)->pending_ops > 0)
    {
        PRIV(op)->op_state = base_state+1;
        return 1;
    }
resume_2:
    heap->mark_lsn_fsynced(PRIV(op)->lsn);
    return 2;
}
