/*
 * class:  TDBJavaTest -- Test suite for this package
 * Copyright (C) 2015 Greenstone Digital Library, University of Waikato
 * $Id$
 *
 *  Based on: javagdbm/GdbmTest.java
 *  Original author: Martin Pool, 1997 Pharos IP Pty Ltd
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.greenstone.tdbjava;

import java.io.File;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Random;

import org.greenstone.tdbjava.TDBJava;
import org.greenstone.tdbjava.TDBJavaException;
import org.greenstone.tdbjava.Test;

/** Test harness for the TDBJava/JNI layer.
 *
 * <P>The source of this class forms a useful demonstration of the
 * TDBJava library and is commended to the intending programmer.
 *
 */
public class TDBJavaTest
{

    static final int MANY_RECORDS = 200;

    /**
     *  Don't instantiate
     */
    private TDBJavaTest() {;}
    /** TDBJavaTest() **/


    /** @function testLongData(TDBJava)
     */
    private static void testLongData(TDBJava db)
	 throws Exception
    {
	// generate some long random content
	StringBuffer buffer = new StringBuffer();
	Random r = new Random();
	for (int i = 0; i < 3600; i++) {
	    buffer.append((char)(r.nextInt(26) + 'a'));
	}
	String value = buffer.toString();
	buffer = null;
	String key = "LargeDatum";

	db.store(key, value);
	String retrieved = db.fetch(key);

	Test.ok(160, retrieved.hashCode() == value.hashCode());
	Test.ok(161, retrieved.equals(value));

	db.delete(key);
	Test.ok(162, true);
    }
    /** testLongData(TDBJava) **/


    /** @function testBigFile(TDBJava)
     */
    private static void testBigFile(TDBJava db)
	throws Exception
    {
	String key;

	// Test a lot of data
	byte[] data = new byte[10000];
	for (int i = 0; i < data.length; i++) {
	    data[i] = (byte)((i & 63) + 32);
	}
	Test.ok(150, true);

	// Store a series of records in the file
	for (int i = 0; i < MANY_RECORDS; i++) {
	    if ((i % 100) == 0) {
		System.out.print(Integer.toString(i)+ " ");
	    }
	    db.store(Integer.toString(i, 16), data.toString());
	}
	System.out.println();
	Test.ok(151, true);

	// Enumerate keys and values out of the file
	int count = 0;
	Enumeration keys = db.keys();
	while ( keys.hasMoreElements() ) {
	    key = (String) keys.nextElement();
	    count++;
	    if ((count % 100) == 0) {
		System.out.print(Integer.toString(count)+ " ");
	    }
	}
	System.out.println();
	Test.ok(152, count==MANY_RECORDS);

	// Delete all the records from the file
	count = 0;
	while ( (key = (String) db.getFirstKey()) != null ) {
	    db.delete(key);
	    count++;
	    if ((count % 100) == 0) {
		System.out.print(Integer.toString(count)+ " ");
	    }
	}
	System.out.println();
	Test.ok(153, count==MANY_RECORDS);
    }
    /** testBigFile(TDBJava) **/


    public static void ensureNoDatabase(String file_path)
    {
	try {
	    File delete_me = new File(file_path);
	    if (delete_me.exists()) {
		delete_me.delete();
	    }
	}
	catch (Exception e) {
	    System.err.println("Failed to remove existing database: " + file_path);
	    System.exit(0);
	}
    }
    /** ensureNoDatabase(String) **/


    /** @function main()
     */
    public static void main(String[] argv)
    {
	final String db_path = "/tmp/java.tdb";
	String key, value;
	boolean found;

	// Test cases, inspired by db-hash.t in the Perl5.003_93
	// distribution
	System.out.println("===== TDBJava Database tests =====");
	System.out.println(" - Library version: " + TDBJava.getLibraryVersion());
	System.out.println(" - Driver version:  " + TDBJava.getWrapperVersion());
	ensureNoDatabase(db_path);

	try {
	    // Create a simple database
	    TDBJava db = TDBJava.getConnection(db_path);
	    Test.ok(1, db != null);

	    // Check database is open
	    Test.ok(1.5, db.isOpen());

	    // Store some data in the file
	    db.store("mountain", "Kosciusko");
	    Test.ok(2, true);

	    // Store and read back data
	    db.store("abc", "ABC");
	    value = db.fetch("abc");
	    Test.ok(3, value.equals("ABC"));

	    // Check whether keys exist or not
	    Test.ok(4, db.exists("abc"));
	    Test.ok(5, !db.exists("jimmy"));
	    Test.ok(6, !db.exists("abc\0"));

	    // Open a second handle to the same database
	    TDBJava db4 = TDBJava.getConnection(db_path);
	    Test.ok(701, db4 != null);
	    // read an existing key
	    value = db4.fetch("abc");
	    Test.ok(702, value.equals("ABC"));
	    // write a new key
	    db4.store("breakfast", "Bacon");
	    Test.ok(703, true);
	    // read it out
	    value = db4.fetch("breakfast");
	    Test.ok(704, value.equals("Bacon"));
	    // delete a record
	    db4.delete("mountain");
	    Test.ok(705, !db4.exists("mountain"));
	    // close it
	    db4.close();
	    // test added record still exists
	    value = db.fetch("breakfast");
	    Test.ok(706, value.equals("Bacon"));

	    // Delete those records
	    db.delete("breakfast");
	    Test.ok(9, !db.exists("breakfast"));
	    db.delete("abc");
	    Test.ok(10, !db.exists("abc"));

	    // Store a series of records in the file
	    for (int i = 100; i < 200; i++) {
		db.store(Integer.toString(i, Character.MAX_RADIX),
			 Integer.toString(i, Character.MIN_RADIX));
	    }

	    // Enumerate keys and values out of the file
	    int count = 0;
	    Enumeration keys = db.keys();
	    while (keys.hasMoreElements()) {
		key = (String) keys.nextElement();
		// System.out.print(key + "=");
		// value = (String) db.fetch(key);
		// System.out.println(value);
		count++;
	    }
	    Test.ok(20, count==100);

	    // Delete all the records from the file
	    count = 0;
	    while ((key = (String) db.getFirstKey()) != null) {
		db.delete(key);
		count++;
	    }
	    Test.ok(30, count==100);

	    // Try to fetch a non-existent key
	    boolean caught = false;
	    try {
		db.fetch("larry!");
	    }
	    catch (Exception e) {
		// TODO: Check the exception was the one we expected?
		///ystem.out.println("caught: " + e.toString());
		caught = true;
	    }
	    Test.ok(40, caught);

	// Store some very large data
	testLongData(db);

	// Store strings containing non-ASCII characters
	final String euro_string = "Some funny characters: «¡ æ ø â ß !»";
	db.store("high ASCII", euro_string);
	Test.ok(50, db.fetch("high ASCII").equals(euro_string));

	// Store for later
	db.store("mountain", "Pirongia");

	// Close the database
	db.close();
	Test.ok(60, true);

	// Try to read from a database that is closed
	caught = false;
	try {
	    db.fetch("larry!");
	}
	catch (Exception e) {
	    ///ystem.out.println("caught: " + e.toString());
	    caught = true;
	}
	Test.ok(70, caught);

	/* No longer applicable
	// Try to open a non-existent database
	String bogus_db_path = "/tmp/somenonexistentdatabase.tdb";
	ensureNoDatabase(bogus_db_path);
	caught = false;
	try {
	    db = TDBJava.getConnection(bogus_db_path, TDBJava.TDB_DEFAULT, TDBJava.O_READONLY);
	}
	catch (Exception e) {
	    ///ystem.out.println("caught: " + e.toString());
	    caught = true;
	}
	Test.ok(80, caught);

	// Open a database read-only
	db = TDBJava.getConnection(db_path, TDBJava.TDB_DEFAULT, TDBJava.O_READONLY);
	Test.ok(90);

	// Try to insert
	caught = false;
	try {
	    db.store("apple", "crumble");
	}
	catch (Exception e) {
	    System.out.println("caught: " + e.toString());
	    caught = true;
	}
	Test.ok(91, caught);

	db.close();
	*/

	// Replace an existing database
	db = TDBJava.newConnection(db_path);
	Test.ok(100, !db.exists("mountain"));

	// Juggle three databases at once
	final String db_path2 = "/tmp/java2.tdb";
	ensureNoDatabase(db_path2);
	TDBJava db2 = TDBJava.getConnection(db_path2);
	final String db_path3 = "/tmp/java3.tdb";
	ensureNoDatabase(db_path3);
	TDBJava db3 = TDBJava.getConnection(db_path3);
	Test.ok(110, true);

	keys = db.keys();
	while (keys.hasMoreElements()) {
	    key = (String) keys.nextElement();
	    value = db.fetch(key);
	    db2.store(key, value);
	    db3.store(key, value);
	}
	Test.ok(120, true);

	keys = db2.keys();
	found = true;
	while (keys.hasMoreElements()) {
	    found &= db3.exists((String)keys.nextElement());
	}
	Test.ok(130, found);

	db2.close();
	db3.close();

	Test.ok(140, true);

	testBigFile(db);

	db.close();
	Test.ok(160, true);
	}
	catch (Exception e) {
	    e.printStackTrace();
	}
	Test.summary();
    }
}
