/*-
 * Public Domain 2014-present MongoDB, Inc.
 * Public Domain 2008-2014 WiredTiger, Inc.
 *
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "test_util.h"

static const char name[] = "lsm:test";
#define NUM_DOCS 100000
#define NUM_QUERIES (NUM_DOCS / 100)

static void
rand_str(uint64_t i, char *str)
{
    uint64_t x, y;

    y = strlen(str);
    for (x = y; x > y - 8; x--) {
        str[x - 1] = (char)(i % 10) + 48;
        i = i / 10;
    }
}

static void
check_str(uint64_t i, char *str, bool mod)
{
    char str2[] = "0000000000000000";

    rand_str(i, str2);
    if (mod)
        str2[0] = 'A';
    testutil_checkfmt(strcmp(str, str2), "strcmp failed, got %s, expected %s", str, str2);
}

static void
query_docs(WT_CURSOR *cursor, bool mod)
{
    WT_ITEM key, value;
    int i;

    for (i = 0; i < NUM_QUERIES; i++) {
        testutil_check(cursor->next(cursor));
        testutil_check(cursor->get_key(cursor, &key));
        testutil_check(cursor->get_value(cursor, &value));
        check_str((uint64_t)key.data, (char *)value.data, mod);
    }
    printf("%d documents read\n", NUM_QUERIES);
}

static void *
compact_thread(void *args)
{
    WT_SESSION *session;

    session = (WT_SESSION *)args;
    testutil_check(session->compact(session, name, NULL));
    return (NULL);
}

int
main(int argc, char *argv[])
{
    TEST_OPTS *opts, _opts;
    WT_CURSOR *rcursor, *wcursor;
    WT_ITEM key, value;
    WT_SESSION *session, *session2;
    pthread_t thread;
    uint64_t i;

    char str[] = "0000000000000000";

    /*
     * Create a clean test directory for this run of the test program if the environment variable
     * isn't already set (as is done by make check).
     */
    opts = &_opts;
    memset(opts, 0, sizeof(*opts));
    testutil_check(testutil_parse_opts(argc, argv, opts));
    testutil_make_work_dir(opts->home);
    testutil_check(wiredtiger_open(opts->home, NULL, "create,cache_size=200M", &opts->conn));

    testutil_check(opts->conn->open_session(opts->conn, NULL, NULL, &session));
    testutil_check(opts->conn->open_session(opts->conn, NULL, NULL, &session2));

    testutil_check(session->create(session, name, "key_format=Q,value_format=S"));

    /* Populate the table with some data. */
    testutil_check(session->open_cursor(session, name, NULL, "overwrite", &wcursor));
    for (i = 0; i < NUM_DOCS; i++) {
        wcursor->set_key(wcursor, i);
        rand_str(i, str);
        wcursor->set_value(wcursor, str);
        testutil_check(wcursor->insert(wcursor));
    }
    testutil_check(wcursor->close(wcursor));
    printf("%d documents inserted\n", NUM_DOCS);

    /* Perform some random reads */
    testutil_check(session->open_cursor(session, name, NULL, "next_random=true", &rcursor));
    query_docs(rcursor, false);
    testutil_check(rcursor->close(rcursor));

    /* Setup Transaction to pin the current values */
    testutil_check(session2->begin_transaction(session2, "isolation=snapshot"));
    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));

    /* Perform updates in a txn to confirm that we see only the original. */
    testutil_check(session->open_cursor(session, name, NULL, "overwrite", &wcursor));
    for (i = 0; i < NUM_DOCS; i++) {
        rand_str(i, str);
        str[0] = 'A';
        wcursor->set_key(wcursor, i);
        wcursor->set_value(wcursor, str);
        testutil_check(wcursor->update(wcursor));
    }
    testutil_check(wcursor->close(wcursor));
    printf("%d documents set to update\n", NUM_DOCS);

    /* Random reads, which should see the original values */
    query_docs(rcursor, false);
    testutil_check(rcursor->close(rcursor));

    /* Finish the txn */
    testutil_check(session2->rollback_transaction(session2, NULL));

    /* Random reads, which should see the updated values */
    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));
    query_docs(rcursor, true);
    testutil_check(rcursor->close(rcursor));

    /* Setup a pre-delete txn */
    testutil_check(session2->begin_transaction(session2, "isolation=snapshot"));
    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));

    /* Delete all but one document */
    testutil_check(session->open_cursor(session, name, NULL, "overwrite", &wcursor));
    for (i = 0; i < NUM_DOCS - 1; i++) {
        wcursor->set_key(wcursor, i);
        testutil_check(wcursor->remove(wcursor));
    }
    testutil_check(wcursor->close(wcursor));
    printf("%d documents deleted\n", NUM_DOCS - 1);

    /* Random reads, which should not see the deletes */
    query_docs(rcursor, true);
    testutil_check(rcursor->close(rcursor));

    /* Rollback the txn so we can see the deletes */
    testutil_check(session2->rollback_transaction(session2, NULL));

    /* Find the one remaining document 3 times */
    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));
    for (i = 0; i < 3; i++) {
        testutil_check(rcursor->next(rcursor));
        testutil_check(rcursor->get_key(rcursor, &key));
        testutil_check(rcursor->get_value(rcursor, &value));
        /* There should only be one value available to us */
        testutil_assertfmt((uint64_t)key.data == NUM_DOCS - 1, "expected %d and got %" PRIu64,
          NUM_DOCS - 1, (uint64_t)key.data);
        check_str((uint64_t)key.data, (char *)value.data, true);
    }
    printf("Found the deleted doc 3 times\n");
    testutil_check(rcursor->close(rcursor));

    /* Repopulate the table for compact. */
    testutil_check(session->open_cursor(session, name, NULL, "overwrite", &wcursor));
    for (i = 0; i < NUM_DOCS - 1; i++) {
        wcursor->set_key(wcursor, i);
        rand_str(i, str);
        str[0] = 'A';
        wcursor->set_value(wcursor, str);
        testutil_check(wcursor->insert(wcursor));
    }
    testutil_check(wcursor->close(wcursor));

    /* Run random cursor queries while compact is running */
    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));
    testutil_check(pthread_create(&thread, NULL, compact_thread, session));
    query_docs(rcursor, true);
    testutil_check(rcursor->close(rcursor));
    testutil_check(pthread_join(thread, NULL));

    /* Delete everything. Check for infinite loops */
    testutil_check(session->open_cursor(session, name, NULL, "overwrite", &wcursor));
    for (i = 0; i < NUM_DOCS; i++) {
        wcursor->set_key(wcursor, i);
        testutil_check(wcursor->remove(wcursor));
    }
    testutil_check(wcursor->close(wcursor));

    testutil_check(session2->open_cursor(session2, name, NULL, "next_random=true", &rcursor));
    for (i = 0; i < 3; i++)
        testutil_assert(rcursor->next(rcursor) == WT_NOTFOUND);
    printf("Successfully got WT_NOTFOUND\n");

    testutil_cleanup(opts);
    return (EXIT_SUCCESS);
}
