/*
 * Conversion between Time and TimeFields
 *
 * RtlTimeToTimeFields, RtlTimeFieldsToTime and defines are taken from ReactOS and
 * adapted to wine with special permissions of the author
 * Rex Jolliff (rex@lvcablemodem.com)
 *
 * Copyright 1999 Juergen Schmied
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "wine/port.h"

#include <string.h>
#include <time.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include "winternl.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ntdll);

#define TICKSPERSEC        10000000
#define TICKSPERMSEC       10000
#define SECSPERDAY         86400
#define SECSPERHOUR        3600
#define SECSPERMIN         60
#define MINSPERHOUR        60
#define HOURSPERDAY        24
#define EPOCHWEEKDAY       0
#define DAYSPERWEEK        7
#define EPOCHYEAR          1601
#define DAYSPERNORMALYEAR  365
#define DAYSPERLEAPYEAR    366
#define MONSPERYEAR        12

/* 1601 to 1970 is 369 years plus 89 leap days */
#define SECS_1601_TO_1970  ((369 * 365 + 89) * (ULONGLONG)86400)
/* 1601 to 1980 is 379 years plus 91 leap days */
#define SECS_1601_to_1980  ((379 * 365 + 91) * (ULONGLONG)86400)


static const int YearLengths[2] = {DAYSPERNORMALYEAR, DAYSPERLEAPYEAR};
static const int MonthLengths[2][MONSPERYEAR] =
{
	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

static inline int IsLeapYear(int Year)
{
	return Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) ? 1 : 0;
}

static inline void NormalizeTimeFields(CSHORT *FieldToNormalize, CSHORT *CarryField,int Modulus)
{
	*FieldToNormalize = (CSHORT) (*FieldToNormalize - Modulus);
	*CarryField = (CSHORT) (*CarryField + 1);
}

/******************************************************************************
 *  RtlTimeToTimeFields		[NTDLL.@]
 *
 */

VOID WINAPI RtlTimeToTimeFields(
	PLARGE_INTEGER liTime,
	PTIME_FIELDS TimeFields)
{
	const int *Months;
	int LeapSecondCorrections, SecondsInDay, CurYear;
	int LeapYear, CurMonth, GMTOffset;
	long int Days;
	LONGLONG Time = *(LONGLONG *)&liTime;

	/* Extract millisecond from time and convert time into seconds */
	TimeFields->Milliseconds = (CSHORT) ((Time % TICKSPERSEC) / TICKSPERMSEC);
	Time = Time / TICKSPERSEC;

	/* FIXME: Compute the number of leap second corrections here */
	LeapSecondCorrections = 0;

	/* FIXME: get the GMT offset here */
	GMTOffset = 0;

	/* Split the time into days and seconds within the day */
	Days = Time / SECSPERDAY;
	SecondsInDay = Time % SECSPERDAY;

	/* Adjust the values for GMT and leap seconds */
	SecondsInDay += (GMTOffset - LeapSecondCorrections);
	while (SecondsInDay < 0)
	{ SecondsInDay += SECSPERDAY;
	  Days--;
	}
	while (SecondsInDay >= SECSPERDAY)
	{ SecondsInDay -= SECSPERDAY;
	  Days++;
	}

	/* compute time of day */
	TimeFields->Hour = (CSHORT) (SecondsInDay / SECSPERHOUR);
	SecondsInDay = SecondsInDay % SECSPERHOUR;
	TimeFields->Minute = (CSHORT) (SecondsInDay / SECSPERMIN);
	TimeFields->Second = (CSHORT) (SecondsInDay % SECSPERMIN);

	/* FIXME: handle the possibility that we are on a leap second (i.e. Second = 60) */

	/* compute day of week */
	TimeFields->Weekday = (CSHORT) ((EPOCHWEEKDAY + Days) % DAYSPERWEEK);

	/* compute year */
	CurYear = EPOCHYEAR;
	/* FIXME: handle calendar modifications */
	while (1)
	{ LeapYear = IsLeapYear(CurYear);
	  if (Days < (long) YearLengths[LeapYear])
	  { break;
	  }
	  CurYear++;
	  Days = Days - (long) YearLengths[LeapYear];
	}
	TimeFields->Year = (CSHORT) CurYear;

	/* Compute month of year */
	Months = MonthLengths[LeapYear];
	for (CurMonth = 0; Days >= (long) Months[CurMonth]; CurMonth++)
	  Days = Days - (long) Months[CurMonth];
	TimeFields->Month = (CSHORT) (CurMonth + 1);
	TimeFields->Day = (CSHORT) (Days + 1);
}
/******************************************************************************
 *  RtlTimeFieldsToTime		[NTDLL.@]
 *
 */
BOOLEAN WINAPI RtlTimeFieldsToTime(
	PTIME_FIELDS tfTimeFields,
	PLARGE_INTEGER Time)
{
	int CurYear, CurMonth;
	LONGLONG rcTime;
	TIME_FIELDS TimeFields = *tfTimeFields;

	rcTime = 0;

	/* FIXME: normalize the TIME_FIELDS structure here */
	while (TimeFields.Second >= SECSPERMIN)
	{ NormalizeTimeFields(&TimeFields.Second, &TimeFields.Minute, SECSPERMIN);
	}
	while (TimeFields.Minute >= MINSPERHOUR)
	{ NormalizeTimeFields(&TimeFields.Minute, &TimeFields.Hour, MINSPERHOUR);
	}
	while (TimeFields.Hour >= HOURSPERDAY)
	{ NormalizeTimeFields(&TimeFields.Hour, &TimeFields.Day, HOURSPERDAY);
	}
	while (TimeFields.Day > MonthLengths[IsLeapYear(TimeFields.Year)][TimeFields.Month - 1])
	{ NormalizeTimeFields(&TimeFields.Day, &TimeFields.Month, SECSPERMIN);
	}
	while (TimeFields.Month > MONSPERYEAR)
	{ NormalizeTimeFields(&TimeFields.Month, &TimeFields.Year, MONSPERYEAR);
	}

	/* FIXME: handle calendar corrections here */
	for (CurYear = EPOCHYEAR; CurYear < TimeFields.Year; CurYear++)
	{ rcTime += YearLengths[IsLeapYear(CurYear)];
	}
	for (CurMonth = 1; CurMonth < TimeFields.Month; CurMonth++)
	{ rcTime += MonthLengths[IsLeapYear(CurYear)][CurMonth - 1];
	}
	rcTime += TimeFields.Day - 1;
	rcTime *= SECSPERDAY;
	rcTime += TimeFields.Hour * SECSPERHOUR + TimeFields.Minute * SECSPERMIN + TimeFields.Second;
	rcTime *= TICKSPERSEC;
	rcTime += TimeFields.Milliseconds * TICKSPERMSEC;
	*Time = *(LARGE_INTEGER *)&rcTime;

	return TRUE;
}
/************* end of code by Rex Jolliff (rex@lvcablemodem.com) *******************/

/******************************************************************************
 *  RtlSystemTimeToLocalTime 	[NTDLL.@]
 */
VOID WINAPI RtlSystemTimeToLocalTime(
	IN  PLARGE_INTEGER SystemTime,
	OUT PLARGE_INTEGER LocalTime)
{
	FIXME("(%p, %p),stub!\n",SystemTime,LocalTime);

	memcpy (LocalTime, SystemTime, sizeof (PLARGE_INTEGER));
}

/******************************************************************************
 *  RtlTimeToSecondsSince1970		[NTDLL.@]
 */
BOOLEAN WINAPI RtlTimeToSecondsSince1970( const LARGE_INTEGER *time, PULONG res )
{
    ULONGLONG tmp = ((ULONGLONG)time->s.HighPart << 32) | time->s.LowPart;
    tmp = RtlLargeIntegerDivide( tmp, 10000000, NULL );
    tmp -= SECS_1601_TO_1970;
    if (tmp > 0xffffffff) return FALSE;
    *res = (DWORD)tmp;
    return TRUE;
}

/******************************************************************************
 *  RtlTimeToSecondsSince1980		[NTDLL.@]
 */
BOOLEAN WINAPI RtlTimeToSecondsSince1980( const LARGE_INTEGER *time, LPDWORD res )
{
    ULONGLONG tmp = ((ULONGLONG)time->s.HighPart << 32) | time->s.LowPart;
    tmp = RtlLargeIntegerDivide( tmp, 10000000, NULL );
    tmp -= SECS_1601_to_1980;
    if (tmp > 0xffffffff) return FALSE;
    *res = (DWORD)tmp;
    return TRUE;
}

/******************************************************************************
 *  RtlSecondsSince1970ToTime		[NTDLL.@]
 */
void WINAPI RtlSecondsSince1970ToTime( DWORD time, LARGE_INTEGER *res )
{
    ULONGLONG secs = RtlExtendedIntegerMultiply( time + SECS_1601_TO_1970, 10000000 );
    res->s.LowPart  = (DWORD)secs;
    res->s.HighPart = (DWORD)(secs >> 32);
}

/******************************************************************************
 *  RtlSecondsSince1980ToTime		[NTDLL.@]
 */
void WINAPI RtlSecondsSince1980ToTime( DWORD time, LARGE_INTEGER *res )
{
    ULONGLONG secs = RtlExtendedIntegerMultiply( time + SECS_1601_to_1980, 10000000 );
    res->s.LowPart  = (DWORD)secs;
    res->s.HighPart = (DWORD)(secs >> 32);
}

/******************************************************************************
 * RtlTimeToElapsedTimeFields [NTDLL.@]
 * FIXME: prototype guessed
 */
VOID WINAPI RtlTimeToElapsedTimeFields(
	PLARGE_INTEGER liTime,
	PTIME_FIELDS TimeFields)
{
	FIXME("(%p,%p): stub\n",liTime,TimeFields);
}

/***********************************************************************
 *      NtQuerySystemTime   (NTDLL.@)
 *      ZwQuerySystemTime   (NTDLL.@)
 */
NTSTATUS WINAPI NtQuerySystemTime( PLARGE_INTEGER time )
{
    ULONGLONG secs;
    struct timeval now;

    gettimeofday( &now, 0 );
    secs = RtlExtendedIntegerMultiply( now.tv_sec+SECS_1601_TO_1970, 10000000 ) + now.tv_usec * 10;
    time->s.LowPart  = (DWORD)secs;
    time->s.HighPart = (DWORD)(secs >> 32);
    return STATUS_SUCCESS;
}
