/*

 BabbleMud - A MUD server

 Author:  Nick Gammon 
          http://www.gammon.com.au/ 

(C) Copyright Nick Gammon 2004. Permission to copy, use, modify, sell and
distribute this software is granted provided this copyright notice appears
in all copies. This software is provided "as is" without express or implied
warranty, and with no claim as to its suitability for any purpose.
 
*/

#include <stdlib.h>
#include <time.h>

#include "commands.h"

const int getrandom (const int x)
{
	long n;

	if (x <= 0)
		return 0;
		n = rand ();
	return (n % x);
}   // end of getrandom

const int roll (const int howmany, const int die)
{
	int count;
	int total = 0;

	for (count = 0; count < howmany; ++count)
		total += getrandom (die) + 1;

	return total;

} // end of roll


// returns true of a x% probability exists
// eg. percent (80) will be true 80% of the time
const bool percent (const int prob)
  {
  if (prob <= 0)
    return false;
  if (prob >= 100)
    return true;

  return getrandom (100) > (100 - prob);

  }

static int initRandom ()
  {
  srand   (time (NULL));
#ifndef WIN32
  srand48 (time (NULL));
#endif
  return 0;
  }

// initialise random number generator
static int someNumber = initRandom ();

extern ofstream logFileGeneral;
extern ofstream logFileFailedCommands;

void generalLogger (ofstream & f,   // what log file
                    const string & suppliedFileName,  // log file name
                    string & actualFileName,          // log file with date in it
                    const string & what,      // what to log
                    const thing * user) // player or web user, if any
  {
string logFileName = FindAndReplace (suppliedFileName, "~", stringFileDate (time (NULL)));
  
  // if log file not open, open it
  // if already open, and date has changed, close and re-open it
  if (!f.is_open () || actualFileName != logFileName)
    {
    if (f.is_open ())
      f.close (); // close if already open so we can re-open with different name
    
    cout << "Opening log file " << logFileName << endl;
    
    f.open (logFileName.c_str (), ios::out | ios::app);
    if (!f.is_open ())
      throw runtime_error ("Could not open log file: " + logFileName + " for writing.");
    
    actualFileName = logFileName;    // remember name
    }
  
  string who = "Game";
  if (user)
    who = user->gets ("name");
  
  f << stringShortDate (time (NULL)) << " :: " << who << ": " << what << endl;
  
  if (f.fail ())
      throw runtime_error ("Could not write to log file: " + logFileName);
    
  } // end of generalLogger

/*
I am making routines here to statically store the current file name,
so we can detect when to roll over log files to a new day, and a failure
flag, which will remain set if an exception is thrown, so we don't keep
logging to a failed file (eg. disk full)
*/

#define MAKE_LOG_ROUTINE(func_name, log_file, log_file_name)  \
void func_name (const string & what, const thing * player) \
  {\
static bool failed = false;\
static string currentFileName;\
  if (failed) \
    return; \
  failed = true;  \
  generalLogger (log_file, log_file_name, currentFileName, what, player); \
  failed = false; \
  }

// log something
MAKE_LOG_ROUTINE (gamelog, logFileGeneral, LOG_FILE)
 
// log failed commands
MAKE_LOG_ROUTINE (failedlog, logFileFailedCommands, LOG_FILE_FAILED)

// log failed commands
MAKE_LOG_ROUTINE (mobfailedlog, logFileFailedMobCommands, LOG_FILE_MOB_FAILED)

// log web server
MAKE_LOG_ROUTINE (webserverlog, logFileWebServer, LOG_FILE_WEB_SERVER)

// convert error code like "comms_error_bind" into its actual message
// you supply a prefix, which is put in front of the code (eg. fr_ )
const string convertErrorCode (const string & code, const string & prefix)
  {
  // lookup message in map
  StringMap::const_iterator iter = errormap ().find (prefix + code);
  
  string msg;
  
  // if found, take it, otherwise use default of code
  if (iter != errormap ().end ())
      msg = iter->second;
  else
      msg = messagemap () [prefix + "no_error_message"] + " [" + prefix + code + "]";
  // return results
  return msg;
  
  } // end of convertErrorCode

const string convertMessageCode (const string & code, const string & prefix)
  {
  // lookup message in map
  StringMap::const_iterator iter = messagemap ().find (prefix + code);
  
  string msg;
  
  // if found, take it, otherwise use default of code
  if (iter != messagemap ().end ())
      msg = iter->second;
  else
      msg = messagemap () [prefix + "no_message_message"] + " [" + prefix + code + "]";
  
  return msg;
  
  } // end of convertMessageCode

// random-hash maker for where we need a nice unguessable token
// the idea here is to defat an attack which might be based on guessing
// what the token is.
const string makeRandomHash ()
  {
  ostringstream os;
  
  // get a whole lot of stuff they shouldn't be able to guess
  
  os << serverInfo ().gets ("started_at")
     << serverInfo ().gets ("last_player_started_date")
     << serverInfo ().gets ("last_player_quit_date")
     << serverInfo ().geti ("web_server_hits")
     << serverInfo ().geti ("last_incoming_port")
     << serverInfo ().gets ("last_incoming_address")
     << serverInfo ().geti ("web_last_incoming_port")
     << serverInfo ().gets ("web_last_incoming_address")
     << getpid ()       // process ID
     << time (NULL)     // time now
     << rand ()         // a few pseudo-random numbers
     << rand ()
     << rand ()         
     << HashThing (serverInfo ())  // all server info, probabably hard to guess
     << UniqueID ();    // it's probably hard to know how many unique IDs were allocated
      
  return SecureHash (os.str ()).str ();

  } // end of makeRandomHash

// returns an appropriate string depending on whether num is 1 or not
const string plural (const int num, const string & units, const bool addSpace)
  {
  string result;
  
  if (num != 1)
    result = MAKE_STRING (num << " " << units << "s");
  else 
    result = MAKE_STRING (num << " " << units);
 
  if (addSpace)
    return result + " ";
  
  return result;  
  }

// converts a number of seconds into a time interval, eg.
//   4 weeks 2 days 1 hour 3 minutes 1 second
const string intervalString (const int seconds)
  {
  const int secsPerMinute = 60;
  const int secsPerHour = secsPerMinute * 60;
  const int secsPerDay = secsPerHour * 24;
  const int secsPerWeek = secsPerDay * 7;
 
// break down into days, hours, minutes, seconds
  int weeksElapsed;
  int daysElapsed;
  int hoursElapsed;
  int minsElapsed;
  int secsElapsed = seconds;  // start with supplied seconds
  
  // weeks
  weeksElapsed = secsElapsed / secsPerWeek;    // how many weeks up?
  secsElapsed -= weeksElapsed * secsPerWeek;   // less seconds now

  // days
  daysElapsed =  secsElapsed / secsPerDay;    // how many days up?
  secsElapsed -= daysElapsed * secsPerDay;    // less seconds now
  
  // hours
  hoursElapsed = secsElapsed / secsPerHour;   // how many hours up?
  secsElapsed -= hoursElapsed * secsPerHour;  // less seconds now
  
  // minutes
  minsElapsed =  secsElapsed / secsPerMinute; // how many minutes up?
  secsElapsed -= minsElapsed * secsPerMinute; // less seconds now
   
  if (weeksElapsed > 0)
    return plural (weeksElapsed, "week") +
           plural (daysElapsed, "day") +
           plural (hoursElapsed, "hour") +
           plural (minsElapsed, "minute") +
           plural (secsElapsed, "second", false);
  
  if (daysElapsed > 0)
    return plural (daysElapsed, "day") +
           plural (hoursElapsed, "hour") +
           plural (minsElapsed, "minute") +
           plural (secsElapsed, "second", false);
  
  if (hoursElapsed > 0)
    return plural (hoursElapsed, "hour") +
           plural (minsElapsed, "minute") +
           plural (secsElapsed, "second", false);
  
  if (minsElapsed > 0)
    return plural (minsElapsed, "minute") +
           plural (secsElapsed, "second", false);
  
  return plural (secsElapsed, "second", false);
  } // end of intervalString
