/**********************************************************************
 *
 * tdbcli.cpp --
 *
 * Similar to txt2db (GDBM) executable in that you open a pipe to it and
 * write commands to be applied to a GDBM database. However, unlike txt2db,
 * this executable allows bidirectional streams (so you need to open the
 * pipe for both reading and writing). It then supports commands of this
 * form:
 *
 * \[<key>\][+\-\?]
 * (<value>)?
 * -{70}
 *
 * where: + is for add or update
 *        - is for delete
 *        ? is for lookup
 *
 * The aim of this executable is to allow a single, persistent, connection
 * to a TDB database, accessed through some kind of multithreaded daemon, so
 * as to support multiple readers and writers and thus parallel collection
 * building.
 *
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Copyright (C) 2012 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.
 *
 **********************************************************************/

#if defined(GSDL_USE_OBJECTSPACE)
#  include <ospace\std\iostream>
#elif defined(GSDL_USE_IOS_H)
#  include <iostream.h>
#else
#  include <iostream>
#endif

#include <stdlib.h>
#include <cstring>

#include "tdb.h"
#include "text_t.h"

// use the standard namespace
#if !defined (GSDL_NAMESPACE_BROKEN)
#if defined(GSDL_USE_OBJECTSPACE)
using namespace ospace::std;
#else
using namespace std;
#endif
#endif

void
printUsage ()
{
  cerr << "===== TDB Command Line Interface v1.0 =====" << endl << endl;
  cerr << "usage: tdbcli <db path> [-empty] [-debug]" << endl << endl;
  cerr << "where: -empty  Clear out any contents of database first (opposite" << endl;
  cerr << "               of the legacy command -append)" << endl;
  cerr << "       -debug  Print debug messages" << endl << endl;
  cerr << "Once the program is running, the first thing it expects is the" << endl;
  cerr << "path to the TDB database to open, after which you can use the" << endl;
  cerr << "commands below:" << endl;
  cerr << "  [key]+<newline>value <= adds the pair key:value" << endl;
  cerr << "  [key]<newline>value  <= also adds the pair" << endl;
  cerr << "  [key]-               <= deleted the pair identified by key" << endl;
  cerr << "  [key]?               <= lookup the value for key" << endl;
  cerr << "  [*]                  <= retrieve a list of keys" << endl;
  cerr << "  []                   <= exit " << endl << endl;
}

int
main (int argc, char *argv[])
{
  if (argc < 2)
  {
    printUsage();
    exit(0);
  }

  char *dbname = argv[1];
  bool empty = false;
  bool debug = false;
  for (int i = 2; i < argc; i++)
  {
    if (strcmp(argv[i], "-empty") == 0)
    {
      empty = true;
    }
    if (strcmp(argv[i], "-debug") == 0)
    {
      debug = true;
    }
  }

  // open the database
  int hash_size = 0;
  int tdb_flags = TDB_DEFAULT; // Default = 0
  if (empty)
  {
    tdb_flags = TDB_CLEAR_IF_FIRST;
  }
  // Disable file IO for testing purposes
  /*tdb_flags = tdb_flags | TDB_INTERNAL;*/
  int open_flags = O_RDWR | O_CREAT;
  int tdb_store_flags = TDB_DEFAULT; // used later when storing
  TDB_CONTEXT *dbf = tdb_open(dbname, hash_size, tdb_flags, open_flags, 0664);
  if (!dbf)
  {
    cerr << "tdbcli::main() - couldn't create " << dbname << endl;
    exit (0);
  }

  // Start reading commands from STDIN
  bool quit = false;
  char c;
  cin.get(c);
  while (!cin.eof() && !quit)
  {
    text_t key = "";
    bool action_delete = false;
    bool action_lookup = false;
    bool action_keys = false;
    text_t value = "";
    int num_dashes = 0;

    // Parse out 'key' from [key]\n
    // scan for first occurrence of [
    while (!cin.eof() && c != '[')
    {
      cin.get(c);
    }
    if (!cin.eof())
    {
      cin.get(c); // skip [
    }

    // now look for closing ], building up 'key' as we go
    while (!cin.eof() && c != ']')
    {
      key.push_back ((unsigned char)c);
      cin.get(c);
    }

    // Empty key means exit
    if (key.empty())
    {
      quit = true;
    }
    else
    {
      // * as a key means return a list of keys
      if (key == "*")
      {
        action_keys = true;
      }

      if (key == "?")
      {
        cout << "0.1" << endl;
        tdb_close (dbf);
        return 0;
      }

      // retrieve the command token
      if (!cin.eof())
      {
        cin.get(c);
        if (c == '+')
        {
          // defaults to action_add anyway
          cin.get(c);
        }
        else if (c == '?')
        {
          action_lookup = true;
          cin.get(c);
        }
        else if (c == '-')
        {
          action_delete = true;
          cin.get(c);
        }
      }

      // Returning the list of all keys (one per line) is of highest priority
      if (action_keys)
      {
        TDB_DATA key_data = tdb_firstkey (dbf);
        bool first_key = true;
        while (key_data.dptr != NULL)
        {
          if (first_key)
          {
            first_key = false;
          }
          else
          {
            cout << endl;
          }
          for (int i = 0; i < key_data.dsize; ++i)
          {
            cout << key_data.dptr[i];
          }
          /* get next key */
          TDB_DATA nextkey_data = tdb_nextkey (dbf, key_data);
          /* free old key's dptr, otherwise causes memory leak */
          free(key_data.dptr);
          /* can now safely copy content of nextkey into key */
          key_data = nextkey_data;
        }
      }
      // if we've been asked for a lookup, retrieve the information for this
      // key, write to STDOUT, and then continue processing STDIN for further
      // commands
      else if (action_lookup)
      {
        // convert key to a TDB_DATA datatype
        TDB_DATA key_data;
        key_data.dptr = (unsigned char*)key.getcstr();
        if (key_data.dptr == NULL)
        {
          cerr << "NULL key_data.dptr" << endl;
          exit (0);
        }
        key_data.dsize = key.size();

        TDB_DATA value_data;
        value_data = tdb_fetch (dbf, key_data);
        for (int i = 0; i < value_data.dsize; ++i)
        {
          cout << value_data.dptr[i];
        }
        free(value_data.dptr);
        free(key_data.dptr);
      }
      // if we've been asked to delete a key/value pair, do so and then
      // continue processing STDIN for further commands
      else if (action_delete)
      {
        // convert key to a TDB_DATA datatype
        TDB_DATA key_data;
        key_data.dptr = (unsigned char*)key.getcstr();
        if (key_data.dptr == NULL)
        {
          cerr << "NULL key_data.dptr" << endl;
          exit (0);
        }
        key_data.dsize = key.size();
	// delete the given key
	if (tdb_delete(dbf, key_data) < 0)
        {
	  cerr << "tdb_delete returned an error trying to delete key " << key << endl;
	}
        free(key_data.dptr);
      }
      // Everything else is an add/update. Read in value, action and then
      // continue processing STDIN for further commands
      else
      {
        // convert key to a TDB_DATA datatype
        TDB_DATA key_data;
        key_data.dptr = (unsigned char*)key.getcstr();
        if (key_data.dptr == NULL)
        {
          cerr << "NULL key_data.dptr" << endl;
          exit (0);
        }
        key_data.dsize = key.size();

        // eat up whitespace
        while (!cin.eof() && (c == '\t' || c == ' ' || c == '\n' || c == '\r'))
        {
          cin.get(c);
        }
        // parse in value (if any), watching for the 70 dashes that mark the end
        text_t tmp = "";
        while (!cin.eof() && (num_dashes < 70))
        {
          // - reset number of dashes on newline
          if (c == '\n')
          {
            tmp.push_back ((unsigned char)c);
            num_dashes = 0;
          }
          // Here we are able to process both Windows-specific text files
          // (containing carriage-return, newline) and Linux text files
          // (containing only newline characters) by ignoring the Windows'
          // carriage-return altogether so that we produce a uniform database
          // file from either system's type of text file.
          // If we don't ignore the carriage return here, txt.gz files
          // produced on Windows cause a GS library running on Linux to break.
          // - reset number of dashes on carriage return
          else if (c == '\r')
          {
            num_dashes = 0;
          }
          else if (c == '-')
          {
            tmp.push_back ((unsigned char)c);
            ++num_dashes;
          }
          else
          {
            value += tmp;
            value.push_back ((unsigned char)c);
            tmp = "";
            num_dashes = 0;
          }
          cin.get(c);
        }

	// convert value to a TDB_DATA datatype
	TDB_DATA value_data;
	value_data.dptr = (unsigned char*)value.getcstr();
	if (value_data.dptr == NULL)
        {
	  cerr << "NULL value_data.dptr" << endl;
	  exit (0);
	}
	value_data.dsize = value.size();
	// store the value
	if (tdb_store (dbf, key_data, value_data, tdb_store_flags) < 0)
        {
	  cerr << "tdb_store returned an error" << endl;
	  exit (0);
	}
        // done with value
	free(value_data.dptr);
        free(key_data.dptr);
      }
      // - always output 70 hyphens when finished
      cout << endl << "----------------------------------------------------------------------" << endl;
      // done with key too
    }
  }
  tdb_close (dbf);
  cout << "Database updated. Goodbye." << endl;
  return 0;
}
