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


#include "connectivity/dbconversion.hxx"
#include <connectivity/dbtools.hxx>
#include <com/sun/star/script/XTypeConverter.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/util/NumberFormat.hpp>
#include <com/sun/star/util/XNumberFormatTypes.hpp>
#include <com/sun/star/sdb/XColumnUpdate.hpp>
#include <com/sun/star/sdb/XColumn.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <comphelper/extract.hxx>
#include "TConnection.hxx"
#include "diagnose_ex.h"
#include <comphelper/numbers.hxx>
#include <rtl/ustrbuf.hxx>
#include <tools/diagnose_ex.h>


using namespace ::connectivity;
using namespace ::comphelper;
using namespace ::com::sun::star::script;
using namespace ::com::sun::star::sdb;
using namespace ::com::sun::star::sdbc;
using namespace ::dbtools;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::util;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::util;
using namespace ::com::sun::star::beans;
// -----------------------------------------------------------------------------
::rtl::OUString DBTypeConversion::toSQLString(sal_Int32 eType, const Any& _rVal, sal_Bool bQuote,
											  const Reference< XTypeConverter >&  _rxTypeConverter)
{
	::rtl::OUStringBuffer aRet;
	if (_rVal.hasValue())
	{
		try
		{
			switch (eType)
			{
				case DataType::INTEGER:
				case DataType::BIT:
				case DataType::BOOLEAN:
				case DataType::TINYINT:
				case DataType::SMALLINT:
					if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_BOOLEAN)
					{
						if (::cppu::any2bool(_rVal))
							aRet.appendAscii("1");
						else
							aRet.appendAscii("0");
					}
					else
                    {
                        ::rtl::OUString sTemp;
					    _rxTypeConverter->convertToSimpleType(_rVal, TypeClass_STRING) >>= sTemp;
                        aRet.append(sTemp);
                    }
					break;
				case DataType::CHAR:
				case DataType::VARCHAR:
                case DataType::LONGVARCHAR:
					if (bQuote)
						aRet.appendAscii("'");
					{
						::rtl::OUString aTemp;
						_rxTypeConverter->convertToSimpleType(_rVal, TypeClass_STRING) >>= aTemp;
						sal_Int32 nIndex = (sal_Int32)-1;
						const ::rtl::OUString sQuot(RTL_CONSTASCII_USTRINGPARAM("\'"));
						const ::rtl::OUString sQuotToReplace(RTL_CONSTASCII_USTRINGPARAM("\'\'"));
						do
						{
							nIndex += 2;
							nIndex = aTemp.indexOf(sQuot,nIndex);
							if(nIndex != -1)
								aTemp = aTemp.replaceAt(nIndex,sQuot.getLength(),sQuotToReplace);
						} while (nIndex != -1);

						aRet.append(aTemp);
					}
					if (bQuote)
						aRet.appendAscii("'");
					break;
				case DataType::REAL:
				case DataType::DOUBLE:
				case DataType::DECIMAL:
				case DataType::NUMERIC:
				case DataType::BIGINT:
                default:
                    {
                        ::rtl::OUString sTemp;
					    _rxTypeConverter->convertToSimpleType(_rVal, TypeClass_STRING) >>= sTemp;
                        aRet.append(sTemp);
                    }
					break;
				case DataType::TIMESTAMP:
				{
					DateTime aDateTime;
                    bool bOk = false;
                    if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_DOUBLE)
                    {
                        double nValue = 0.0;
                       _rVal >>= nValue;
                       aDateTime = DBTypeConversion::toDateTime(nValue);
                       bOk = true;
                    }
                    else if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_STRING)
                    {
                        ::rtl::OUString sValue;
                       _rVal >>= sValue;
                       aDateTime = DBTypeConversion::toDateTime(sValue);
                       bOk = true;
                    }
                    else
                        bOk = _rVal >>= aDateTime;

                    OSL_VERIFY_RES( bOk, "DBTypeConversion::toSQLString: _rVal is not datetime!");
					// check if this is really a timestamp or only a date
					if ( bOk )
					{
						if (bQuote) 
                            aRet.appendAscii("{TS '");
						aRet.append(DBTypeConversion::toDateTimeString(aDateTime));
						if (bQuote) 
                            aRet.appendAscii("'}");
						break;
					}
					break;
				}
				case DataType::DATE:
				{
					Date aDate;
                    bool bOk = false;
                    if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_DOUBLE)
                    {
                        double nValue = 0.0;
                       _rVal >>= nValue;
                       aDate = DBTypeConversion::toDate(nValue);
                       bOk = true;
                    }
                    else if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_STRING)
                    {
                        ::rtl::OUString sValue;
                       _rVal >>= sValue;
                       aDate = DBTypeConversion::toDate(sValue);
                       bOk = true;
                    }
                    else
                        bOk = _rVal >>= aDate;
                    OSL_VERIFY_RES( bOk, "DBTypeConversion::toSQLString: _rVal is not date!");
					if (bQuote) 
                        aRet.appendAscii("{D '");
					aRet.append(DBTypeConversion::toDateString(aDate));
					if (bQuote) 
                        aRet.appendAscii("'}");
				}	break;
				case DataType::TIME:
				{
					Time aTime;
                    bool bOk = false;
                    if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_DOUBLE)
                    {
                        double nValue = 0.0;
                       _rVal >>= nValue;
                       aTime = DBTypeConversion::toTime(nValue);
                       bOk = true;
                    }
                    else if (_rVal.getValueType().getTypeClass() == ::com::sun::star::uno::TypeClass_STRING)
                    {
                        ::rtl::OUString sValue;
                       _rVal >>= sValue;
                       aTime = DBTypeConversion::toTime(sValue);
                       bOk = true;
                    }
                    else
                        bOk = _rVal >>= aTime;
                    OSL_VERIFY_RES( bOk,"DBTypeConversion::toSQLString: _rVal is not time!");
					if (bQuote) 
                        aRet.appendAscii("{T '");
					aRet.append(DBTypeConversion::toTimeString(aTime));
					if (bQuote) 
                        aRet.appendAscii("'}");
				} break;
			}
		}
		catch ( const Exception&  )
		{
			OSL_ENSURE(0,"TypeConversion Error");
		}
	}
	else
		aRet.appendAscii(" NULL ");
	return aRet.makeStringAndClear();
}
// -----------------------------------------------------------------------------
Date DBTypeConversion::getNULLDate(const Reference< XNumberFormatsSupplier > &xSupplier)
{
	OSL_ENSURE(xSupplier.is(), "getNULLDate : the formatter doesn't implement a supplier !");
	if (xSupplier.is())
	{
		try
		{
			// get the null date
			Date aDate;
			xSupplier->getNumberFormatSettings()->getPropertyValue(::rtl::OUString::createFromAscii("NullDate")) >>= aDate;
			return aDate;
		}
		catch ( const Exception&  )
		{
		}
	}

	return getStandardDate();
}
// -----------------------------------------------------------------------------
void DBTypeConversion::setValue(const Reference<XColumnUpdate>& xVariant,
								const Reference<XNumberFormatter>& xFormatter,
								const Date& rNullDate,
								const ::rtl::OUString& rString,
								sal_Int32 nKey,
								sal_Int16 nFieldType,
								sal_Int16 nKeyType) throw(::com::sun::star::lang::IllegalArgumentException)
{
	double fValue = 0;
	if (rString.getLength())
	{
			// Muss der String formatiert werden?
		sal_Int16 nTypeClass = nKeyType & ~NumberFormat::DEFINED;
		sal_Bool bTextFormat = nTypeClass == NumberFormat::TEXT;
		sal_Int32 nKeyToUse  = bTextFormat ? 0 : nKey;
		sal_Int16 nRealUsedTypeClass = nTypeClass;
			// bei einem Text-Format muessen wir dem Formatter etwas mehr Freiheiten einraeumen, sonst
			// wirft convertStringToNumber eine NotNumericException
		try
		{
			fValue = xFormatter->convertStringToNumber(nKeyToUse, rString);
			sal_Int32 nRealUsedKey = xFormatter->detectNumberFormat(0, rString);
			if (nRealUsedKey != nKeyToUse)
				nRealUsedTypeClass = getNumberFormatType(xFormatter, nRealUsedKey) & ~NumberFormat::DEFINED;

			// und noch eine Sonderbehandlung, diesmal fuer Prozent-Formate
			if ((NumberFormat::NUMBER == nRealUsedTypeClass) && (NumberFormat::PERCENT == nTypeClass))
			{	// die Formatierung soll eigentlich als Prozent erfolgen, aber der String stellt nur eine
				// einfache Nummer dar -> anpassen
				::rtl::OUString sExpanded(rString);
				static ::rtl::OUString s_sPercentSymbol = ::rtl::OUString::createFromAscii("%");
					// need a method to add a sal_Unicode to a string, 'til then we use a static string
				sExpanded += s_sPercentSymbol;
				fValue = xFormatter->convertStringToNumber(nKeyToUse, sExpanded);
			}

			switch (nRealUsedTypeClass)
			{
				case NumberFormat::DATE:
				case NumberFormat::DATETIME:
				case NumberFormat::TIME:
					DBTypeConversion::setValue(xVariant,rNullDate,fValue,nRealUsedTypeClass);
					//	xVariant->updateDouble(toStandardDbDate(rNullDate, fValue));
					break;
				case NumberFormat::CURRENCY:
				case NumberFormat::NUMBER:
				case NumberFormat::SCIENTIFIC:
				case NumberFormat::FRACTION:
				case NumberFormat::PERCENT:
					xVariant->updateDouble(fValue);
					break;
				default:
					xVariant->updateString(rString);
			}
		}
		catch(const Exception& )
		{
			xVariant->updateString(rString);
		}
	}
	else
	{
		switch (nFieldType)
		{
			case ::com::sun::star::sdbc::DataType::CHAR:
			case ::com::sun::star::sdbc::DataType::VARCHAR:
			case ::com::sun::star::sdbc::DataType::LONGVARCHAR:
				xVariant->updateString(rString);
				break;
			default:
				xVariant->updateNull();
		}
	}
}

//------------------------------------------------------------------------------
void DBTypeConversion::setValue(const Reference<XColumnUpdate>& xVariant,
								const Date& rNullDate,
								const double& rValue,
								sal_Int16 nKeyType) throw(::com::sun::star::lang::IllegalArgumentException)
{
	switch (nKeyType & ~NumberFormat::DEFINED)
	{
		case NumberFormat::DATE:
			xVariant->updateDate(toDate( rValue, rNullDate));
			break;
		case NumberFormat::DATETIME:
			xVariant->updateTimestamp(toDateTime(rValue,rNullDate));
			break;
		case NumberFormat::TIME:
			xVariant->updateTime(toTime(rValue));
			break;
		default:
			{
				double nValue = rValue;
//				Reference<XPropertySet> xProp(xVariant,UNO_QUERY);
//				if (	xProp.is()
//					&&	xProp->getPropertySetInfo()->hasPropertyByName(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISSIGNED))
//					&& !::comphelper::getBOOL(xProp->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISSIGNED))) )
//				{
//					switch (::comphelper::getINT32(xProp->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE))))
//					{
//						case DataType::TINYINT:
//							nValue = static_cast<sal_uInt8>(rValue);
//							break;
//						case DataType::SMALLINT:
//							nValue = static_cast<sal_uInt16>(rValue);
//							break;
//						case DataType::INTEGER:
//							nValue = static_cast<sal_uInt32>(rValue);
//							break;
//						case DataType::BIGINT:
//							nValue = static_cast<sal_uInt64>(rValue);
//							break;
//					}
//				}
				xVariant->updateDouble(nValue);
			}
	}
}

//------------------------------------------------------------------------------
double DBTypeConversion::getValue( const Reference< XColumn >& i_column, const Date& i_relativeToNullDate )
{
	try
	{
		const Reference< XPropertySet > xProp( i_column, UNO_QUERY_THROW );

        const sal_Int32 nColumnType = ::comphelper::getINT32( xProp->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_TYPE ) ) );
        switch ( nColumnType )
        {
        case DataType::DATE:
			return toDouble( i_column->getDate(), i_relativeToNullDate );

        case DataType::TIME:
			return toDouble( i_column->getTime() );

        case DataType::TIMESTAMP:
			return toDouble( i_column->getTimestamp(), i_relativeToNullDate );

        default:
            {
                sal_Bool bIsSigned = sal_True;
                OSL_VERIFY( xProp->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_ISSIGNED ) ) >>= bIsSigned );
			    if ( !bIsSigned )
			    {
				    switch ( nColumnType)
				    {
					    case DataType::TINYINT:
						    return static_cast<double>(static_cast<sal_uInt8>(i_column->getByte()));
					    case DataType::SMALLINT:
						    return static_cast<double>(static_cast<sal_uInt16>(i_column->getShort()));
					    case DataType::INTEGER:
						    return static_cast<double>(static_cast<sal_uInt32>(i_column->getInt()));
					    case DataType::BIGINT:
						    return static_cast<double>(static_cast<sal_uInt64>(i_column->getLong()));
				    }
			    }
            }
		    return i_column->getDouble();
        }
    }
	catch( const Exception& )
	{
        DBG_UNHANDLED_EXCEPTION();
		return 0.0;
	}
}
//------------------------------------------------------------------------------
::rtl::OUString DBTypeConversion::getFormattedValue(const Reference< XPropertySet>& _xColumn,
										   const Reference<XNumberFormatter>& _xFormatter,
										   const ::com::sun::star::lang::Locale& _rLocale,
										   const Date& _rNullDate)
{
	OSL_ENSURE(_xColumn.is() && _xFormatter.is(), "DBTypeConversion::getFormattedValue: invalid arg !");
	if (!_xColumn.is() || !_xFormatter.is())
		return ::rtl::OUString();

	sal_Int32 nKey(0);
	try
	{
		_xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FORMATKEY)) >>= nKey;
	}
	catch (const Exception& )
	{
        OSL_ENSURE(false, "DBTypeConversion::getFormattedValue: caught an exception while asking for the format key!");
	}

	if (!nKey)
	{
		Reference<XNumberFormats> xFormats( _xFormatter->getNumberFormatsSupplier()->getNumberFormats() );
		Reference<XNumberFormatTypes> xTypeList(_xFormatter->getNumberFormatsSupplier()->getNumberFormats(), UNO_QUERY);

		nKey = ::dbtools::getDefaultNumberFormat(_xColumn,
										   Reference< XNumberFormatTypes > (xFormats, UNO_QUERY),
										   _rLocale);

	}

	sal_Int16 nKeyType = getNumberFormatType(_xFormatter, nKey) & ~NumberFormat::DEFINED;

	return DBTypeConversion::getFormattedValue(Reference< XColumn > (_xColumn, UNO_QUERY), _xFormatter, _rNullDate, nKey, nKeyType);
}

//------------------------------------------------------------------------------
::rtl::OUString DBTypeConversion::getFormattedValue(const Reference<XColumn>& xVariant,
								   const Reference<XNumberFormatter>& xFormatter,
								   const Date& rNullDate,
								   sal_Int32 nKey,
								   sal_Int16 nKeyType)
{
	::rtl::OUString aString;
	if (xVariant.is())
	{
		try
		{
			switch (nKeyType & ~NumberFormat::DEFINED)
			{
				case NumberFormat::DATE:
				case NumberFormat::DATETIME:
				{
                    // get a value which represents the given date, relative to the given null date
                    double fValue = getValue( xVariant, rNullDate );
                    if ( !xVariant->wasNull() )
                    {
                         // get the null date of the formatter
                         Date aFormatterNullDate( rNullDate );
                         try
                         {
                             Reference< XNumberFormatsSupplier > xSupplier( xFormatter->getNumberFormatsSupplier(), UNO_SET_THROW );
                             Reference< XPropertySet > xFormatterSettings( xSupplier->getNumberFormatSettings(), UNO_SET_THROW );
                             OSL_VERIFY( xFormatterSettings->getPropertyValue( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "NullDate" ) ) ) >>= aFormatterNullDate );
                         }
                         catch( const Exception& )
                         {
                            DBG_UNHANDLED_EXCEPTION();
                         }
                         // get a value which represents the given date, relative to the null date of the formatter
                         fValue -= toDays( rNullDate, aFormatterNullDate );
                         // format this value
                        aString = xFormatter->convertNumberToString( nKey, fValue );
                    }
                }
				break;
				case NumberFormat::TIME:
				case NumberFormat::NUMBER:
				case NumberFormat::SCIENTIFIC:
				case NumberFormat::FRACTION:
				case NumberFormat::PERCENT:
				{
					double fValue = xVariant->getDouble();
					if (!xVariant->wasNull())
						aString = xFormatter->convertNumberToString(nKey, fValue);
				}	break;
				case NumberFormat::CURRENCY:
				{
					double fValue = xVariant->getDouble();
					if (!xVariant->wasNull())
						aString = xFormatter->getInputString(nKey, fValue);
				}	break;
				case NumberFormat::TEXT:
					aString = xFormatter->formatString(nKey, xVariant->getString());
					break;
				default:
					aString = xVariant->getString();
			}
		}
		catch(const Exception& )
		{
			aString = xVariant->getString();
		}
	}
	return aString;
}
//------------------------------------------------------------------