// Copyright (c) 1997,2002 by Jim Lynch.
// Copyright (c) 2010-2012 by Philipp Schafft.
// This software comes with NO WARRANTY WHATSOEVER.
//
// 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; version 2 dated June, 1991, 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 software; see the file COPYING.  If not, write to
//    the Free Software Foundation, 51 Franklin Street, Fifth Floor,
//    Boston, MA 02110-1301, USA.

#include "db4++-stuff.h"
#include "main.h"

#include <iostream>
#include <sstream>
#include <errno.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <malloc.h>

#include "str-conversions.h"

long lastKeyAssigned;

int Write(Db &theFile, const std::string &theKey, const std::string &theValue)
{
  int putBad;
  Dbt key;
  Dbt value;
  
  key.set_data((void*) theKey.c_str());
  key.set_size(strlen(theKey.c_str()) + 1);
  
  value.set_data((void*) theValue.c_str());
  value.set_size(strlen(theValue.c_str()) + 1);
  
  try
    {
      putBad = theFile.put(NULL, &key, &value, 0);
    }
  catch(DbException &e)
    {
      std::cerr << "db2++ Write(Db filehandle, ";
      std::cerr << '"' << theKey << '"';
      std::cerr << ", ";
      std::cerr << '"' << theValue << '"';
      std::cerr << "): " << e.what() << std::endl;
      putBad = 1; // or throw game exception here?
    }

  return ! putBad;
}

int Read(Db &theFile, const std::string &theKey, std::string &theValue)
{
  int getBad;
  Dbt key;
  Dbt value;
  
  key.set_data((void*) theKey.c_str());
  key.set_size(strlen(theKey.c_str()) + 1);
  
  try
    {
      getBad = theFile.get(NULL, &key, &value, 0);
    }
  catch(DbException &e)
    {
      std::cerr << "db3++ Read(Db filehandle, ";
      std::cerr << '"' << theKey << '"';
      std::cerr << ", ";
      std::cerr << "(some value placeholder)";
      std::cerr << "): " << e.what() << std::endl;
      getBad = 1; // or throw game exception here?
    }

  if(value.get_data())
    {
      theValue = (char*) value.get_data();
    }
  else
    {
      theValue = "";
    }

  return ! getBad;
}



Db *OpenFile(const char * dbfile)
{
  Db *theFile;
  struct stat dummy;
  
  if(stat(dbfile, &dummy) == -1) // stat failed?
    {
      if(errno == ENOENT) // file not found?
	{
	  int createBad;
	  
	  // create the file here, fully; ready for animals.
	  
	  mode_t saveMode = umask(0);
	  
	  try
	    {
	      theFile = new Db(NULL, 0);
	      createBad = theFile->open
                           (
		             dbfile,
			     NULL, /* name of database within file (unused) */
			     DB_BTREE /* use B+Tree structure */,
			     DB_CREATE,
			     0660
		           );
	    }
	  catch(DbException &e)
	    {
	      std::cerr << "db2++ Open(): while trying to create";
	      std::cerr << ": " << e.what() << std::endl;
	    }
	  
	  umask(saveMode);
	  
	  if(createBad) // can't create?
	    {
	      // errno may be set now.
	      // Deal with it...
	      
	      std::cout << progName << ": OpenFile: file " << dbfile
		        << ": errno=" << errno << std::endl;
	      
	      exit(-1);
	    }
	  else
	    {
	      // new gdbm file. Add initial infrastructure.

	      int putBad;

	      putBad = ! Write(*theFile, "last", "0");

	      //Dbt key;
	      //Dbt lastAddedKey;
	      //
	      //key.set_data((void*) "last");
	      //key.set_size(5);
	      //
	      //lastAddedKey.set_data((void*) "0");
	      //lastAddedKey.set_size(2);
	      //
	      //try
	      //{
	      //  putBad = theFile->put(NULL, &key, &lastAddedKey, 0);
	      //}
	      //catch(DbException &e)
	      //{
	      //  cerr << e.what() << endl;
	      //  putBad = 1; // or throw game exception here?
	      //}
	    }
	} // endif(errno == ENOENT)
      else
	{
	}
    }
  else
    {
      int chmod_res, openBad;
      
      try
	{
	  theFile = new Db(NULL, 0);
	  openBad = theFile->open
	              (
		        dbfile,
			NULL, /* name of database within file (unused) */
			DB_BTREE /* use B+Tree structure */,
			0,
			0660
		      );
	}
      catch(DbException &e)
	{
	  std::cerr << e.what() << std::endl;
	  openBad = 1; // or throw game exception here?
	}
      
      if(openBad) // can't open existing db file?
	{
	  // either or both of gdbm_errno and errno may be set now.
	  // Deal with it...
	  
	  std::cout << progName << ": OpenFile: file " << dbfile;
	  std::cout << ": errno=" << errno << std::endl;
	  
	  exit(-1);
	}
      else
	{
	  // file is open for update; get "last" into lastKeyAssigned
	  
	  std::string lastString;
	  int getBad;
	  
	  getBad = ! Read(*theFile, "last", lastString);

	  //Dbt key((void*) "last", 5);
	  //Dbt value;
	  //
	  //try
	  //  {
	  //    getBad = theFile->get(NULL, &key, &value, 0);
	  //  }
	  //catch(DbException &e)
	  //  {
	  //    cerr << e.what() << endl;
	  //    getBad = 1; // or throw a game exception?
	  //  }
	  //
	  
	  if(lastString == "") // should not be null; 
	                       // if it is, "last" is missing
	    {
	      std::cout << "'last' key value missing: database seems corrupt";
	      std::cout << std::endl;
	      
	      theFile->close(0);
	      exit(-1);
	    }
	  
	  std::istringstream *theLastStream 
	    = new std::istringstream(lastString.c_str());
	  
	  (*theLastStream) >> lastKeyAssigned;
	  
	  delete theLastStream;
	}
    }
  
  return theFile;
}

void CloseFile(Db &handle)
{
  try
    {
      handle.close(0);
    }
  catch(DbException &e)
    {
      std::cerr << e.what() << std::endl;
      // throw game exception here?
    }
}

void SetLast(Db &handle, const std::string &newKey)
{
  int wrOK = Write(handle, "last", newKey);
  
  // Dbt key((void*) "last", 5);
  // char *p;
  // 
  // p = MakeCStringCopy(newKey);
  // 
  // Dbt content((void*) p, strlen(p) + 1);
  // 
  // try
  //   {
  //     handle.del(NULL, &key, 0);
  //     handle.put(NULL, &key, &content, 0);
  //   }
  // catch(DbException &e)
  //   {
  //     std::cerr << e.what() << endl;
  //     // maybe propigate the exception to a game exception
  //   }
  // 
  // delete[] p;
}

std::string GetLast(Db &handle)
{
  std::string result;
  
  int rdOK = Read(handle, "last", result);
  
  // Dbt content;
  // Dbt key((void*) "last", 5);
  // int getOK = 1;
  // 
  // try
  //   {
  //     handle.get(NULL, &key, &content, 0);
  //   }
  // catch(DbException &e)
  //   {
  //     cerr << e.what() << endl;
  //     getOK = 0;
  //   }
  //
  // if(getOK)
  //   {
  //     if(content.get_data())
  //       {
  //         result = (char*) content.get_data();
  //       }
  //   }
  // else
  //   {
  //     // throw game exception?
  //   }
  
  return result;
}

int IsAnimal(Db &handle, const std::string &theKey)
{
  int result = 0;
  std::string theValue;
  int getOK = Read(handle, theKey, theValue);
  
  char firstChar = theValue[0];
  result = (firstChar == 'a');
  
  // char *p = MakeCStringCopy(theKey);
  // Dbt key((void*) p, strlen(p) + 1);
  // Dbt content;
  // 
  // try
  //   {
  //     handle.get(NULL, &key, &content, 0);
  //   }
  // catch(DbException &e)
  //   {
  //     cerr << e.what() << endl;
  //     getOK = 0;
  //   }
  // 
  // if(p)
  //   delete[] p;
  // 
  // if(getOK)
  //   {
  //     p = (char *) content.get_data();
  //
  //     if(p)
  //       if(*p == 'a')
  //         result = 1;
  //   }
  // else
  //   {
  //     // perhaps throw an exception here relevent to the game...
  //   }
  
  return result;
}

