/*
 * Copyright (C) 1999  Rob Crittenden (rcrit@greyoak.com)
 * Copyright (C) 1999,2000  Ross Combs (rocombs@cs.nmsu.edu)
 *
 * 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; either version 2
 * of the License, 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 program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#define BNETTIME_INTERNAL_ACCESS
#include "common/setup_before.h"
#include <stdio.h>
#ifdef HAVE_STDDEF_H
# include <stddef.h>
#else
# ifndef NULL
#  define NULL ((void *)0)
# endif
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include "compat/strerror.h"
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include "compat/gettimeofday.h"
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#include "common/eventlog.h"
#include "common/bn_type.h"
#include "common/bnettime.h"
#include "common/setup_after.h"


/*
 * By comparing the hex numbers from packet dumps with the times displayed
 * on the login screen, bnettime seems to be in units of 10E-7 seconds. It
 * is stored in a 64bit value, and represented as two distinct numbers when
 * it is used as a string in the protocol.
 *
 * examples (client in GMT timezone):
 * 11/04/99 05:16    0x01                  "29304451 3046165090"
 * 01/03/99 11:19    0x01                  "29243146 3825346784"
 * 08/02/99 21:28    0x01                  "29285677 4283311890"
 * 12/12/99 01:55    0x01                  "29312068 151587081"
 *
 * The top number seems to be in units of about 7 minutes because the bottom
 * number rolls over every 2^32 10E-7 seconds, which is:
 * (2^32)*.0000001
 * 429.4967296 // seconds
 * 429/60
 * 7.15827882666666666666 // minutes
 *
 * The epoch was determined using a binary search looking for Jan 1, 1970
 * (GMT) to be displayed after setting the last game time to a pair of
 * numbers. It was determined through all 64 bits by looking for the exact
 * number where adding one pushes it over to 00:01. It was determined to be:
 * "27111902 3577643008"
 *
 * It appears that this is the same format as is used in MS-DOS to store file
 * times. The format is a 64 bit number telling how many 100 nanosecond
 * intervals since Jan 1, 1601.
 *
 * If I was clever I should be able to do this without floating point math.
 * I don't feel like being clever though :) (Refer to the function
 * DOSFS_UnixTimeToFileTime() in dosfs.c in the WINE source code for an
 * example that I wish I had seen before writing this).
 *
 * Update... from looking at the WINE sources VAP has determined that these
 * are actually in MS-Windows FileTime format.
 *
 * "In the WINE listing, you can see that
 *  FileTime = (UnixTime * 10 000 000) + 116 444 736 000 000 000 + remainder
 *  These dates are represented from 1 Jan 1601."
 */

#define SEC_PER_USEC .0000001

#define UNIX_EPOCH 11644473600. /* seconds the Unix epoch is from the bnettime epoch */

#define SEC_PER_UPPER 429.4967296 /* (2^32)*10E-7 */
#define SEC_PER_LOWER .0000001

#define UPPER_PER_SEC .00232830643653869628  /* 10E7/(2^32) */
#define LOWER_PER_SEC 10000000.


extern t_bnettime secs_to_bnettime(double secs)
{
    t_bnettime temp;
    
/*eventlog(eventlog_level_debug,"secs_to_bnettime","got %+13.8f",secs);*/
    temp.u = (unsigned int)(secs*UPPER_PER_SEC);
    temp.l = (unsigned int)(secs*LOWER_PER_SEC);
    
    return temp;
}


extern double bnettime_to_secs(t_bnettime bntime)
{
    return ((double)bntime.u)*SEC_PER_UPPER+
           ((double)bntime.l)*SEC_PER_LOWER;
}


extern t_bnettime time_to_bnettime(time_t stdtime, unsigned int usec)
{
    return secs_to_bnettime((double)stdtime+(double)usec*SEC_PER_USEC+UNIX_EPOCH);
}


extern time_t bnettime_to_time(t_bnettime bntime)
{
    return (time_t)(bnettime_to_secs(bntime)-UNIX_EPOCH);
}


/* return current time as bnettime */
extern t_bnettime bnettime(void)
{
    struct timeval tv;
    
    if (gettimeofday(&tv,NULL)<0)
    {
        eventlog(eventlog_level_error,"bnettime","could not get time (gettimeofday: %s)",strerror(errno));
        return time_to_bnettime(time(NULL),0);
    }
    return time_to_bnettime((time_t)tv.tv_sec,tv.tv_usec);
}


/* FIXME: the string functions here should probably go in account_wrap */
extern char const * bnettime_get_str(t_bnettime bntime)
{
    static char temp[1024];
    
    sprintf(temp,"%u %u",bntime.u,bntime.l);
    
    return temp;
}


extern int bnettime_set_str(t_bnettime * bntime, char const * timestr)
{
    if (!bntime)
    {
	eventlog(eventlog_level_error,"bnettime_set_str","got NULL bntime");
	return -1;
    }
    if (!timestr)
    {
	eventlog(eventlog_level_error,"bnettime_set_str","got NULL timestr");
	return -1;
    }
    
    if (sscanf(timestr,"%u %u",&bntime->u,&bntime->l)!=2)
	return -1;
    
    return 0;
}


extern void bnettime_to_bn_long(t_bnettime in, bn_long * out)
{
    if (!out)
    {
	eventlog(eventlog_level_error,"bnettime_to_bn_long","got NULL out");
	return;
    }
    
    bn_long_set_a_b(out,in.u,in.l);
}


extern void bn_long_to_bnettime(bn_long in, t_bnettime * out)
{
    if (!out)
    {
	eventlog(eventlog_level_error,"bn_long_to_bnettime","got NULL out");
	return;
    }
    
    out->u = bn_long_get_a(in);
    out->l = bn_long_get_b(in);
}


extern int local_tzbias(void) /* in minutes */
{
#ifdef HAVE_MKTIME
    time_t      now;
    time_t      test;
    time_t      testloc;
    struct tm * temp;

    now = time(NULL);
    
    if (!(temp = gmtime(&now)))
	return 0;
    if ((test = mktime(temp))==(time_t)(-1))
	return 0;
    
    if (!(temp = localtime(&now)))
	return 0;
    if ((testloc = mktime(temp))==(time_t)(-1))
	return 0;
    
    if (testloc>test) /* time_t is probably unsigned... */
	return -(int)(testloc-test)/60;
    return (int)(test-testloc)/60;
#else
    return 0; /* can't determine current offset */
#endif
}


extern t_bnettime bnettime_add_tzbias(t_bnettime bntime, int tzbias)
{
    return secs_to_bnettime(bnettime_to_secs(bntime)-(double)tzbias*60.0);
}
