/*

 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 <string>
#include <iterator>
#include <set>
#include <vector>
#include <iomanip>

// Windows needs this to be explicitly included   -dhaley aug-15-04
#ifdef WIN32
#include <time.h>
#endif

using namespace std; 

#include "strings.h"

// string find-and-replace
const string FindAndReplace
  (const string& source, const string target, const string replacement)
  {
  string str = source;
  string::size_type pos = 0,   // where we are now
                    found;     // where the found data is
  if (target.size () > 0)   // searching for nothing will cause a loop
    while ((found = str.find (target, pos)) != string::npos)
      {
      str.replace (found, target.size (), replacement);
      pos = found + replacement.size ();
      }
  return str;
  };   // end of FindAndReplace

const string trim_right (const string & s, const string & t)
  { 
  string d (s); 
  string::size_type i (d.find_last_not_of (t));
  if (i == string::npos)
    return "";
  else
   return d.erase (d.find_last_not_of (t) + 1) ; 
  }  // end of trim_right

const string trim_left (const string & s, const string & t) 
  { 
  string d (s); 
  return d.erase (0, s.find_first_not_of (t)) ; 
  }  // end of trim_left

// get rid of leading and trailing spaces from a string
const string trim (const string & s, const string & t)
{
  string d = s; 
  string::size_type i = d.find_last_not_of (t);
  if (i == string::npos)
    return "";
  else
   return d.erase (i + 1).erase (0, s.find_first_not_of (t)) ; 
}

// returns a lower case version of the string 
const string tolower (const string & s)
  {
string d = s;
  transform (ISEQ (d), d.begin (), (int(*)(int)) tolower);
  return d;
  }  // end of tolower

// transformation function for tocapitals that has a "state"
// so it can capitalise a sequence
class fCapitals : public unary_function<unsigned char,unsigned char> 
  {
  bool bUpper;

  public:

  // first letter in string will be in capitals
  fCapitals () : bUpper (true) {}; // constructor

  unsigned char operator() (const unsigned char & c)  
    { 
    unsigned char c1;
    // capitalise depending on previous letter
    if (bUpper)
      c1 = toupper (c);
    else
      c1 = tolower (c);

    // work out whether next letter should be capitals
    bUpper = isalnum (c) == 0;
    return c1; 
    }
  };  // end of class fCapitals

// returns a capitalized version of the string 
const string tocapitals (const string & s)
  {
string d = s;
  transform (ISEQ (d), d.begin (), fCapitals ());
  return d;
  }  // end of tocapitals
  
// compare strings for equality using the binary function above
// returns true is s1 == s2
bool ciStringEqual (const string & s1, const string & s2)
  {
  return ciEqualTo () (s1, s2);
  }  // end of ciStringEqual

  /* split a line into the first word, and rest-of-the-line */

const pair<string, string> GetWord (const string & s, const string & delim)
{
  string rest = s;  // make copy so we can modify it
  
 // find delimiter  
  string::size_type i (rest.find (delim));

  // split into before and after delimiter
  string w (rest.substr (0, i));

  if (i == string::npos)
    rest.erase ();          // if no delimiter, remainder is empty
  else   
    rest.erase (0, i + delim.size ());  // erase up to the delimiter

  // return first word in line, rest of line
  return make_pair (trim (w), trim (rest));
  
} /* end of GetWord */

  
// compare to see if start of s1 is s2
//  eg. returns true for: strPrefix ("abacus", "aba");

const bool strPrefix (const string & s1,     // string to search    
                const string & s2,     // what to look for 
                const bool no_case)    // case-insensitive?   
  {                                               
  
  // if either string is empty or s1 is smaller than s2
  //  then they can't be identical
  if (s1.empty () ||
      s2.empty () ||
      s1.size () < s2.size ())
    return false;

  if (no_case)
    return ciStringEqual (s1.substr (0, s2.size ()), s2);
  else
    return s1.substr (0, s2.size ()) == s2;

  } // end of strPrefix


// compares to see if (s1, offset by pos, for length s2) == s2

const bool strPrefix (const string & s1,              // string to search
                const string::size_type pos,    // where in s1 to start
                const string & s2,              // what to look for
                const bool no_case)             // case-insensitive?
  {
  // can't be true if position outside string size
  // casts are to ensure a signed comparison 
  if ((int) pos >= ((int) s1.size () - (int) s2.size ()))
    return false;

  // make a substring of s1 for s2's size
  return strPrefix (s1.substr (pos, s2.size ()), s2, no_case);

  } // end of strPrefix

// simple function for transforming non-printables to spaces
char non_printables_to_spaces (const char & c)
  {
  if (c < ' ')
    return ' ';
  return c;
  }

const string stringDate (const time_t & t)
  {  
  struct tm *thetime;
  char sTimeBuff [80];

  thetime = localtime (&t);
  strftime (sTimeBuff, sizeof sTimeBuff, "%A, %B %d, %Y, %I:%M %p", thetime);
  return sTimeBuff;
  } // end of stringDate

const string stringShortDate (const time_t & t)
  {  
  struct tm *thetime;
  char sTimeBuff [80];

  thetime = localtime (&t);
  strftime (sTimeBuff, sizeof sTimeBuff, "%Y-%m-%d %H:%M:%S", thetime);
  return sTimeBuff;
  } // end of stringShortDate

const string stringFileDate (const time_t & t)
  {  
  struct tm *thetime;
  char sTimeBuff [80];

  thetime = localtime (&t);
  strftime (sTimeBuff, sizeof sTimeBuff, "%Y-%m-%d", thetime);
  return sTimeBuff;
  } // end of stringFileDate

 // tests a string to see if it is numeric
const bool IsNumber (const string & s, const bool bSigned)
  {
  string::size_type pos = 0;

  if (s.empty ())
    return false;  // empty? can't be a number

  if (bSigned)
    {
    if (s [0] == '+' || s [0] == '-')
      {
      pos++;
      if (s.size () == 1)
        return false;   // can't just have the sign
      }
    }  // end of sign allowed

  return s.find_first_not_of ("0123456789", pos) == string::npos;
  }   // end of IsNumber


// tests a string to see if it is hex
const bool IsHex (const string & s)
  {

  if (s.empty ())
    return false;  // empty? can't be hex

  return s.find_first_not_of ("0123456789ABCDEFabcdef") == string::npos;
  }   // end of IsHex

const string firstLetterCaps (const string & s)
  {
  string sFixed = s;
  // make sentence caps
  if (!sFixed.empty ())
    sFixed [0] = toupper (sFixed [0]);
  return sFixed;
  }

// for below, checks supplied number is in range 0 to 60
static int InRange (const int i)
  {
  if (i >= 0 && i <= 60)
    return i;
  else
    return 0;
  }

// this will generate a hopefully-unique ID for use in objects and things
// that we want to be able to refer to without clashes.
// what is does is take the invocation time (first time called) and turn it
// into a 6-character sequence in the form YMDhms (year, month, day, hour,
// minute, second) where each of those is a single letter. Then it appends 
// .1, .2, .3 etc. where the appended number will be incremented each time
// in a session. Thus, the only way you would get duplication would be if
// you started two sessions in the same second in real-time.

const string UniqueID ()
  {
static string sInvokedTime;
static unsigned int count = 0;   // maybe make longer (eg. long long) if necessary

  // first time through, calculate the base string, the rest of this session
  // it will not change, and we will just add 1 to the ID. This lets us 
  // generate thousands of them in a single second if we want to.
  if (sInvokedTime.empty ())
    {
    // 62 characters - most fields should not exceed 59 (eg. the second or the minute)
    char letters [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    
    time_t currentTime;
    time (&currentTime);    // get time now
    tm * convertedTime = localtime (&currentTime);  // get fields (hour, minute etc.)
      
    // warning - this will stop working properly in 2060, because that is the limit
    // for the year, however provided the day/month/hour/minute/second are unique it will
    // still be pretty good.
    sInvokedTime += letters [InRange (convertedTime->tm_year - 100)]; // year (2000 is zero)
    sInvokedTime += letters [InRange (convertedTime->tm_mon)];        // month
    sInvokedTime += letters [InRange (convertedTime->tm_mday)];       // day
    sInvokedTime += letters [InRange (convertedTime->tm_hour)];       // hour
    sInvokedTime += letters [InRange (convertedTime->tm_min)];        // minute
    sInvokedTime += letters [InRange (convertedTime->tm_sec)];        // second
    sInvokedTime += ".";    // separator
    }

  return sInvokedTime + MAKE_STRING (++count);
  } // end of UniqueID

// hash generator

// ctor
SecureHash::SecureHash (const string & s) 
  { 
  // work out if little-endian or not
  int x = 1;
  reverse_wanted = * (char *) &x == 1;
  hash (s);   // hash initial string
  } // end of SecureHash::SecureHash

// hash a supplied string
void SecureHash::hash (const string & s)
  {
  shsInit (this);
  shsUpdate (this, (const BYTE *) s.c_str (), s.size ());
  shsFinal (this);
  } // end of SecureHash::hash 

// return result in hex
const string SecureHash::str ()
  {
  ostringstream os;
  os << hex;
  for (int i = 0; i < NUMITEMS (digest); i++)
    os << setw (8) << setfill ('0') << digest [i];
  return os.str ();
  } // end of SecureHash::str 
  
const string makeHeading (const string & text, // eg. "Help"
                          const char filler,   // eg. '-'
                          const string & textColour, // eg. "`C"
                          const int width)  // how long
  {
  int fillerCount = (width - text.size () - 2) / 2;
  return string (fillerCount, filler) +
        "[`^" + // bracket and push current colour
        textColour + text +   // what we want to say and the colour
        "`v]" + // pop colour and other bracket
        string (fillerCount, filler);
  } // end of makeHeading

// decode URLs - + becomes space, and convert %xx into hex strings

const string URLDecode (const string & sInput)
  {
const char * p = sInput.c_str ();
const char * pStart = p;   // where buffer starts
string sFixed;

//  printf ("Decoding URL string: %s\n", str (sInput));
  
  while (*p)
    {
    unsigned char c = *p;
    
    switch (c)
      {
      case '+': 
          sFixed += string (pStart, p++ - pStart);
          sFixed += " ";
          pStart = p;                             
          break;  
          
      case '%':
          {
          sFixed += string (pStart, p++ - pStart);
 
          unsigned char cVal = 0;
          c = toupper (*p++);
          if (isxdigit (c))
            {
            if (c >= 'A')
              c -= 7;
            cVal = (c - '0') << 4;
            }
          c = toupper (*p++);
          
          if (isxdigit (c))
            {
            if (c >= 'A')
              c -= 7;
            cVal |= c - '0';
            }
          
          sFixed += cVal;
          
          pStart = p;
          }          
          break;        
          
       default:
         p++;
         break;
         
      }
      
  } // end of loop
  
  sFixed += pStart;

//  printf ("Decoded string: %s\n", str (sFixed));
   
  return sFixed; 
  } // end of URLDecode

// converts < > and & " to &lt; &gt; &amp; and &quot;
const string FixHTML (const string & sInput)
  {
const char * p = sInput.c_str ();
const char * pStart = p;   // where buffer starts
string sFixed;
  
  while (*p)
    {
    char c = *p;
    
    switch (c)
      {
      case '<': 
          sFixed += string (pStart, p++ - pStart);
          sFixed += "&lt;";
          pStart = p;                   
          break;  

      case '>': 
          sFixed += string (pStart, p++ - pStart);
          sFixed += "&gt;";
          pStart = p;                   
          break;  

      case '&': 
          sFixed += string (pStart, p++ - pStart);
          sFixed += "&amp;";
          pStart = p;                   
          break;  
          
      case '\"': 
          sFixed += string (pStart, p++ - pStart);
          sFixed += "&quot;";
          pStart = p;                   
          break;  

      default:
         p++;
         break;
         
      }
      
  } // end of loop
  
  sFixed += pStart;

  return sFixed;     
  } // end of FixHTML

// changes newline characters to the <br/> sequence
const string nl2br (const string & sInput)
  {
  return FindAndReplace (sInput, "\n", "<br/>\n");
  } // end of nl2br
