/*-
 * Public Domain 2014-2018 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);
}
