/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_tools.hxx"

#define _TOOLS_TIME_CXX

#if defined( OS2 )
#define INCL_DOSMISC
#define INCL_DOSDATETIME
#include <svpm.h>
#elif defined( WNT )
#ifdef _MSC_VER
#pragma warning (push,1)
#endif
#include <tools/svwin.h>
#ifdef _MSC_VER
#pragma warning (pop)
#endif
#elif defined UNX
#include <unistd.h>
#include <limits.h>
#include <math.h>
#include <sys/time.h>
#endif

#include <time.h>
#include <tools/time.hxx>

#if defined(SOLARIS) && defined(__GNUC__)
extern long altzone;
#endif

// =======================================================================

static sal_Int32 TimeToSec100( const Time& rTime )
{
	short  nSign   = (rTime.GetTime() >= 0) ? +1 : -1;
	sal_Int32   nHour   = rTime.GetHour();
	sal_Int32   nMin    = rTime.GetMin();
	sal_Int32   nSec    = rTime.GetSec();
	sal_Int32   n100Sec = rTime.Get100Sec();

//	Wegen Interal Compiler Error bei MSC, etwas komplizierter
//	return (n100Sec + (nSec*100) + (nMin*60*100) + (nHour*60*60*100) * nSign);

	sal_Int32 nRet = n100Sec;
	nRet	 += nSec*100;
	nRet	 += nMin*60*100;
	nRet	 += nHour*60*60*100;

	return (nRet * nSign);
}

// -----------------------------------------------------------------------

static Time Sec100ToTime( sal_Int32 nSec100 )
{
	short nSign;
	if ( nSec100 < 0 )
	{
		nSec100 *= -1;
		nSign = -1;
	}
	else
		nSign = 1;

	Time aTime( 0, 0, 0, nSec100 );
	aTime.SetTime( aTime.GetTime() * nSign );
	return aTime;
}

// =======================================================================

Time::Time()
{
#if defined( OS2 )
	DATETIME aDateTime;
	DosGetDateTime( &aDateTime );

	// Zeit zusammenbauen
	nTime = (((sal_Int32)aDateTime.hours)*1000000) +
			(((sal_Int32)aDateTime.minutes)*10000) +
			(((sal_Int32)aDateTime.seconds)*100) +
			((sal_Int32)aDateTime.hundredths);
#elif defined( WNT )
	SYSTEMTIME aDateTime;
	GetLocalTime( &aDateTime );

	// Zeit zusammenbauen
	nTime = (((sal_Int32)aDateTime.wHour)*1000000) +
			(((sal_Int32)aDateTime.wMinute)*10000) +
			(((sal_Int32)aDateTime.wSecond)*100) +
			((sal_Int32)aDateTime.wMilliseconds/10);
#else
	time_t	   nTmpTime;
	struct tm aTime;

	// Zeit ermitteln
	nTmpTime = time( 0 );

	// Zeit zusammenbauen
	if ( localtime_r( &nTmpTime, &aTime ) )
	{
		nTime = (((sal_Int32)aTime.tm_hour)*1000000) +
				(((sal_Int32)aTime.tm_min)*10000) +
				(((sal_Int32)aTime.tm_sec)*100);
	}
	else
		nTime = 0;
#endif
}

// -----------------------------------------------------------------------

Time::Time( const Time& rTime )
{
	nTime = rTime.nTime;
}

// -----------------------------------------------------------------------

Time::Time( sal_uIntPtr nHour, sal_uIntPtr nMin, sal_uIntPtr nSec, sal_uIntPtr n100Sec )
{
	// Zeit normalisieren
	nSec	+= n100Sec / 100;
	n100Sec  = n100Sec % 100;
	nMin	+= nSec / 60;
	nSec	 = nSec % 60;
	nHour	+= nMin / 60;
	nMin	 = nMin % 60;

	// Zeit zusammenbauen
	nTime = (sal_Int32)(n100Sec + (nSec*100) + (nMin*10000) + (nHour*1000000));
}

// -----------------------------------------------------------------------

void Time::SetHour( sal_uInt16 nNewHour )
{
	short  nSign	  = (nTime >= 0) ? +1 : -1;
	sal_Int32   nMin 	  = GetMin();
	sal_Int32   nSec 	  = GetSec();
	sal_Int32   n100Sec	  = Get100Sec();

	nTime = (n100Sec + (nSec*100) + (nMin*10000) +
			(((sal_Int32)nNewHour)*1000000)) * nSign;
}

// -----------------------------------------------------------------------

void Time::SetMin( sal_uInt16 nNewMin )
{
	short  nSign	  = (nTime >= 0) ? +1 : -1;
	sal_Int32   nHour	  = GetHour();
	sal_Int32   nSec 	  = GetSec();
	sal_Int32   n100Sec	  = Get100Sec();

	// kein Ueberlauf
	nNewMin = nNewMin % 60;

	nTime = (n100Sec + (nSec*100) + (((sal_Int32)nNewMin)*10000) +
			(nHour*1000000)) * nSign;
}

// -----------------------------------------------------------------------

void Time::SetSec( sal_uInt16 nNewSec )
{
	short       nSign	  = (nTime >= 0) ? +1 : -1;
	sal_Int32   nHour	  = GetHour();
	sal_Int32   nMin 	  = GetMin();
	sal_Int32   n100Sec	  = Get100Sec();

	// kein Ueberlauf
	nNewSec = nNewSec % 60;

	nTime = (n100Sec + (((sal_Int32)nNewSec)*100) + (nMin*10000) +
			(nHour*1000000)) * nSign;
}

// -----------------------------------------------------------------------

void Time::Set100Sec( sal_uInt16 nNew100Sec )
{
	short       nSign	  = (nTime >= 0) ? +1 : -1;
	sal_Int32   nHour	  = GetHour();
	sal_Int32   nMin 	  = GetMin();
	sal_Int32   nSec 	  = GetSec();

	// kein Ueberlauf
	nNew100Sec = nNew100Sec % 100;

	nTime = (((sal_Int32)nNew100Sec) + (nSec*100) + (nMin*10000) +
			(nHour*1000000)) * nSign;
}

// -----------------------------------------------------------------------

sal_Int32 Time::GetMSFromTime() const
{
	short       nSign	  = (nTime >= 0) ? +1 : -1;
	sal_Int32   nHour	  = GetHour();
	sal_Int32   nMin 	  = GetMin();
	sal_Int32   nSec 	  = GetSec();
	sal_Int32   n100Sec	  = Get100Sec();

	return (((nHour*3600000)+(nMin*60000)+(nSec*1000)+(n100Sec*10))*nSign);
}

// -----------------------------------------------------------------------

void Time::MakeTimeFromMS( sal_Int32 nMS )
{
	short nSign;
	if ( nMS < 0 )
	{
		nMS *= -1;
		nSign = -1;
	}
	else
		nSign = 1;

	Time aTime( 0, 0, 0, nMS/10 );
	SetTime( aTime.GetTime() * nSign );
}

// -----------------------------------------------------------------------

double Time::GetTimeInDays() const
{
	short  nSign	  = (nTime >= 0) ? +1 : -1;
    double nHour      = GetHour();
    double nMin       = GetMin();
    double nSec       = GetSec();
    double n100Sec    = Get100Sec();

    return (nHour+(nMin/60)+(nSec/(60*60))+(n100Sec/(60*60*100))) / 24 * nSign;
}

// -----------------------------------------------------------------------

Time& Time::operator =( const Time& rTime )
{
	nTime = rTime.nTime;
	return *this;
}

// -----------------------------------------------------------------------

Time& Time::operator +=( const Time& rTime )
{
	nTime = Sec100ToTime( TimeToSec100( *this ) +
						  TimeToSec100( rTime ) ).GetTime();
	return *this;
}

// -----------------------------------------------------------------------

Time& Time::operator -=( const Time& rTime )
{
	nTime = Sec100ToTime( TimeToSec100( *this ) -
						  TimeToSec100( rTime ) ).GetTime();
	return *this;
}

// -----------------------------------------------------------------------

Time operator +( const Time& rTime1, const Time& rTime2 )
{
	return Sec100ToTime( TimeToSec100( rTime1 ) +
						 TimeToSec100( rTime2 ) );
}

// -----------------------------------------------------------------------

Time operator -( const Time& rTime1, const Time& rTime2 )
{
	return Sec100ToTime( TimeToSec100( rTime1 ) -
						 TimeToSec100( rTime2 ) );
}

// -----------------------------------------------------------------------

sal_Bool Time::IsEqualIgnore100Sec( const Time& rTime ) const
{
    sal_Int32 n1 = (nTime < 0 ? -Get100Sec() : Get100Sec() );
    sal_Int32 n2 = (rTime.nTime < 0 ? -rTime.Get100Sec() : rTime.Get100Sec() );
    return (nTime - n1) == (rTime.nTime - n2);
}

// -----------------------------------------------------------------------

Time Time::GetUTCOffset()
{
#if defined( OS2 )
#undef timezone
	DATETIME aDateTime;
	DosGetDateTime( &aDateTime );

	// Zeit zusammenbauen
	if ( aDateTime.timezone != -1  )
	{
		short nTempTime = (short)Abs( aDateTime.timezone );
		Time aTime( 0, (sal_uInt16)nTempTime );
		if ( aDateTime.timezone > 0 )
			aTime = -aTime;
		return aTime;
	}
	else
		return Time( 0 );
#elif defined( WNT )
	TIME_ZONE_INFORMATION	aTimeZone;
	aTimeZone.Bias = 0;
	DWORD nTimeZoneRet = GetTimeZoneInformation( &aTimeZone );
	sal_Int32 nTempTime = aTimeZone.Bias;
	if ( nTimeZoneRet == TIME_ZONE_ID_STANDARD )
		nTempTime += aTimeZone.StandardBias;
	else if ( nTimeZoneRet == TIME_ZONE_ID_DAYLIGHT )
		nTempTime += aTimeZone.DaylightBias;
	Time aTime( 0, (sal_uInt16)Abs( nTempTime ) );
	if ( nTempTime > 0 )
		aTime = -aTime;
	return aTime;
#else
	static sal_uIntPtr	nCacheTicks = 0;
	static sal_Int32 	nCacheSecOffset = -1;
	sal_uIntPtr			nTicks = Time::GetSystemTicks();
	time_t			nTime;
	tm 			    aTM;
	sal_Int32			nLocalTime;
	sal_Int32			nUTC;
	short			nTempTime;

	// Evt. Wert neu ermitteln
	if ( (nCacheSecOffset == -1)			||
		 ((nTicks - nCacheTicks) > 360000)	||
		 ( nTicks < nCacheTicks ) // handle overflow
		 )
	{
		nTime = time( 0 );
		localtime_r( &nTime, &aTM );
		nLocalTime = mktime( &aTM );
#if defined( SOLARIS )
		// Solaris gmtime_r() seems not to handle daylight saving time
		// flags correctly
		nUTC = nLocalTime + ( aTM.tm_isdst == 0 ? timezone : altzone );
#elif defined( LINUX )
		// Linux mktime() seems not to handle tm_isdst correctly
		nUTC = nLocalTime - aTM.tm_gmtoff;
#else
 		gmtime_r( &nTime, &aTM );
 		nUTC = mktime( &aTM );
#endif
 		nCacheTicks = nTicks;
 		nCacheSecOffset = (nLocalTime-nUTC) / 60;
	}

	nTempTime = (short)Abs( nCacheSecOffset );
	Time aTime( 0, (sal_uInt16)nTempTime );
	if ( nCacheSecOffset < 0 )
		aTime = -aTime;
	return aTime;
#endif
}


// -----------------------------------------------------------------------

sal_uIntPtr Time::GetSystemTicks()
{
#if defined WNT
	return (sal_uIntPtr)GetTickCount();
#elif defined( OS2 )
	sal_uIntPtr nClock;
	DosQuerySysInfo( QSV_MS_COUNT, QSV_MS_COUNT, &nClock, sizeof( nClock ) );
	return (sal_uIntPtr)nClock;
#else
	timeval tv;
	gettimeofday (&tv, 0);

	double fTicks = tv.tv_sec;
	fTicks *= 1000;
	fTicks += ((tv.tv_usec + 500) / 1000);

	fTicks = fmod (fTicks, double(ULONG_MAX));
	return sal_uIntPtr(fTicks);
#endif
}

// -----------------------------------------------------------------------

sal_uIntPtr Time::GetProcessTicks()
{
#if defined WNT
	return (sal_uIntPtr)GetTickCount();
#elif defined( OS2 )
	sal_uIntPtr nClock;
	DosQuerySysInfo( QSV_MS_COUNT, QSV_MS_COUNT, &nClock, sizeof( nClock ) );
	return (sal_uIntPtr)nClock;
#else
	static sal_uIntPtr	nImplTicksPerSecond = 0;
	static double	dImplTicksPerSecond;
	static double	dImplTicksULONGMAX;
	sal_uIntPtr			nTicks = (sal_uIntPtr)clock();

	if ( !nImplTicksPerSecond )
	{
		nImplTicksPerSecond = CLOCKS_PER_SEC;
		dImplTicksPerSecond = nImplTicksPerSecond;
		dImplTicksULONGMAX	= (double)(sal_uIntPtr)ULONG_MAX;
	}

	double fTicks = nTicks;
	fTicks *= 1000;
	fTicks /= dImplTicksPerSecond;
	fTicks = fmod (fTicks, dImplTicksULONGMAX);
	return (sal_uIntPtr)fTicks;
#endif
}
