/**********************************************************************
 *
 * JdbmAPI.java -- 
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the 
 * University of Waikato, New Zealand.
 *
 * Copyright (C) 2010  The New Zealand Digital Library Project
 * 
 * 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.
 *
 **********************************************************************/

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import java.util.Properties;
import java.util.ArrayList;

import jdbm.RecordManager;
import jdbm.RecordManagerFactory;
import jdbm.helper.FastIterator;
import jdbm.htree.HTree;



public class JdbmAPI
{
    static String TNAME = "greenstone";

    RecordManager  recman_;
    HTree          hashtable_;

    static private PrintWriter utf8out = null;

    static
    {
        try {
            OutputStreamWriter osw = new OutputStreamWriter(System.out, "UTF-8");
            utf8out = new PrintWriter(osw, true);
        }
        catch (UnsupportedEncodingException e) {
            System.out.println(e);
        }
    }

    public JdbmAPI(String db_filename,boolean must_exist)
	throws IOException
    {
	if (db_filename.endsWith(".jdb")) {
	    // remove file extension as JDBM does not expect it
	    db_filename = db_filename.substring(0,db_filename.length()-4);
	}

        // create or open a record manager
        Properties props = new Properties();
        recman_ = RecordManagerFactory.createRecordManager(db_filename, props);

        // load existing table (if exists) otherwise create new one
        long recid = recman_.getNamedObject(TNAME);

	if (recid != 0) {
	    //	    System.err.println("# Loading existing database table '" + TNAME +"' ...");
	    hashtable_ = HTree.load(recman_, recid);
	}
	else {

	    if (must_exist) {
		recman_.close();
		//		System.err.println("Database table '" + TNAME +"' does not exist.");
		throw new IOException();
	    }
	    else {
		//		System.err.println("# No database table '" + TNAME +"' to set.  Creating new one");
		hashtable_ = HTree.createInstance(recman_);
		recman_.setNamedObject(TNAME, hashtable_.getRecid());
	    }
	}
    }

    public JdbmAPI(String db_filename)
	throws IOException
    {
	// default is that database does not have to exist
	this(db_filename,false);
    }
    
    public void append(String key, String val)
	throws IOException
    {    	    
	String orig_val = (String)hashtable_.get(key);           
	String new_val = orig_val + val;

	hashtable_.put(key,new_val);
	recman_.commit();
    }

    public void set(String key, String val)
	throws IOException
	       
    {
	hashtable_.put(key,val);
	recman_.commit();
    }

    public void delete(String key)
	throws IOException
    {
	hashtable_.remove(key);
	recman_.commit();
    }
    

    public String get(String key)
        throws IOException
    {
	String val = (String) hashtable_.get(key);
        
        recman_.commit();
	return val;
    }


    public ArrayList get_keys()
        throws IOException
    {
	ArrayList keys = new ArrayList();

	FastIterator iter = hashtable_.keys();
        String next_key = (String) iter.next();

        while (next_key != null) {
	    keys.add(next_key);
            next_key = (String) iter.next();
        }
        
        recman_.commit();
	return keys;
    }

    public void close()
        throws IOException
    {
	recman_.close();
	//	System.err.println("# Done");
    }


    public static void skip_eol(BufferedReader brin)
        throws IOException
    {
	int c = brin.read();
	if (c == '\r') {
	    // Assume Windows style EOL
	    // consume another character
	    c = brin.read();
	}
	if (c != '\n') {
	    //	    System.err.println("JdbmAPI: Warning, expected end-of-line character, but encountered '" + (char)c + "' ord(" + c + ")");
	}
    }

    public static void print_usage()
    {
	System.err.println("");
	System.err.println("Usage: java JdbmAPI database-name");
	System.err.println("");
	System.err.println("  - The JDBM commands to perform are then taken from standard-in");
	System.err.println("  - Commands (with operands) are:");
	System.err.println("      Keys");
	System.err.println("      Get\\n<key>");
	System.err.println("      Set\\n<key>\\n<vallen>\\n<val>");
	System.err.println("      Append\\n<key>\\n<vallen>\\n<val>");
	System.err.println("      Delete\\n<key>");
	System.err.println("");
	System.err.println("  - Operands are given on separate lines.");
	System.err.println("  - In the case of <val> it is preceeded by its length.");
	System.err.println("");
	System.err.println("  - For example the following would set the key 'HASH0143' to 'testing':");
	System.err.println("      Set");
	System.err.println("      HASH0143");
	System.err.println("      7");
	System.err.println("      testing");
	System.err.println("");
	System.exit(-1);
    }

    public static void main(String[] args)
    {
	int argc = args.length;

	// sanity check
	if (argc != 1) {
	    print_usage();
	}

	try {
	    String dbname = args[0];
	    JdbmAPI jdbm_api = new JdbmAPI(dbname);

	    InputStreamReader isr=new InputStreamReader(System.in,"UTF-8");
	    BufferedReader brin = new BufferedReader(isr);

	    boolean encountered_error = false;

	    String cmd = brin.readLine();
	    while (cmd != null) {
		if (cmd.equals("Keys")) {
		    ArrayList keys = jdbm_api.get_keys();

		    int keys_len = keys.size();
		    utf8out.println(keys_len);

		    for (int i=0; i<keys_len; i++) {
			String key = (String) keys.get(i);
			utf8out.println(key);
		    }
		}
		else if ((cmd.equals("Get")) || (cmd.equals("Delete"))) {
		    String key = brin.readLine();
		    if (key != null) {
			
			if (cmd.equals("Get")) {
			    String val = jdbm_api.get(key);
			    utf8out.println(val.length());
			    utf8out.println(val);
			}
			else {
			    jdbm_api.delete(key);
			}

		    }
		    else {
			//			System.err.println("JdbmAPI: Command '" + cmd + "'ended prematurely");
			encountered_error = true;
			break;
		    }

		}
		else if ((cmd.equals("Set")) || (cmd.equals("Append"))) {
		    String key = brin.readLine();

		    String vallen_str = brin.readLine();
		    if (vallen_str==null) {
			//			System.err.println("JdbmAPI: Command '" + cmd + "'ended prematurely");
			encountered_error = true;
			break;
		    }
		    int vallen = Integer.parseInt(vallen_str);
		    char[] valbuffer = new char[vallen];
		    brin.read(valbuffer,0,vallen);
		    skip_eol(brin);
		    
		    if (valbuffer.length==0) {
			//			System.err.println("JdbmAPI: Command '" + cmd + "'ended prematurely");
			encountered_error = true;
			break;
		    }

		    String val = new String(valbuffer);

		    if (cmd.equals("Set")) {
			jdbm_api.set(key,val);
		    }
		    else {
			jdbm_api.append(key,val);
		    }
		}
		else {
		    //		    System.err.println("JdbmAPI: Unrecognised command '" + cmd + "'");
		    encountered_error = true;
		}

		// read in next command (may be null, signifying eof)
		cmd = brin.readLine();	    
	    }

	    jdbm_api.close();

	    if (encountered_error) {
		System.exit(-1);
	    }
	}

	catch (IOException e) {
	    e.printStackTrace();
	}
	
    }

}


