//<copyright>
// 
// Copyright (c) 1993-1995
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// 
//</copyright>

//<file>
//
// Name:       environ.C
//
// Purpose:    Access to environment variables and other system values
//
// Created:     1 Feb 92    Gerald Pani
//
// Modified:   21 Jul 93    Gerald Pani
//
//
//
// Description:
//
//   See class Environ.
//
//</file>

// $Log: environ.C,v $
// Revision 1.16  1997/02/04 13:39:38  jfasch
// for BSDI also
// #define hg_utils_environ_C_have_mktime
//
// Revision 1.15  1996/12/04 19:11:42  jschipf
// add the functions lDate2gmDate
// andd gmDate2lDate
//
// Revision 1.14  1996/08/01 09:55:19  jfasch
// removed some compiler warnings
//
// Revision 1.13  1996/06/26 12:17:08  jfasch
// changed Environ::setEnv() to use ::putenv() (which suddenly appeared
// to be available on all our machines (:-?))
//
// Revision 1.12  1996/06/24 10:33:31  gpani
// WIN32
//
// Revision 1.11  1996/06/17 09:07:15  gpani
// Environ::timeString correction for years > 1999 ("2035/02/28")
//
// Revision 1.10  1996/02/21 15:01:49  jfasch
// GNU 2.7.2 allows conversions from any pointer type to bool, resulting
// in ambiguities in the setEnv methods.
//
// Revision 1.9  1996/02/07 13:48:43  gpani
// IBM!!
//
// Revision 1.8  1996/02/07 13:16:21  gpani
// Environ::hostAddress: always check if hostname is already an address
//
// Revision 1.7  1995/10/11 14:27:30  gpani
// hgtime2lDate, lDate2hgtime
//
// Revision 1.6  1995/10/11 10:35:37  gpani
// gmDate "yyyy/mm/dd hh:mm:ss" and "yy/mm/dd hh:mm:ss"
//
// Revision 1.5  1995/10/10 08:07:01  gpani
// const RString& Environ::hgtime2gmDate( hgtime time)
//
// Revision 1.4  1995/10/09 16:39:16  gpani
// hgtime gmDate2hgtime( const char*)
//
// Revision 1.3  1995/08/24 13:37:47  gpani
// ultrix adjustments
//

#include "hgunistd.h"

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

#ifdef __PC__
#include <pcutil\bsdstuff.h>
#endif

#include <time.h>

// ULTRIX, AIX, PC
# include <stdio.h>

#include <sys/socket.h>
#include <netdb.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/utsname.h>
#include <pwd.h>

#include <errno.h>

#include "types.h"
#include "environ.h"


#if defined(ULTRIX) && defined(__GNUC__)
extern "C" char* inet_ntoa( in_addr);
extern "C" unsigned long inet_addr( const char*);
#endif

#if defined(AIX) || defined(BSD) && defined(SUN) && defined(__GNUC__)
extern "C" char* inet_ntoa( in_addr);
#endif

#define MAX_INT_TO_STRING_LEN 11


const char* Environ::version_1_0 = "Environ: $Id: environ.C,v 1.16 1997/02/04 13:39:38 jfasch Exp $";

boolean Environ::getEnv( const RString& var, RString& value) {
     char* tmp;
     if ((tmp = ::getenv( var.string()))) {
	  value = tmp;
	  return true;
     }
     return false;
}

boolean Environ::getEnv( const RString& var, int& value) {
     char* tmp;
     if ((tmp = ::getenv( var.string()))) {
	  value = atoi( tmp);
	  return true;
     }
     return false;
}

boolean Environ::getEnv( const RString& var, boolean& value) {
     char* tmp;
     if ((tmp = ::getenv( var.string()))) {
	  if (!::strcmp( tmp, "true") || !::strcmp( tmp, "1"))
	       value = true;
	  else
	       value = false;
	  return true;
     }
     return false;
}


// static int env_index( const RString& var);
// static int env_entries();
// static void add_env( const RString& var, const RString& value);
// static boolean change_env( const RString& var, const RString& value);

void Environ::setEnv( const RString& var, const RString& value) {
//    if (! change_env( var, value))
//       add_env( var, value);
   char* tmp = (char*)malloc (var.length() + value.length() + 2) ;
   ::sprintf (tmp, "%s=%s", var.string(), value.string()) ;
   ::putenv (tmp) ;
}
void Environ::setEnv( const RString& var, const char* value) {
   setEnv (var, RString(value)) ;
}
void Environ::setEnv( const RString& var, int value) {
   static char tmp[MAX_INT_TO_STRING_LEN + 1];
   ::sprintf( tmp, "%d", value);
   setEnv( var, tmp);
}
void Environ::setEnv( const RString& var, boolean value) {
   static const RString t ("true") ;
   static const RString f ("false") ;
   setEnv( var, value? t: f) ;
}

const RString& Environ::user() {
     static RString user;
     RString passwd;
     getpw( user, passwd);
     return user;
}

boolean Environ::getpw( RString& user, RString& passwd) {
     struct passwd* pwd;
     if (!(pwd = ::getpwuid( ::getuid()))) {
	  ::perror( "Environ::getpw:getpwuid");
	  user = nil;
	  passwd = nil;
	  return false;
     }
     else {
	  user = pwd->pw_name;
	  passwd = pwd->pw_passwd;
	  return true;
     }
}


const RString& Environ::timeString( time_t t, boolean local) {
     static RString timeString;
     static char timestr[18];
     struct tm* tm = local ? ::localtime( &t) : ::gmtime( &t);
     if (tm->tm_year >= 100)
	::sprintf( timestr, "%04d/%02d/%02d %02d:%02d:%02d", 
		   tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
     else
	::sprintf( timestr, "%02d/%02d/%02d %02d:%02d:%02d", 
		   tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
     return timeString = timestr;
}       

const RString& Environ::gmTimeString() {
     time_t time = ::time( nil);
     return timeString( time, false);
}       


static char digit[256] = {     
//   0 1 2 3 4 5 6 7 8 9 a b c d e f
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 1
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 2
     1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, // 3
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 4
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 5
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 6
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 7
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 9
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // a
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // b
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // c
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // d
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // e
     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // f
     };

static int monTable[12] =     { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
static int leapMonTable[12] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };
static int mdayTable[12] =        {31, 28, 31, 30,  31,  30,  31,  31,  30,  31,  30,  31 };
static int leapMdayTable[12] =    {31, 29, 31, 30,  31,  30,  31,  31,  30,  31,  30,  31 };

#if defined(OSF1) || defined(ULTRIX) || (defined(SUN) && HG_OSMaj >= 5) || defined(HPUX) || defined(IRIX) || defined(LINUX) || defined(AIX) || defined(BSDI) || defined(WIN32)
#define hg_utils_environ_C_have_mktime
#endif

#if !defined(IRIX) && !defined(HPUX) && !(defined(SUN) && HG_OSMaj >= 5) && !defined(LINUX) && !defined(AIX) && !defined(WIN32)
#define hg_utils_environ_C_have_tm_zone
#endif

boolean Environ::gmDate2Sec( const RString& date, time_t& secs) {
     static boolean havetimezone;
     static time_t timezone = 0;
     if (!havetimezone) {
	  tzset();
	  havetimezone = true;
#ifdef hg_utils_environ_C_have_mktime
	  // calculate offset from GMT
	  struct tm tms;
	  tms.tm_sec = 0;
	  tms.tm_min = 0;
	  tms.tm_hour = 0;
	  tms.tm_mday = 2;
	  tms.tm_mon = 0;
	  tms.tm_year = 70;
	  tms.tm_wday = 0;
	  tms.tm_yday = 0;
#if defined(SUN) && HG_OSMaj >= 5
	  tms.tm_isdst = 1;
#else
	  tms.tm_isdst = 0;
#endif

#ifdef hg_utils_environ_C_have_tm_zone
	  tms.tm_zone = nil;
	  tms.tm_gmtoff = 0L;
#endif
	  time_t secs = mktime( &tms);
	  timezone = secs - 86400;
#endif
     }
     
     struct tm tms;
     tms.tm_sec = -timezone;
     tms.tm_min = 0;
     tms.tm_hour = 0;
     tms.tm_mday = 1;
     tms.tm_mon = 0;
     tms.tm_year = 70;
     tms.tm_wday = 0;
     tms.tm_yday = 0;
#if defined(SUN) && HG_OSMaj >= 5
     tms.tm_isdst = 1;
#else
     tms.tm_isdst = 0;
#endif
     
#ifdef hg_utils_environ_C_have_tm_zone
     tms.tm_zone = nil;
     tms.tm_gmtoff = 0L;
#endif

     if (digit[date[0]]) {
	  tms.tm_year = (date[0] - '0')*10;
	  if (digit[date[1]]) {
	       tms.tm_year += date[1] - '0';
	       if (date[2] == '/' && digit[date[3]]) {
		    tms.tm_mon = (date[3] - '0')*10;
		    if (digit[date[4]]) {
			 tms.tm_mon += date[4] - '0';
			 if (date[5] == '/' && digit[date[6]]) {
			      tms.tm_mday = (date[6] - '0')*10;
			      if (digit[date[7]]) {
				   tms.tm_mday += date[7] - '0';
				   if (date[8] == ' ' && digit[date[9]]) {
					tms.tm_hour = (date[9] - '0')*10;
					if (digit[date[10]]) {
					     tms.tm_hour += date[10] - '0';
					     if (date[11] == ':' && digit[date[12]]) {
						  tms.tm_min = (date[12] - '0')*10;
						  if (digit[date[13]]) {
						       tms.tm_min += date[13] - '0';
						       if (date[14] == ':' && digit[date[15]]) {
							    tms.tm_sec = (date[15] - '0')*10;
							    if (digit[date[16]]) {
								 tms.tm_sec += date[16] - '0';
							    }
							    tms.tm_sec -= timezone;
						       }
						  }
					     }
					}
				   }
			      }
			 }
		    }
		    tms.tm_mon--;
	       }
	  }
     }

#ifdef hg_utils_environ_C_have_mktime
     secs = mktime( &tms);
#else
     secs = timegm( &tms);
#endif
     if (secs == -1) {
	  if (tms.tm_year != 69 
	      || tms.tm_mon != 11
	      || tms.tm_mday != 31
	      || tms.tm_hour != 23
	      || tms.tm_min != 59
	      || tms.tm_sec != 59)
	       return false;
     }
     return true;
}

static hgtime date2hgtime( const char* date, boolean local) {
     hgtime hgtime;

     static boolean havetimezone;
     static time_t timezone = 0;
     if (!havetimezone) {
	  tzset();
	  havetimezone = true;
#ifdef hg_utils_environ_C_have_mktime
	  // calculate offset from GMT
	  struct tm tms;
	  tms.tm_sec = 0;
	  tms.tm_min = 0;
	  tms.tm_hour = 0;
	  tms.tm_mday = 2;
	  tms.tm_mon = 0;
	  tms.tm_year = 70;
	  tms.tm_wday = 0;
	  tms.tm_yday = 0;
#if defined(SUN) && HG_OSMaj >= 5
	  tms.tm_isdst = 1;
#else
	  tms.tm_isdst = 0;
#endif

#ifdef hg_utils_environ_C_have_tm_zone
	  tms.tm_zone = nil;
	  tms.tm_gmtoff = 0L;
#endif
	  time_t secs = mktime( &tms);
	  timezone = secs - 86400;
#endif
     }
     
     int year = 0;
     int ndx;
     for (ndx = 0; ndx < 4 && digit[date[ndx]]; ndx++)
	  year = year*10 + (date[ndx] - '0');
     if (ndx == 2)
	  year += 1900;
     else if (ndx != 4)
	  // no year
	  year = -1;

     if (year >= 1970) {
	  static struct tm tms;
	  tms.tm_sec = local ? 0 : -timezone;
	  tms.tm_min = 0;
	  tms.tm_hour = 0;
	  tms.tm_mday = 1;
	  tms.tm_mon = 0;
	  tms.tm_year = year - 1900;
	  tms.tm_wday = 0;
	  tms.tm_yday = 0;
#if defined(SUN) && HG_OSMaj >= 5
	  tms.tm_isdst = local ? -1 : 1;
#else
	  tms.tm_isdst = local ? -1 : 0;
#endif
     
#ifdef hg_utils_environ_C_have_tm_zone
	  tms.tm_zone = nil;
	  tms.tm_gmtoff = 0L;
#endif

	  ndx -= 2;
	  if (date[ndx+2] == '/' && digit[date[ndx+3]]) {
	       tms.tm_mon = (date[ndx+3] - '0')*10;
	       if (digit[date[ndx+4]]) {
		    tms.tm_mon += date[ndx+4] - '0';
		    if (date[ndx+5] == '/' && digit[date[ndx+6]]) {
			 tms.tm_mday = (date[ndx+6] - '0')*10;
			 if (digit[date[ndx+7]]) {
			      tms.tm_mday += date[ndx+7] - '0';
			      if (date[ndx+8] == ' ' && digit[date[ndx+9]]) {
				   tms.tm_hour = (date[ndx+9] - '0')*10;
				   if (digit[date[ndx+10]]) {
					tms.tm_hour += date[ndx+10] - '0';
					if (date[ndx+11] == ':' && digit[date[ndx+12]]) {
					     tms.tm_min = (date[ndx+12] - '0')*10;
					     if (digit[date[ndx+13]]) {
						  tms.tm_min += date[ndx+13] - '0';
						  if (date[ndx+14] == ':' && digit[date[ndx+15]]) {
						       tms.tm_sec = (date[ndx+15] - '0')*10;
						       if (digit[date[ndx+16]]) {
							    tms.tm_sec += date[ndx+16] - '0';
						       }
						       tms.tm_sec -= local ? 0 : timezone;
						  }
					     }
					}
				   }
			      }
			 }
		    }
	       }
	       tms.tm_mon--;
	  }

#ifdef hg_utils_environ_C_have_mktime
	  hgtime = mktime( &tms);
#else
	  hgtime = local ? timelocal( &tms) : timegm( &tms);
#endif
	  if (hgtime & 0x80000000)
	       hgtime = 0;
	  hgtime |= 0x80000000;
     }
     else if (year == -1) {
	  hgtime = 0x7ff50558;	// 0000/01/01 00:00:00
     }
     else {
	  // calculate days
	  hgtime = 0x80000000 - 1;
	  int yDiff = 1970 - year;
	  hgtime -= yDiff * 365;
	  hgtime -= (yDiff + 2)/4 - (yDiff + 30)/100 + (yDiff + 30)/400;
	  boolean leapYear = false;
	  if ((yDiff + 2)%4) 
	       leapYear = false;
	  else if (!((yDiff + 30)/100))
	       leapYear = true;
	  else if ((yDiff + 30)/400)
	       leapYear = true;
	  int mon = 0;
	  int mday = 0;
	  ndx -= 2;
	  if (date[ndx+2] == '/' && digit[date[ndx+3]]) {
	       mon = (date[ndx+3] - '0')*10;
	       if (digit[date[ndx+4]]) {
		    mon += date[ndx+4] - '0';
		    if (date[ndx+5] == '/' && digit[date[ndx+6]]) {
			 mday = (date[ndx+6] - '0')*10;
			 if (digit[date[ndx+7]]) {
			      mday += date[ndx+7] - '0';
			 }
		    }
	       }
	  }
	  mon = (mon < 1) ? 1 : mon;
	  mon = (mon > 12) ? 12 : mon;
	  mday = (mday < 1) ? 1 : mday;
	  if (leapYear) {
	       hgtime += leapMonTable[ mon -1];
	       hgtime += (mday > leapMdayTable[ mon -1]) ? leapMdayTable[ mon -1] : mday;
	  }
	  else {
	       hgtime += monTable[ mon -1];
	       hgtime += (mday > mdayTable[ mon -1]) ? mdayTable[ mon -1] : mday;
	  }
     }
     return hgtime;
}

hgtime Environ::gmDate2hgtime( const char* date) {
     return date2hgtime( date, false);
}

hgtime Environ::lDate2hgtime( const char* date) {
     return date2hgtime( date, true);
}

#undef hg_utils_environ_C_have_mktime
#undef hg_utils_environ_C_have_tm_zone

static const RString& hgtime2date( hgtime time, boolean local) {
     static RString dummyDate = "00/01/01 00:00:00";
     static RString timeString;
     static char timestr[20];

     if (time & 0x80000000) {
	  return Environ::timeString( time & 0x7fffffff, local);
     }

     unsigned int days = 0x80000000 - time;
     unsigned int years = days / 365;
     unsigned int leapDays = (years * 365) + ((years + 2)/4) - ((years + 30)/100) + ((years + 30)/400);
     while (leapDays >= days) {
	  years--;
	  leapDays = (years * 365) + ((years + 2)/4) - ((years + 30)/100) + ((years + 30)/400);
     }
     while (leapDays < days) {
	  years++;
	  leapDays = (years * 365) + ((years + 2)/4) - ((years + 30)/100) + ((years + 30)/400);
     }
     days = leapDays - days +1;
     boolean leapYear = false;
     if ((years + 2)%4) 
	  leapYear = false;
     else if (!((years + 30)/100))
	  leapYear = true;
     else if ((years + 30)/400)
	  leapYear = true;

     int mon;
     if (leapYear) {
	  for (mon = 0; mon < 12; mon++) {
	       if (leapMdayTable[mon] >= days)
		    break;
	       days -= leapMdayTable[mon];
	  }
     }
     else {
	  for (mon = 0; mon < 12; mon++) {
	       if (mdayTable[mon] >= days)
		    break;
	       days -= mdayTable[mon];
	  }
     }
	  
     if (years > 70)
	  ::sprintf( timestr, "%04d/%02d/%02d 00:00:00", 
		    1970 - years, mon +1, days);
     else
	  ::sprintf( timestr, "%02d/%02d/%02d 00:00:00", 
		    70 - years, mon +1, days);
     return timeString = timestr;
}

const RString& Environ::lDate2gmDate( const char* date)
{
  return hgtime2date( date2hgtime( date, true), false);
}
const RString& Environ::gmDate2lDate( const char* date)
{
  return hgtime2lDate(gmDate2hgtime(date));
}

const RString& Environ::hgtime2gmDate( hgtime time) {
     return hgtime2date( time, false);
}

const RString& Environ::hgtime2lDate( hgtime time) {
     return hgtime2date( time, true);
}

#if (defined(SUN) && HG_OSMaj >= 5) || defined(OSF1) || defined(AIX)

const RString& Environ::hostName() {
     static RString hostName;
     if (!hostName.length()) {
	  struct utsname uts;
	  if ( ::uname( &uts) != -1)
	       hostName = uts.nodename;
	  else
	       ::perror( "Environ::hostName:uname"); 
     }
     return hostName;
}

#else

const RString& Environ::hostName() {
     static RString hostName;
     if (!hostName.length()) {
	  char buffer[32];
	  if (::gethostname( buffer, 32) == -1)
	       ::perror( "Environ::hostName:gethostname");
	  else
	       hostName = buffer;
     }
     return hostName;
}

#endif


const RString& Environ::hostAddress() {
     static RString hAddress;

     if (!hAddress.length()) {
	  hostAddress( hostName(), hAddress);
     }
     return hAddress;
}

boolean Environ::hostAddress( const RString& hostName, RString& addr) {
{
     unsigned long address = inet_addr( hostName.string());
     in_addr in;
     in.s_addr = address;
     if (hostName == inet_ntoa( in)) {
	  addr = hostName;
	  return true;
     }
}    
#ifndef AIX
     ::sethostent(0);
#endif

     hostent* host;     
     if (!(host = ::gethostbyname( hostName.string()))) {
	  addr = "gethostbyname:";
#ifdef SYSV
	  addr += strerror(errno);
#else
	  if (errno < sys_nerr)
	       addr += sys_errlist[errno];
#endif
	  return false;
     }
     else {
	  in_addr *ia = (in_addr*)host->h_addr;
	  char* inaddr;
	  if (!(inaddr = inet_ntoa(*ia))) {
	       addr = "inet_ntoa:";
#ifdef SYSV
	       addr += strerror(errno);
#else
	       if (errno < sys_nerr)
		    addr += sys_errlist[errno];
#endif
	       return false;
	  }
	  else {
	       addr = inaddr;
	       return true;
	  }
     }
}

boolean Environ::hostName( const RString& hostAddr, RString& name) {
     HG_inet_addr addr;
     if ( (addr = inet_addr( hostAddr.string())) == (HG_inet_addr)-1) {
	  name = "inet_addr:malformed request";
	  return false;
     }
#ifndef AIX
     ::sethostent(0);
#endif
     hostent* host;     
     if (!(host = ::gethostbyaddr( (char*)&addr, sizeof( addr), AF_INET))) {
	  name = "gethostbyaddr:";
#ifdef SYSV
	  name += strerror(errno);
#else
	  if (errno < sys_nerr)
	       name += sys_errlist[errno];
#endif
	  return false;
     }
     else {
	  name = host->h_name;
	  return true;
     }
}


// extern char** environ ;
// static char** const origenviron = environ;

// static int env_index (const RString& name) {
//    for (int i=0 ; environ[i] ; i++)
//       if (!strncmp(environ[i], name.string(), name.length())  &&  environ[i][name.length()]=='=')
//          return i ;
//    return -1 ; // "<name>=" not found
// }

// static int env_entries() {
//    int i = 0 ;
//    while (environ[i]) i++ ;
//    return i ;
// }

// static void add_env (const RString& name, const RString& value) {
//    char** newenv ;
//    if (::origenviron == environ)
//       // copy original environment to the heap
//       newenv = (char**) malloc ((env_entries()+2) * sizeof(char*)) ;
//    else 
//       // already on the heap; just realloc
//       newenv = (char**) realloc (environ, (env_entries()+2) * sizeof(char*)) ;

//    char** ostr ;
//    char** nstr ;
//    for (ostr=environ, nstr=newenv ; *ostr ; *nstr++ = *ostr++) ;
//    char* newentry = (char*) malloc ((name.length()+value.length()+2) * sizeof(char)) ;
//    sprintf (newentry, "%s=%s", name.string(), value.string()) ;
//    *nstr++ = newentry ;
//    *nstr = nil ;
//    environ = newenv ;
// }

// static boolean change_env (const RString& name, const RString& value) {
//    int ix = env_index (name) ;
//    if (ix < 0)
//       return false ;
//    if (strlen (environ[ix]) >= name.length()+value.length()+1)
//       // enough space in the original string
//       sprintf (environ[ix], "%s=%s", name.string(), value.string()) ;
//    else {
//       // ++++ cannot free the old entry because it might not be on the heap, 
//       // but in the original environment space
//       environ[ix] = (char*) malloc (name.length()+value.length()+2) ;
//       sprintf (environ[ix], "%s=%s", name.string(), value.string()) ;
//    }
//    return true ;
// }

