// Copyright (c) 1997 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 "Record.h"
#include "Animal.h"
#include "Question.h"

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

#include <cstdlib>
#include <iostream>
#include <sstream>
// #include <gdbm.h>
#include <malloc.h>
#include <signal.h>

#include <string>
#include <string.h>


// There are two possible datum formats, both strings; and the records
// in both formats are separated by |.
//
// The first format is for an animal. Its format is simply:
//
// "a|nameofanimal|".
//
// The second format is for a yes-or-no question, which has a pair
// of gdbm keys in it. The format is:
//
// "q|text of question without the question mark|yes key|no key|".
//
// The keys will be simply numeric strings, converted using a strstream.
// Because of this, the program can later be expanded to allow larger
// numbers of animals and questions, without changing the file format
// _at_ _all_.
//
// There will be one key/value pair in the database, called "last" whose
// value is the ascii digits of the key of the last added. If 0, there
// are no animals.

Db *theFileHandle = NULL;
std::string progName;
std::string lineBuf;

void PlayGame(Db &handle)
{
  std::string theAnimal;
  std::string last(GetLast(handle));
  std::string dbtype_singular(GetDbType(handle, false));
  std::string dbtype_plural(GetDbType(handle, true));
  
  if(last == "0")
  {
    std::cout << "I give up! You win! What was your " << dbtype_singular << "? " << std::flush;
    readline(std::cin, theAnimal);
    while (theAnimal.length() < 1)
    {
	std::cout << "What was your " << dbtype_singular << "? " << std::flush;
	readline(std::cin, theAnimal);
    }
    
    Animal it(theAnimal);
    it.Write(handle, "1");
    SetLast(handle, "1");
    
    std::cout << "I now know 1 " << dbtype_plural << "!\07" << std::endl << std::endl;
  }
  else
  {
    std::string browseIndex = "1";
    Record *theRecordPtr;
    int isAnimal = IsAnimal(handle, browseIndex);
    
    if(isAnimal)
      theRecordPtr = new Animal(handle, browseIndex);
    else
      theRecordPtr = new Question(handle, browseIndex);
    
    while(! isAnimal)
    {
      Question theQuestion = *((Question *) theRecordPtr);
      delete theRecordPtr;
      
      std::cout << theQuestion << "? ";
      
      readline(std::cin, lineBuf);
      
      if(lineBuf[0] == 'y')
      {
	browseIndex = theQuestion.GetYesKey();
      }
      else if(lineBuf[0] == 'n')
      {
	browseIndex = theQuestion.GetNoKey();
      }

      isAnimal = IsAnimal(handle, browseIndex);
      
      if(isAnimal)
	theRecordPtr = new Animal(handle, browseIndex);
      else
	theRecordPtr = new Question(handle, browseIndex);
    }
    
    std::cout << std::endl << "I think I'll try a guess now...\07" << std::endl;
    std::cout << "Is your " << dbtype_singular << " a " << (*theRecordPtr) << "? " << std::flush;
    readline(std::cin, lineBuf);
    if(lineBuf[0] == 'y')
    {
      std::cout << "I win! I guessed your " << dbtype_singular << "!" << std::endl << std::endl;
    }
    else
    {
      std::cout << std::endl;
      std::cout << "I give up! You win! What was your " << dbtype_singular << "? " << std::flush;
      readline(std::cin, theAnimal);

      while (theAnimal.length() < 1)
      {
	std::cout << "What was your " << dbtype_singular << "? " << std::flush;
	readline(std::cin, theAnimal);
      }
      
      std::string questionBuf;
      int goodQuestion = 0;
      long guessedAnimalKey;
      long newAnimalKey;
      int guessedAnimalAnswer;
      int newAnimalAnswer;
      Animal newAnimal;
      Animal guessedAnimal = *((Animal *) theRecordPtr);
      
      while(! goodQuestion)
      {
	newAnimal = Animal(theAnimal);
	
	std::cout << std::endl;
	std::cout << "I need a yes-or-no question so I can later" << std::endl;
	std::cout << "tell the difference between a " << guessedAnimal;
	std::cout << " and a " << newAnimal << "." << std::endl;
	
	readline(std::cin, questionBuf);
	
	ChopQuestionMark(questionBuf);
	
	guessedAnimalKey = CStringToLong( GetLast(handle).c_str() ) + 1;
	newAnimalKey = guessedAnimalKey + 1;
	
	newAnimalAnswer = -1;
	
	while(newAnimalAnswer == -1)
	{
	  std::cout << "what would be the answer be for a ";
	  std::cout << newAnimal << "? " << std::flush;
	  
	  readline(std::cin, lineBuf);
	  
	  newAnimalAnswer = ((lineBuf[0] == 'y') ? 1 :
			     (lineBuf[0] == 'n') ? 0 : -1);
	}
	
	guessedAnimalAnswer = -1;
	
	while(guessedAnimalAnswer == -1)
	{
	  std::cout << "what would be the answer be for a ";
	  std::cout << guessedAnimal << "? " << std::flush;
	  
	  readline(std::cin, lineBuf);
	  
	  guessedAnimalAnswer = ((lineBuf[0] == 'y') ? 1 :
				 (lineBuf[0] == 'n') ? 0 : -1);
	}
	
	if(newAnimalAnswer != guessedAnimalAnswer)
	  goodQuestion = 1;
	else
	{
	  std::cout << std::endl;
	  std::cout << "I don't understand! If both answers are the";
	  std::cout << std::endl;
	  std::cout << "same, maybe I can't use the question! Maybe";
	  std::cout << std::endl;
	  std::cout << "I misunderstood? Please tell me again!";
	  std::cout << std::endl;
	}
      }
      
      const char *newKeyString = LongToNewCString(newAnimalKey);
      const char *guessedKeyString = LongToNewCString(guessedAnimalKey);
      Question newQuestion
	(
	  // watch for memory leaks in the next line
	  questionBuf.c_str(),
	  (newAnimalAnswer ? newKeyString : guessedKeyString),
	  (newAnimalAnswer ? guessedKeyString : newKeyString)
	);
      
      newQuestion.Write(handle, browseIndex.c_str());
      guessedAnimal.Write(handle, guessedKeyString);
      newAnimal.Write(handle, newKeyString);
      
      SetLast(handle, newKeyString);
      
      std::cout << std::endl;
      std::cout << "I now know " << (newAnimalKey + 1) / 2 << " " << dbtype_plural << "!";
      std::cout << std::endl;
      std::cout << std::endl;
    }
  }
}

void SigHandler(int sig)
{
  if (theFileHandle) {
    CloseFile(*theFileHandle);
  }
  std::cout << "Killed..." << std::endl;
  exit(0);
}

int main(int argc, char * argv[])
{
  std::string wantsToContinue("y");
  std::string dbtype_singular;
  const char * dbfile = DbName;
  
  for (int i = 1; i < argc; i++) {
   if ( !strcmp(argv[i], "-h") || !strcmp(argv[i], "--help") ) {
    printf("Usage: %s [DatabaseFile [TypeString [PluralTypeString]]]\n", argv[0]);
    printf("   Or: %s {-h|--help}\n", argv[0]);
    return 0;
   }
  }

  progName = argv[0];

  if ( argc >= 2 )
  {
    dbfile = argv[1];
  }

  signal(SIGHUP, SigHandler);
  signal(SIGTERM, SigHandler);
  signal(SIGINT, SigHandler);
 
  std::cout << std::endl << "Welcome to animals!" << std::endl << std::endl;
  
  do
  {
    theFileHandle = OpenFile(dbfile);

    if ( argc >= 3 )
    {
	SetDbType(*theFileHandle, argv[2], false);
	if ( argc >= 4 )
	{
	    SetDbType(*theFileHandle, argv[3], true);
	}
    }

    dbtype_singular = GetDbType(*theFileHandle, false);

    if ( theFileHandle == NULL )
    {
	return 1;
    }

    
    std::cout << "Think of an " << dbtype_singular << "." << std::endl;
    std::cout << "Press <Enter> or <Return> to continue..." << std::flush;
    readline(std::cin, lineBuf);
    std::cout << std::endl;

    PlayGame(*theFileHandle);
    CloseFile(*theFileHandle);
    
    std::cout << "Want to play again? " << std::flush;
    readline(std::cin, lineBuf);
    std::cout << std::endl;
    
    wantsToContinue = lineBuf[0];
  }
  while(wantsToContinue == "y" || wantsToContinue == "s");

  return 0;
}

