/*-
 * 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.
 *
 * ex_log.java
 * 	demonstrates how to logging and log cursors.
 */
package com.wiredtiger.examples;
import com.wiredtiger.db.*;
import java.io.*;
import java.util.*;

class Lsn {
    int file;
    int offset;
}

public class ex_log {

    public static String home1 = "WT_HOME_LOG_1";
    public static String home2 = "WT_HOME_LOG_2";
    public static String uri = "table:logtest";

    public static final String CONN_CONFIG =
        "create,cache_size=100MB,log=(archive=false,enabled=true)";
    public static final int MAX_KEYS = 10;

    static Session
    setup_copy()
        throws WiredTigerException
    {
        int ret = 0;
        Connection conn;

        conn = wiredtiger.open(home2, CONN_CONFIG);
        Session session = conn.open_session(null);
        session.create(uri, "key_format=S,value_format=S");
        return (session);
    }

    static int
    compare_tables(Session session, Session sess_copy)
        throws WiredTigerException
    {
        int ret;

        Cursor cursor = session.open_cursor(uri, null, null);
        Cursor curs_copy = sess_copy.open_cursor(uri, null, null);

        while ((ret = cursor.next()) == 0) {
            ret = curs_copy.next();
            String key = cursor.getKeyString();
            String value = cursor.getValueString();
            String key_copy = curs_copy.getKeyString();
            String value_copy = curs_copy.getValueString();
            if (!key.equals(key_copy) ||
                !value.equals(value_copy)) {
                System.err.println(
                    "Mismatched: key " + key + ", key_copy " + key_copy +
                    ", value " + value + ", value_copy " + value_copy);
                ret = cursor.close();
                ret = curs_copy.close();
                return (1);
            }
        }
        if (ret != wiredtiger.WT_NOTFOUND)
            System.err.println("WT_CURSOR.next: " +
                wiredtiger.wiredtiger_strerror(ret));
        ret = cursor.close();

        ret = curs_copy.next();
        if (ret != wiredtiger.WT_NOTFOUND)
            System.err.println("WT_CURSOR.next: " +
                wiredtiger.wiredtiger_strerror(ret));
        ret = curs_copy.close();

        return (ret);
    }

    /*! [log cursor walk] */
    static void
    print_record(Lsn lsn, int opcount,
       int rectype, int optype, long txnid, int fileid,
       byte[] key, byte[] value)
    {
        System.out.print(
            "LSN [" + lsn.file + "][" + lsn.offset + "]." + opcount +
            ": record type " + rectype + " optype " + optype +
            " txnid " + txnid + " fileid " + fileid);
        System.out.println(" key size " + key.length +
            " value size " + value.length);
        if (rectype == wiredtiger.WT_LOGREC_MESSAGE)
            System.out.println("Application Record: " + new String(value));
    }

    /*
     * simple_walk_log --
     *	A simple walk of the log.
     */
    static int
    simple_walk_log(Session session, int count_min)
        throws WiredTigerException
    {
        Cursor cursor;
        Lsn lsn = new Lsn();
        byte[] logrec_key, logrec_value;
        long txnid;
        int fileid, opcount, optype, rectype;
        int count, ret;

        /*! [log cursor open] */
        cursor = session.open_cursor("log:", null, null);
        /*! [log cursor open] */

        count = 0;
        while ((ret = cursor.next()) == 0) {
            count++;
            /*! [log cursor get_key] */
            lsn.file = cursor.getKeyInt();
            lsn.offset = cursor.getKeyInt();
            opcount = cursor.getKeyInt();
            /*! [log cursor get_key] */
            /*! [log cursor get_value] */
            txnid = cursor.getValueLong();
            rectype = cursor.getValueInt();
            optype = cursor.getValueInt();
            fileid = cursor.getValueInt();
            logrec_key = cursor.getValueByteArray();
            logrec_value = cursor.getValueByteArray();
            /*! [log cursor get_value] */

            print_record(lsn, opcount,
                rectype, optype, txnid, fileid, logrec_key, logrec_value);
        }
        if (ret == wiredtiger.WT_NOTFOUND)
            ret = 0;
        ret = cursor.close();
	if (count < count_min) {
            System.err.println("Expected minimum " + count_min +
                               " records, found " + count);
            return (1);
        }
        return (ret);
    }
    /*! [log cursor walk] */

    static int
    walk_log(Session session)
        throws WiredTigerException
    {
        Connection wt_conn2;
        Cursor cursor, cursor2;
        Lsn lsn, lsnsave;
        byte[] logrec_key, logrec_value;
        Session session2;
        long txnid;
        int fileid, opcount, optype, rectype;
        int i, ret;
        boolean in_txn, first;

        session2 = setup_copy();
        wt_conn2 = session2.getConnection();
        cursor = session.open_cursor("log:", null, null);
        cursor2 = session2.open_cursor(uri, null, "raw=true");
        i = 0;
        in_txn = false;
        txnid = 0;
        lsn = new Lsn();
        lsnsave = new Lsn();
        while ((ret = cursor.next()) == 0) {
            lsn.file = cursor.getKeyInt();
            lsn.offset = cursor.getKeyInt();
            opcount = cursor.getKeyInt();

            /*
             * Save one of the LSNs we get back to search for it
             * later.  Pick a later one because we want to walk from
             * that LSN to the end (where the multi-step transaction
             * was performed).  Just choose the record that is MAX_KEYS.
             */
            if (++i == MAX_KEYS) {
                lsnsave.file = lsn.file;
                lsnsave.offset = lsn.offset;
            }
            txnid = cursor.getValueLong();
            rectype = cursor.getValueInt();
            optype = cursor.getValueInt();
            fileid = cursor.getValueInt();
            logrec_key = cursor.getValueByteArray();
            logrec_value = cursor.getValueByteArray();

            print_record(lsn, opcount,
                rectype, optype, txnid, fileid, logrec_key, logrec_value);

            /*
             * If we are in a transaction and this is a new one, end
             * the previous one.
             */
            if (in_txn && opcount == 0) {
                ret = session2.commit_transaction(null);
                in_txn = false;
            }

            /*
             * If the operation is a put, replay it here on the backup
             * connection.  Note, we cheat by looking at the fileid.
             * The metadata is fileid 0, skip its records.
             */
            if (fileid != 0 && rectype == wiredtiger.WT_LOGREC_COMMIT &&
                optype == wiredtiger.WT_LOGOP_ROW_PUT) {
                if (!in_txn) {
                    ret = session2.begin_transaction(null);
                    in_txn = true;
                }
                cursor2.putKeyByteArray(logrec_key);
                cursor2.putValueByteArray(logrec_value);
                ret = cursor2.insert();
            }
        }
        if (in_txn)
            ret = session2.commit_transaction(null);

        ret = cursor2.close();
        /*
         * Compare the tables after replay.  They should be identical.
         */
        if (compare_tables(session, session2) != 0) {
            cursor.close();
            session2.close(null);
            wt_conn2.close(null);
            return (ret);
        }
        ret = session2.close(null);
        ret = wt_conn2.close(null);

        ret = cursor.reset();
        /*! [log cursor set_key] */
        cursor.putKeyInt(lsnsave.file);
        cursor.putKeyInt(lsnsave.offset);
        cursor.putKeyInt(0);
        /*! [log cursor set_key] */
        /*! [log cursor search] */
        ret = cursor.search();
        /*! [log cursor search] */
        System.out.println("Reset to saved...");
        /*
         * Walk all records starting with this key.
         */
        first = true;
        while (ret == 0) {
            lsn.file = cursor.getKeyInt();
            lsn.offset = cursor.getKeyInt();
            opcount = cursor.getKeyInt();
            if (first) {
                first = false;
                if (lsnsave.file != lsn.file ||
                    lsnsave.offset != lsn.offset) {
                    System.err.println("search returned the wrong LSN");
                    System.exit(1);
                }
            }
            txnid = cursor.getValueLong();
            rectype = cursor.getValueInt();
            optype = cursor.getValueInt();
            fileid = cursor.getValueInt();
            logrec_key = cursor.getValueByteArray();
            logrec_value = cursor.getValueByteArray();

            print_record(lsn, opcount, rectype, optype, txnid,
                fileid, logrec_key, logrec_value);

            ret = cursor.next();
            if (ret != 0)
                break;
        }
        ret = cursor.close();
        return (ret);
    }

    public static int
    logExample()
        throws WiredTigerException
    {
        Connection wt_conn;
        Cursor cursor;
        Session session;
        int count_min, i, record_count, ret;

        count_min = 0;
        try {
            String command = "/bin/rm -rf " + home1 + " " + home2;
            Process proc = Runtime.getRuntime().exec(command);
            BufferedReader br = new BufferedReader(
                new InputStreamReader(proc.getInputStream()));
            while(br.ready())
                System.out.println(br.readLine());
            br.close();
            proc.waitFor();
            new File(home1).mkdir();
            new File(home2).mkdir();
        } catch (Exception ex) {
            System.err.println("Exception: " + ex);
            return (1);
        }
        if ((wt_conn = wiredtiger.open(home1, CONN_CONFIG)) == null) {
            System.err.println("Error connecting to " + home1);
            return (1);
        }

        session = wt_conn.open_session(null);
        ret = session.create(uri, "key_format=S,value_format=S");
        count_min++;

        cursor = session.open_cursor(uri, null, null);
        /*
         * Perform some operations with individual auto-commit transactions.
         */
        for (record_count = 0, i = 0; i < MAX_KEYS; i++, record_count++) {
            String k = "key" + i;
            String v = "value" + i;
            cursor.putKeyString(k);
            cursor.putValueString(v);
            ret = cursor.insert();
            count_min++;
        }
        ret = session.begin_transaction(null);
        /*
         * Perform some operations within a single transaction.
         */
        for (i = MAX_KEYS; i < MAX_KEYS+5; i++, record_count++) {
            String k = "key" + i;
            String v = "value" + i;
            cursor.putKeyString(k);
            cursor.putValueString(v);
            ret = cursor.insert();
        }
        ret = session.commit_transaction(null);
        count_min++;
        ret = cursor.close();

        /*! [log cursor printf] */
        ret = session.log_printf("Wrote " + record_count + " records");
        count_min++;
        /*! [log cursor printf] */

        session.close(null);
        /*
         * Close and reopen the connection so that the log ends up with
         * a variety of records such as file sync and checkpoint.  We
         * have archiving turned off.
         */
        ret = wt_conn.close(null);
        if ((wt_conn = wiredtiger.open(home1, CONN_CONFIG)) == null) {
            System.err.println("Error connecting to " + home1);
            return (ret);
        }

        session = wt_conn.open_session(null);
        ret = simple_walk_log(session, count_min);
        ret = walk_log(session);
        ret = session.close(null);
        ret = wt_conn.close(null);
        return (ret);
    }

    public static void
    main(String[] args)
    {
        try {
            System.exit(logExample());
        }
        catch (WiredTigerException wte) {
            System.err.println("Exception: " + wte);
            wte.printStackTrace();
            System.exit(1);
        }
    }
}
