#include "recv_install_snapshot.h"

#include "assert.h"
#include "convert.h"
#include "flags.h"
#include "log.h"
#include "recv.h"
#include "replication.h"
#include "tracing.h"

#define tracef(...) Tracef(r->tracer, __VA_ARGS__)

static void installSnapshotSendCb(struct raft_io_send *req, int status)
{
    (void)status;
    raft_free(req);
}

int recvInstallSnapshot(struct raft *r,
                        const raft_id id,
                        const char *address,
                        struct raft_install_snapshot *args)
{
    struct raft_io_send *req;
    struct raft_message message;
    struct raft_append_entries_result *result = &message.append_entries_result;
    int rv;
    int match;
    bool async;

    assert(address != NULL);
    tracef(
        "self:%llu from:%llu@%s conf_index:%llu last_index:%llu last_term:%llu "
        "term:%llu",
        r->id, id, address, args->conf_index, args->last_index, args->last_term,
        args->term);

    result->rejected = args->last_index;
    result->last_log_index = logLastIndex(r->log);
    result->version = RAFT_APPEND_ENTRIES_RESULT_VERSION;
    result->features = RAFT_DEFAULT_FEATURE_FLAGS;

    rv = recvEnsureMatchingTerms(r, args->term, &match);
    if (rv != 0) {
        return rv;
    }

    if (match < 0) {
        tracef("local term is higher -> reject ");
        goto reply;
    }

    /* TODO: this logic duplicates the one in the AppendEntries handler */
    assert(r->state == RAFT_FOLLOWER || r->state == RAFT_CANDIDATE);
    assert(r->current_term == args->term);
    if (r->state == RAFT_CANDIDATE) {
        assert(match == 0);
        tracef("discovered leader -> step down ");
        convertToFollower(r);
    }

    rv = recvUpdateLeader(r, id, address);
    if (rv != 0) {
        return rv;
    }
    r->election_timer_start = r->io->time(r->io);

    rv = replicationInstallSnapshot(r, args, &result->rejected, &async);
    if (rv != 0) {
        tracef("replicationInstallSnapshot failed %d", rv);
        return rv;
    }

    if (async) {
        return 0;
    }

    if (result->rejected == 0) {
        /* Echo back to the leader the point that we reached. */
        result->last_log_index = args->last_index;
    }

reply:
    result->term = r->current_term;

    /* Free the snapshot data. */
    raft_configuration_close(&args->conf);
    raft_free(args->data.base);

    message.type = RAFT_IO_APPEND_ENTRIES_RESULT;
    message.server_id = id;
    message.server_address = address;

    req = raft_malloc(sizeof *req);
    if (req == NULL) {
        return RAFT_NOMEM;
    }
    req->data = r;

    rv = r->io->send(r->io, req, &message, installSnapshotSendCb);
    if (rv != 0) {
        raft_free(req);
        return rv;
    }

    return 0;
}

#undef tracef
