#!/usr/bin/env python
#
# 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.
#

import glob, os, shutil, string, subprocess
import wiredtiger

from wtdataset import SimpleDataSet, SimpleIndexDataSet, ComplexDataSet

# Python has a filecmp.cmp function, but different versions of python approach
# file comparison differently.  To make sure we get byte for byte comparison,
# we define it here.
def compare_files(testcase, filename1, filename2):
    testcase.pr('compare_files: ' + filename1 + ', ' + filename2)
    bufsize = 4096
    if os.path.getsize(filename1) != os.path.getsize(filename2):
        print('file comparison failed: ' + filename1 + ' size ' +\
            str(os.path.getsize(filename1)) + ' != ' + filename2 +\
            ' size ' + str(os.path.getsize(filename2)))
        return False
    with open(filename1, "rb") as fp1:
        with open(filename2, "rb") as fp2:
            while True:
                b1 = fp1.read(bufsize)
                b2 = fp2.read(bufsize)
                if b1 != b2:
                    return False
                # Files are identical size.
                if not b1:
                    return True

# Iterate over a set of tables, ensuring that they have identical contents
def compare_tables(testcase, session, uris, config=None):
    cursors = list()
    for next_uri in uris:
        cursors.append(session.open_cursor(next_uri, None, config))

    try:
        done = False
        while not done:
            keys = list()
            for next_cursor in cursors:
                if (next_cursor.next() == wiredtiger.WT_NOTFOUND):
                    done = True
                    break
                keys.append(next_cursor.get_value())
            match = all(x == keys[0] for x in keys)
            if not match:
                return False

        return True
    finally:
        for c in cursors:
            c.close()

# Confirm a URI doesn't exist.
def confirm_does_not_exist(testcase, uri):
    testcase.pr('confirm_does_not_exist: ' + uri)
    testcase.assertRaises(wiredtiger.WiredTigerError,
        lambda: testcase.session.open_cursor(uri, None))
    testcase.assertEqual(glob.glob('*' + uri.split(":")[-1] + '*'), [],
        'confirm_does_not_exist: URI exists, file name matching \"' +
        uri.split(":")[1] + '\" found')

# Confirm a URI exists and is empty.
def confirm_empty(testcase, uri):
    testcase.pr('confirm_empty: ' + uri)
    cursor = testcase.session.open_cursor(uri, None)
    if cursor.value_format == '8t':
        for key,val in cursor:
            testcase.assertEqual(val, 0)
    else:
        testcase.assertEqual(cursor.next(), wiredtiger.WT_NOTFOUND)
    cursor.close()

# Copy a WT home directory.
def copy_wiredtiger_home(testcase, olddir, newdir, aligned=True):
    # Unaligned copy requires 'dd', which may not be available on Windows.
    if not aligned and os.name == "nt":
        raise AssertionError(
            'copy_wiredtiger_home: unaligned copy impossible on Windows')
    shutil.rmtree(newdir, ignore_errors=True)
    os.mkdir(newdir)
    for fname in os.listdir(olddir):
        fullname = os.path.join(olddir, fname)
        # Skip lock file, on Windows it is locked.
        # Skip temporary log files.
        if os.path.isfile(fullname) and "WiredTiger.lock" not in fullname and \
            "WiredTigerTmplog" not in fullname and \
            "WiredTigerPreplog" not in fullname:
            # Use a dd command that does not align on a block boundary.
            if aligned:
                try:
                    shutil.copy(fullname, newdir)
                except OSError as e:
                    # If the copy failed it means the file was removed underneath us in the
                    # short time after we verified the files existence. This can happen if we
                    # are copying wiredtiger log files at the same time they are getting
                    # archived by the background log server thread. To avoid failing in this case
                    # we can log/print the removed file and continue.
                    if "WiredTigerLog" not in fullname:
                        raise e
                    else:
                        testcase.pr('Skipping logfile %s: No longer exists' % fname)
                        continue
            else:
                fullname = os.path.join(olddir, fname)
                inpf = 'if=' + fullname
                outf = 'of=' + newdir + '/' + fullname
                cmd_list = ['dd', inpf, outf, 'bs=300']
                a = subprocess.Popen(cmd_list)
                a.wait()

# Simulate a crash from olddir and restart in newdir.
def simulate_crash_restart(testcase, olddir, newdir):
    # With the connection still open, copy files to new directory.
    copy_wiredtiger_home(testcase, olddir, newdir)
    # Close the original connection and open to new directory.
    # NOTE:  This really cannot test the difference between the
    # write-no-sync (off) version of log_flush and the sync
    # version since we're not crashing the system itself.
    testcase.close_conn()
    testcase.conn = testcase.setUpConnectionOpen(newdir)
    testcase.session = testcase.setUpSessionOpen(testcase.conn)
