/**************************************************************
 * 
 * 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_unotools.hxx"

#include <unotools/calendarwrapper.hxx>
#include <tools/string.hxx>
#include <tools/debug.hxx>

#ifndef _COMPHELPER_COMPONENTFACTORY_HXX_
#include <comphelper/componentfactory.hxx>
#endif
#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
#include <com/sun/star/i18n/XExtendedCalendar.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>

#define CALENDAR_LIBRARYNAME "i18n"
#define CALENDAR_SERVICENAME "com.sun.star.i18n.LocaleCalendar"


using namespace ::com::sun::star;
using namespace ::com::sun::star::i18n;
using namespace ::com::sun::star::uno;


const double MILLISECONDS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;


CalendarWrapper::CalendarWrapper(
			const Reference< lang::XMultiServiceFactory > & xSF
			)
		:
		xSMgr( xSF ),
		aEpochStart( Date( 1, 1, 1970 ) )
{
	if ( xSMgr.is() )
	{
		try
		{
			xC = Reference< XExtendedCalendar > ( xSMgr->createInstance(
				::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( CALENDAR_SERVICENAME ) ) ),
				uno::UNO_QUERY );
		}
		catch ( Exception& e )
		{
#ifdef DBG_UTIL
			ByteString aMsg( "CalendarWrapper ctor: Exception caught\n" );
			aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
			DBG_ERRORFILE( aMsg.GetBuffer() );
#else
			(void)e;
#endif
		}
	}
	else
	{	// try to get an instance somehow
		DBG_ERRORFILE( "CalendarWrapper: no service manager, trying own" );
		try
		{
			Reference< XInterface > xI = ::comphelper::getComponentInstance(
				::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( LLCF_LIBNAME( CALENDAR_LIBRARYNAME ) ) ),
				::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( CALENDAR_SERVICENAME ) ) );
			if ( xI.is() )
			{
				Any x = xI->queryInterface( ::getCppuType((const Reference< XExtendedCalendar >*)0) );
				x >>= xC;
			}
		}
		catch ( Exception& e )
		{
#ifdef DBG_UTIL
			ByteString aMsg( "getComponentInstance: Exception caught\n" );
			aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
			DBG_ERRORFILE( aMsg.GetBuffer() );
#else
			(void)e;
#endif
		}
	}
}


CalendarWrapper::~CalendarWrapper()
{
}


void CalendarWrapper::loadDefaultCalendar( const ::com::sun::star::lang::Locale& rLocale )
{
	try
	{
		if ( xC.is() )
			xC->loadDefaultCalendar( rLocale );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "loadDefaultCalendar: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
		(void)e;
#endif
	}
}


void CalendarWrapper::loadCalendar( const ::rtl::OUString& rUniqueID, const ::com::sun::star::lang::Locale& rLocale )
{
	try
	{
		if ( xC.is() )
			xC->loadCalendar( rUniqueID, rLocale );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "loadCalendar: Exception caught\nrequested: " );
		aMsg += ByteString( String( rUniqueID ), RTL_TEXTENCODING_UTF8 );
		aMsg += "   Locale: ";
		aMsg += ByteString( String( rLocale.Language ), RTL_TEXTENCODING_UTF8 );
		aMsg += '_';
		aMsg += ByteString( String( rLocale.Country ), RTL_TEXTENCODING_UTF8 );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
		(void)e;
#endif
	}
}


::com::sun::star::i18n::Calendar CalendarWrapper::getLoadedCalendar() const
{
	try
	{
		if ( xC.is() )
			return xC->getLoadedCalendar();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getLoadedCalendar: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return ::com::sun::star::i18n::Calendar();
}


::com::sun::star::uno::Sequence< ::rtl::OUString > CalendarWrapper::getAllCalendars( const ::com::sun::star::lang::Locale& rLocale ) const
{
	try
	{
		if ( xC.is() )
			return xC->getAllCalendars( rLocale );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getAllCalendars: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return ::com::sun::star::uno::Sequence< ::rtl::OUString > (0);
}


::rtl::OUString CalendarWrapper::getUniqueID() const
{
	try
	{
		if ( xC.is() )
			return xC->getUniqueID();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getUniqueID: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return ::rtl::OUString();
}


void CalendarWrapper::setDateTime( double nTimeInDays )
{
	try
	{
		if ( xC.is() )
			xC->setDateTime( nTimeInDays );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "setDateTime: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
}


double CalendarWrapper::getDateTime() const
{
	try
	{
		if ( xC.is() )
			return xC->getDateTime();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getDateTime: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0.0;
}


sal_Int32 CalendarWrapper::getCombinedOffsetInMillis(
        sal_Int16 nParentFieldIndex, sal_Int16 nChildFieldIndex ) const
{
    sal_Int32 nOffset = 0;
    try
    {
        if ( xC.is() )
        {
            nOffset = static_cast<sal_Int32>( xC->getValue( nParentFieldIndex )) * 60000;
            sal_Int16 nSecondMillis = xC->getValue( nChildFieldIndex );
            if (nOffset < 0)
                nOffset -= static_cast<sal_uInt16>( nSecondMillis);
            else
                nOffset += static_cast<sal_uInt16>( nSecondMillis);
        }
    }
    catch ( Exception& e )
    {
#ifdef DBG_UTIL
        ByteString aMsg( "setLocalDateTime: Exception caught\n" );
        aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
        DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
    }
    return nOffset;
}


sal_Int32 CalendarWrapper::getZoneOffsetInMillis() const
{
    return getCombinedOffsetInMillis( CalendarFieldIndex::ZONE_OFFSET,
            CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS);
}


sal_Int32 CalendarWrapper::getDSTOffsetInMillis() const
{
    return getCombinedOffsetInMillis( CalendarFieldIndex::DST_OFFSET,
            CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS);
}


void CalendarWrapper::setLocalDateTime( double nTimeInDays )
{
    try
    {
        if ( xC.is() )
        {
            // First set a nearby value to obtain the timezone and DST offset.
            // This is necessary to let ICU choose the corresponding
            // OlsonTimeZone transitions. Since ICU incorporates also
            // historical data even the timezone may differ for different
            // dates! (Which was the cause for #i76623# when the timezone of a
            // previously set date was used.) Timezone may also include
            // seconds, so use milliseconds field as well.
            xC->setDateTime( nTimeInDays );
            sal_Int32 nZone1 = getZoneOffsetInMillis();
            sal_Int32 nDST1  = getDSTOffsetInMillis();
            double nLoc = nTimeInDays - (double)(nZone1 + nDST1) / MILLISECONDS_PER_DAY;
            xC->setDateTime( nLoc );
            sal_Int32 nZone2 = getZoneOffsetInMillis();
            sal_Int32 nDST2  = getDSTOffsetInMillis();
            // If DSTs differ after calculation, we crossed boundaries. Do it
            // again, this time using the DST corrected initial value for the
            // real local time.
            // See also localtime/gmtime conversion pitfalls at
            // http://www.erack.de/download/timetest.c
            if ( nDST1 != nDST2 )
            {
                nLoc = nTimeInDays - (double)(nZone2 + nDST2) / MILLISECONDS_PER_DAY;
                xC->setDateTime( nLoc );
                // #i17222# If the DST onset rule says to switch from 00:00 to
                // 01:00 and we tried to set onsetDay 00:00 with DST, the
                // result was onsetDay-1 23:00 and no DST, which is not what we
                // want. So once again without DST, resulting in onsetDay
                // 01:00 and DST. Yes, this seems to be weird, but logically
                // correct.
                sal_Int32 nDST3 = getDSTOffsetInMillis();
                if ( nDST2 != nDST3 && !nDST3 )
                {
                    nLoc = nTimeInDays - (double)(nZone2 + nDST3) / MILLISECONDS_PER_DAY;
                    xC->setDateTime( nLoc );
                }
            }
        }
    }
    catch ( Exception& e )
    {
#ifdef DBG_UTIL
        ByteString aMsg( "setLocalDateTime: Exception caught\n" );
        aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
        DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
    }
}


double CalendarWrapper::getLocalDateTime() const
{
    try
    {
        if ( xC.is() )
        {
            double nTimeInDays = xC->getDateTime();
            sal_Int32 nZone = getZoneOffsetInMillis();
            sal_Int32 nDST = getDSTOffsetInMillis();
            nTimeInDays += (double)(nZone + nDST) / MILLISECONDS_PER_DAY;
            return nTimeInDays;
        }
    }
    catch ( Exception& e )
    {
#ifdef DBG_UTIL
        ByteString aMsg( "getLocalDateTime: Exception caught\n" );
        aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
        DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
    }
    return 0.0;
}


void CalendarWrapper::setValue( sal_Int16 nFieldIndex, sal_Int16 nValue )
{
	try
	{
		if ( xC.is() )
			xC->setValue( nFieldIndex, nValue );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "setValue: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
}


sal_Bool CalendarWrapper::isValid() const
{
	try
	{
		if ( xC.is() )
            return xC->isValid();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
        ByteString aMsg( "isValid: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
    return sal_False;
}


sal_Int16 CalendarWrapper::getValue( sal_Int16 nFieldIndex ) const
{
	try
	{
		if ( xC.is() )
			return xC->getValue( nFieldIndex );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getValue: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0;
}


void CalendarWrapper::addValue( sal_Int16 nFieldIndex, sal_Int32 nAmount )
{
	try
	{
		if ( xC.is() )
			xC->addValue( nFieldIndex, nAmount );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "addValue: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
}


sal_Int16 CalendarWrapper::getFirstDayOfWeek() const
{
	try
	{
		if ( xC.is() )
			return xC->getFirstDayOfWeek();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getFirstDayOfWeek: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0;
}


void CalendarWrapper::setFirstDayOfWeek( sal_Int16 nDay )
{
	try
	{
		if ( xC.is() )
			xC->setFirstDayOfWeek( nDay );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "setFirstDayOfWeek: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
}


void CalendarWrapper::setMinimumNumberOfDaysForFirstWeek( sal_Int16 nDays )
{
	try
	{
		if ( xC.is() )
			xC->setMinimumNumberOfDaysForFirstWeek( nDays );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "setMinimumNumberOfDaysForFirstWeek: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
}


sal_Int16 CalendarWrapper::getMinimumNumberOfDaysForFirstWeek() const
{
	try
	{
		if ( xC.is() )
			return xC->getMinimumNumberOfDaysForFirstWeek();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getMinimumNumberOfDaysForFirstWeek: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0;
}


sal_Int16 CalendarWrapper::getNumberOfMonthsInYear() const
{
	try
	{
		if ( xC.is() )
			return xC->getNumberOfMonthsInYear();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getNumberOfMonthsInYear: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0;
}


sal_Int16 CalendarWrapper::getNumberOfDaysInWeek() const
{
	try
	{
		if ( xC.is() )
			return xC->getNumberOfDaysInWeek();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getNumberOfDaysInWeek: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return 0;
}


::com::sun::star::uno::Sequence< ::com::sun::star::i18n::CalendarItem > CalendarWrapper::getMonths() const
{
	try
	{
		if ( xC.is() )
			return xC->getMonths();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getMonths: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return ::com::sun::star::uno::Sequence< ::com::sun::star::i18n::CalendarItem > (0);
}


::com::sun::star::uno::Sequence< ::com::sun::star::i18n::CalendarItem > CalendarWrapper::getDays() const
{
	try
	{
		if ( xC.is() )
			return xC->getDays();
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getDays: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return ::com::sun::star::uno::Sequence< ::com::sun::star::i18n::CalendarItem > (0);
}


String CalendarWrapper::getDisplayName( sal_Int16 nCalendarDisplayIndex, sal_Int16 nIdx, sal_Int16 nNameType ) const
{
	try
	{
		if ( xC.is() )
			return xC->getDisplayName( nCalendarDisplayIndex, nIdx, nNameType );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getDisplayName: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return String();
}


// --- XExtendedCalendar -----------------------------------------------------

String CalendarWrapper::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode ) const
{
	try
	{
		if ( xC.is() )
			return xC->getDisplayString( nCalendarDisplayCode, nNativeNumberMode );
	}
	catch ( Exception& e )
	{
#ifdef DBG_UTIL
		ByteString aMsg( "getDisplayString: Exception caught\n" );
		aMsg += ByteString( String( e.Message ), RTL_TEXTENCODING_UTF8 );
		DBG_ERRORFILE( aMsg.GetBuffer() );
#else
        (void)e;
#endif
	}
	return String();
}

