/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



#include "precompiled_svtools.hxx"

#include "cellvalueconversion.hxx"

/** === begin UNO includes === **/
#include <com/sun/star/util/XNumberFormatter.hpp>
#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/util/Time.hpp>
#include <com/sun/star/util/XNumberFormatTypes.hpp>
#include <com/sun/star/util/NumberFormat.hpp>
/** === end UNO includes === **/

#include <comphelper/componentcontext.hxx>
#include <rtl/math.hxx>
#include <rtl/strbuf.hxx>
#include <tools/date.hxx>
#include <tools/time.hxx>
#include <tools/diagnose_ex.h>
#include <unotools/syslocale.hxx>

#include <boost/shared_ptr.hpp>
#include <hash_map>

//......................................................................................................................
namespace svt
{
//......................................................................................................................

    /** === begin UNO using === **/
    using ::com::sun::star::uno::Any;
    using ::com::sun::star::util::XNumberFormatter;
    using ::com::sun::star::uno::UNO_QUERY_THROW;
    using ::com::sun::star::util::XNumberFormatsSupplier;
    using ::com::sun::star::beans::XPropertySet;
    using ::com::sun::star::uno::UNO_SET_THROW;
    using ::com::sun::star::uno::Exception;
    using ::com::sun::star::util::DateTime;
    using ::com::sun::star::uno::TypeClass;
    using ::com::sun::star::util::XNumberFormatTypes;
    using ::com::sun::star::uno::Reference;
    using ::com::sun::star::uno::Sequence;
    using ::com::sun::star::uno::makeAny;
    using ::com::sun::star::uno::Type;
    using ::com::sun::star::uno::TypeClass_BYTE;
    using ::com::sun::star::uno::TypeClass_SHORT;
    using ::com::sun::star::uno::TypeClass_UNSIGNED_SHORT;
    using ::com::sun::star::uno::TypeClass_LONG;
    using ::com::sun::star::uno::TypeClass_UNSIGNED_LONG;
    using ::com::sun::star::uno::TypeClass_HYPER;
    /** === end UNO using === **/
    namespace NumberFormat = ::com::sun::star::util::NumberFormat;

    typedef ::com::sun::star::util::Time UnoTime;
    typedef ::com::sun::star::util::Date UnoDate;

    //==================================================================================================================
    //= helper
    //==================================================================================================================
    namespace
    {
        //--------------------------------------------------------------------------------------------------------------
        double lcl_convertDateToDays( long const i_day, long const i_month, long const i_year )
        {
            long const nNullDateDays = ::Date::DateToDays( 1, 1, 1900 );
            long const nValueDateDays = ::Date::DateToDays( i_day, i_month, i_year );

            return nValueDateDays - nNullDateDays;
        }

        //--------------------------------------------------------------------------------------------------------------
        double lcl_convertTimeToDays( long const i_hours, long const i_minutes, long const i_seconds, long const i_100thSeconds )
        {
            return Time( i_hours, i_minutes, i_seconds, i_100thSeconds ).GetTimeInDays();
        }
    }

    //==================================================================================================================
    //= IValueNormalization
    //==================================================================================================================
    class SAL_NO_VTABLE IValueNormalization
    {
    public:
        virtual ~IValueNormalization() { }

        /** converts the given <code>Any</code> into a <code>double</code> value to be fed into a number formatter
        */
        virtual double convertToDouble( Any const & i_value ) const = 0;
 
        /** returns the format key to be used for formatting values
        */
        virtual ::sal_Int32 getFormatKey() const = 0;
    };

    typedef ::boost::shared_ptr< IValueNormalization >                                      PValueNormalization;
    typedef ::std::hash_map< ::rtl::OUString, PValueNormalization, ::rtl::OUStringHash >    NormalizerCache;

    //==================================================================================================================
    //= CellValueConversion_Data
    //==================================================================================================================
    struct CellValueConversion_Data
    {
        ::comphelper::ComponentContext const    aContext;
        Reference< XNumberFormatter >           xNumberFormatter;
        bool                                    bAttemptedFormatterCreation;
        NormalizerCache                         aNormalizers;

        CellValueConversion_Data( ::comphelper::ComponentContext const & i_context )
            :aContext( i_context )
            ,xNumberFormatter()
            ,bAttemptedFormatterCreation( false )
            ,aNormalizers()
        {
        }
    };

    //==================================================================================================================
    //= StandardFormatNormalizer
    //==================================================================================================================
    class StandardFormatNormalizer : public IValueNormalization
    {
    protected:
        StandardFormatNormalizer( Reference< XNumberFormatter > const & i_formatter, ::sal_Int32 const i_numberFormatType )
            :m_nFormatKey( 0 )
        {
            try
            {
                ENSURE_OR_THROW( i_formatter.is(), "StandardFormatNormalizer: no formatter!" );
                Reference< XNumberFormatsSupplier > const xSupplier( i_formatter->getNumberFormatsSupplier(), UNO_SET_THROW );
                Reference< XNumberFormatTypes > const xTypes( xSupplier->getNumberFormats(), UNO_QUERY_THROW );
                m_nFormatKey = xTypes->getStandardFormat( i_numberFormatType, SvtSysLocale().GetLocale() );
            }
            catch( const Exception& )
            {
            	DBG_UNHANDLED_EXCEPTION();
            }
        }

        virtual ::sal_Int32 getFormatKey() const
        {
            return m_nFormatKey;
        }

    private:
        ::sal_Int32 m_nFormatKey;
    };

    //==================================================================================================================
    //= DoubleNormalization
    //==================================================================================================================
    class DoubleNormalization : public StandardFormatNormalizer
    {
    public:
        DoubleNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::NUMBER )
        {
        }

        virtual double convertToDouble( Any const & i_value ) const
        {
            double returnValue(0);
            ::rtl::math::setNan( &returnValue );
            OSL_VERIFY( i_value >>= returnValue );
            return returnValue;
        }

        virtual ~DoubleNormalization() { }
    };

    //==================================================================================================================
    //= IntegerNormalization
    //==================================================================================================================
    class IntegerNormalization : public StandardFormatNormalizer
    {
    public:
        IntegerNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::NUMBER )
        {
        }

        virtual ~IntegerNormalization() {}

        virtual double convertToDouble( Any const & i_value ) const
        {
            sal_Int64 value( 0 );
            OSL_VERIFY( i_value >>= value );
            return value;
        }
    };

    //==================================================================================================================
    //= BooleanNormalization
    //==================================================================================================================
    class BooleanNormalization : public StandardFormatNormalizer
    {
    public:
        BooleanNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::LOGICAL )
        {
        }

        virtual ~BooleanNormalization() {}

        virtual double convertToDouble( Any const & i_value ) const
        {
            bool value( false );
            OSL_VERIFY( i_value >>= value );
            return value ? 1 : 0;
        }
    };

    //==================================================================================================================
    //= DateTimeNormalization
    //==================================================================================================================
    class DateTimeNormalization : public StandardFormatNormalizer
    {
    public:
        DateTimeNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::DATETIME )
        {
        }

        virtual ~DateTimeNormalization() {}

        virtual double convertToDouble( Any const & i_value ) const
        {
            double returnValue(0);
            ::rtl::math::setNan( &returnValue );

            // extract actual UNO value
            DateTime aDateTimeValue;
            ENSURE_OR_RETURN( i_value >>= aDateTimeValue, "allowed for DateTime values only", returnValue );

            // date part
            returnValue = lcl_convertDateToDays( aDateTimeValue.Day, aDateTimeValue.Month, aDateTimeValue.Year );

            // time part
            returnValue += lcl_convertTimeToDays(
                aDateTimeValue.Hours, aDateTimeValue.Minutes, aDateTimeValue.Seconds, aDateTimeValue.HundredthSeconds );

            // done
            return returnValue;
        }
    };

    //==================================================================================================================
    //= DateNormalization
    //==================================================================================================================
    class DateNormalization : public StandardFormatNormalizer
    {
    public:
        DateNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::DATE )
        {
        }

        virtual ~DateNormalization() {}

        virtual double convertToDouble( Any const & i_value ) const
        {
            double returnValue(0);
            ::rtl::math::setNan( &returnValue );

            // extract
            UnoDate aDateValue;
            ENSURE_OR_RETURN( i_value >>= aDateValue, "allowed for Date values only", returnValue );

            // convert
            returnValue = lcl_convertDateToDays( aDateValue.Day, aDateValue.Month, aDateValue.Year );

            // done
            return returnValue;
        }
    };

    //==================================================================================================================
    //= TimeNormalization
    //==================================================================================================================
    class TimeNormalization : public StandardFormatNormalizer
    {
    public:
        TimeNormalization( Reference< XNumberFormatter > const & i_formatter )
            :StandardFormatNormalizer( i_formatter, NumberFormat::TIME )
        {
        }

        virtual ~TimeNormalization() {}

        virtual double convertToDouble( Any const & i_value ) const
        {
            double returnValue(0);
            ::rtl::math::setNan( &returnValue );

            // extract
            UnoTime aTimeValue;
            ENSURE_OR_RETURN( i_value >>= aTimeValue, "allowed for Time values only", returnValue );

            // convert
            returnValue += lcl_convertTimeToDays(
                aTimeValue.Hours, aTimeValue.Minutes, aTimeValue.Seconds, aTimeValue.HundredthSeconds );

            // done
            return returnValue;
        }
    };

    //==================================================================================================================
    //= operations
    //==================================================================================================================
    namespace
    {
        //--------------------------------------------------------------------------------------------------------------
        bool lcl_ensureNumberFormatter( CellValueConversion_Data & io_data )
        {
            if ( io_data.bAttemptedFormatterCreation )
                return io_data.xNumberFormatter.is();
            io_data.bAttemptedFormatterCreation = true;

            try
            {
                // a number formatter
                Reference< XNumberFormatter > const xFormatter(
                    io_data.aContext.createComponent( "com.sun.star.util.NumberFormatter" ), UNO_QUERY_THROW );

                // a supplier of number formats
                Sequence< Any > aInitArgs(1);
                aInitArgs[0] <<= SvtSysLocale().GetLocale();

                Reference< XNumberFormatsSupplier > const xSupplier(
                    io_data.aContext.createComponentWithArguments( "com.sun.star.util.NumberFormatsSupplier", aInitArgs ),
                    UNO_QUERY_THROW
                );

                // ensure a NullDate we will assume later on
                UnoDate const aNullDate( 1, 1, 1900 );
                Reference< XPropertySet > const xFormatSettings( xSupplier->getNumberFormatSettings(), UNO_SET_THROW );
                xFormatSettings->setPropertyValue( ::rtl::OUString::createFromAscii( "NullDate" ), makeAny( aNullDate ) );

                // knit
                xFormatter->attachNumberFormatsSupplier( xSupplier );

                // done
                io_data.xNumberFormatter = xFormatter;
            }
            catch( const Exception& )
            {
            	DBG_UNHANDLED_EXCEPTION();
            }

            return io_data.xNumberFormatter.is();
        }

        //--------------------------------------------------------------------------------------------------------------
        bool lcl_getValueNormalizer( CellValueConversion_Data & io_data, Type const & i_valueType,
            PValueNormalization & o_formatter )
        {
            NormalizerCache::const_iterator pos = io_data.aNormalizers.find( i_valueType.getTypeName() );
            if ( pos == io_data.aNormalizers.end() )
            {
                // never encountered this type before
                o_formatter.reset();

                ::rtl::OUString const sTypeName( i_valueType.getTypeName() );
                TypeClass const eTypeClass = i_valueType.getTypeClass();

                if ( sTypeName.equals( ::cppu::UnoType< DateTime >::get().getTypeName() ) )
                {
                    o_formatter.reset( new DateTimeNormalization( io_data.xNumberFormatter ) );
                }
                else if ( sTypeName.equals( ::cppu::UnoType< UnoDate >::get().getTypeName() ) )
                {
                    o_formatter.reset( new DateNormalization( io_data.xNumberFormatter ) );
                }
                else if ( sTypeName.equals( ::cppu::UnoType< UnoTime >::get().getTypeName() ) )
                {
                    o_formatter.reset( new TimeNormalization( io_data.xNumberFormatter ) );
                }
                else if ( sTypeName.equals( ::cppu::UnoType< ::sal_Bool >::get().getTypeName() ) )
                {
                    o_formatter.reset( new BooleanNormalization( io_data.xNumberFormatter ) );
                }
                else if (   sTypeName.equals( ::cppu::UnoType< double >::get().getTypeName() )
                        ||  sTypeName.equals( ::cppu::UnoType< float >::get().getTypeName() )
                        )
                {
                    o_formatter.reset( new DoubleNormalization( io_data.xNumberFormatter ) );
                }
                else if (   ( eTypeClass == TypeClass_BYTE )
                        ||  ( eTypeClass == TypeClass_SHORT )
                        ||  ( eTypeClass == TypeClass_UNSIGNED_SHORT )
                        ||  ( eTypeClass == TypeClass_LONG )
                        ||  ( eTypeClass == TypeClass_UNSIGNED_LONG )
                        ||  ( eTypeClass == TypeClass_HYPER )
                        )
                {
                    o_formatter.reset( new IntegerNormalization( io_data.xNumberFormatter ) );
                }
                else
                {
#if OSL_DEBUG_LEVEL > 0
                    ::rtl::OStringBuffer message( "lcl_getValueNormalizer: unsupported type '" );
                    message.append( ::rtl::OUStringToOString( sTypeName, RTL_TEXTENCODING_ASCII_US ) );
                    message.append( "'!" );
                    OSL_ENSURE( false, message.makeStringAndClear() );
#endif
                }
                io_data.aNormalizers[ sTypeName ] = o_formatter;
            }
            else
                o_formatter = pos->second;

            return !!o_formatter;
        }
    }

    //==================================================================================================================
    //= CellValueConversion
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    CellValueConversion::CellValueConversion( ::comphelper::ComponentContext const & i_context )
        :m_pData( new CellValueConversion_Data( i_context ) )
    {
    }

    //------------------------------------------------------------------------------------------------------------------
    CellValueConversion::~CellValueConversion()
    {
    }

    //------------------------------------------------------------------------------------------------------------------
    ::rtl::OUString CellValueConversion::convertToString( const Any& i_value )
    {
        ::rtl::OUString sStringValue;
        if ( !i_value.hasValue() )
            return sStringValue;

        if ( ! ( i_value >>= sStringValue ) )
        {
            if ( lcl_ensureNumberFormatter( *m_pData ) )
            {
                PValueNormalization pNormalizer;
                if ( lcl_getValueNormalizer( *m_pData, i_value.getValueType(), pNormalizer ) )
                {
                    try
                    {
                        double const formatterCompliantValue = pNormalizer->convertToDouble( i_value );
                        sal_Int32 const formatKey = pNormalizer->getFormatKey();
                        sStringValue = m_pData->xNumberFormatter->convertNumberToString(
                            formatKey, formatterCompliantValue );
                    }
                    catch( const Exception& )
                    {
                    	DBG_UNHANDLED_EXCEPTION();
                    }
                }
            }
        }

        return sStringValue;
    }

//......................................................................................................................
} // namespace svt
//......................................................................................................................
