/**************************************************************
 * 
 * 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/predicateinput.hxx>
#include <comphelper/types.hxx>
#include <connectivity/dbtools.hxx>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <osl/diagnose.h>
#include <connectivity/sqlnode.hxx>
#include <connectivity/PColumn.hxx>
#include <comphelper/numbers.hxx>

//.........................................................................
namespace dbtools
{
//.........................................................................

	using ::com::sun::star::sdbc::XConnection;
	using ::com::sun::star::lang::XMultiServiceFactory;
	using ::com::sun::star::util::XNumberFormatsSupplier;
	using ::com::sun::star::util::XNumberFormatter;
	using ::com::sun::star::uno::UNO_QUERY;
	using ::com::sun::star::beans::XPropertySet;
	using ::com::sun::star::beans::XPropertySetInfo;
	using ::com::sun::star::lang::Locale;
	using ::com::sun::star::uno::Exception;
	using ::com::sun::star::i18n::XLocaleData;
	using ::com::sun::star::i18n::LocaleDataItem;

	using namespace ::com::sun::star::sdbc;
	using namespace ::connectivity;

	using ::connectivity::OSQLParseNode;

	#define Reference ::com::sun::star::uno::Reference

	//=====================================================================
	//---------------------------------------------------------------------
	static sal_Unicode lcl_getSeparatorChar( const ::rtl::OUString& _rSeparator, sal_Unicode _nFallback )
	{
		OSL_ENSURE( 0 < _rSeparator.getLength(), "::lcl_getSeparatorChar: invalid separator string!" );

		sal_Unicode nReturn( _nFallback );
		if ( _rSeparator.getLength() )
			nReturn = static_cast< sal_Char >( _rSeparator.getStr()[0] );
		return nReturn;
	}

	//=====================================================================
	//= OPredicateInputController
	//=====================================================================
	//---------------------------------------------------------------------
	sal_Bool OPredicateInputController::getSeparatorChars( const Locale& _rLocale, sal_Unicode& _rDecSep, sal_Unicode& _rThdSep ) const
	{
		_rDecSep = '.';
		_rThdSep = ',';
		try
		{
			LocaleDataItem aLocaleData;
			if ( m_xLocaleData.is() )
			{
				aLocaleData = m_xLocaleData->getLocaleItem( _rLocale );
				_rDecSep = lcl_getSeparatorChar( aLocaleData.decimalSeparator, _rDecSep );
				_rThdSep = lcl_getSeparatorChar( aLocaleData.decimalSeparator, _rThdSep );
				return sal_True;
			}
		}
		catch( const Exception& )
		{
			OSL_ENSURE( sal_False, "OPredicateInputController::getSeparatorChars: caught an exception!" );
		}
		return sal_False;
	}

	//---------------------------------------------------------------------
	OPredicateInputController::OPredicateInputController(
		const Reference< XMultiServiceFactory >& _rxORB, const Reference< XConnection >& _rxConnection, const IParseContext* _pParseContext )
		:m_xORB( _rxORB )
		,m_xConnection( _rxConnection )
		,m_aParser( m_xORB, _pParseContext )
	{
		try
		{
			// create a number formatter / number formats supplier pair
			OSL_ENSURE( m_xORB.is(), "OPredicateInputController::OPredicateInputController: need a service factory!" );
			if ( m_xORB.is() )
			{
				m_xFormatter = Reference< XNumberFormatter >( m_xORB->createInstance(
					::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.util.NumberFormatter" ) ) ),
					UNO_QUERY
				);
			}

			Reference< XNumberFormatsSupplier >  xNumberFormats = ::dbtools::getNumberFormats( m_xConnection, sal_True );
			if ( !xNumberFormats.is() )
				::comphelper::disposeComponent( m_xFormatter );
			else if ( m_xFormatter.is() )
				m_xFormatter->attachNumberFormatsSupplier( xNumberFormats );

			// create the locale data
			if ( m_xORB.is() )
			{
				m_xLocaleData = m_xLocaleData.query( m_xORB->createInstance(
					::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.i18n.LocaleData" ) ) )
				);
			}
		}
		catch( const Exception& )
		{
			OSL_ENSURE( sal_False, "OPredicateInputController::OPredicateInputController: caught an exception!" );
		}
	}

	//---------------------------------------------------------------------
	OSQLParseNode* OPredicateInputController::implPredicateTree(::rtl::OUString& _rErrorMessage, const ::rtl::OUString& _rStatement, const Reference< XPropertySet > & _rxField) const
	{
		OSQLParseNode* pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, _rStatement, m_xFormatter, _rxField );
		if ( !pReturn )
		{	// is it a text field ?
			sal_Int32 nType = DataType::OTHER;
			_rxField->getPropertyValue( ::rtl::OUString::createFromAscii( "Type" ) ) >>= nType;

			if	(	( DataType::CHAR		== nType )
				||	( DataType::VARCHAR		== nType )
				||	( DataType::LONGVARCHAR == nType )
				||	( DataType::CLOB		== nType )
				)
			{	// yes -> force a quoted text and try again
				::rtl::OUString sQuoted( _rStatement );
				if	(	sQuoted.getLength()
					&&	(	(sQuoted.getStr()[0] != '\'')
						||	(sQuoted.getStr()[ sQuoted.getLength() - 1 ] != '\'' )
						)
					)
				{
					static const ::rtl::OUString sSingleQuote( RTL_CONSTASCII_USTRINGPARAM( "'" ) );
					static const ::rtl::OUString sDoubleQuote( RTL_CONSTASCII_USTRINGPARAM( "''" ) );

					sal_Int32 nIndex = -1;
					sal_Int32 nTemp = 0;
					while ( -1 != ( nIndex = sQuoted.indexOf( '\'',nTemp ) ) )
					{
						sQuoted = sQuoted.replaceAt( nIndex, 1, sDoubleQuote );
						nTemp = nIndex+2;
					}

					::rtl::OUString sTemp( sSingleQuote );
					( sTemp += sQuoted ) += sSingleQuote;
					sQuoted = sTemp;
				}
				pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sQuoted, m_xFormatter, _rxField );
			}

			// one more fallback: for numeric fields, and value strings containing a decimal/thousands separator
			// problem which is to be solved with this:
			// * a system locale "german"
			// * a column formatted with an english number format
			// => the output is german (as we use the system locale for this), i.e. "3,4"
			// => the input does not recognize the german text, as predicateTree uses the number format
			//	  of the column to determine the main locale - the locale on the context is only a fallback
			if	(	( DataType::FLOAT == nType )
				||	( DataType::REAL == nType )
				||	( DataType::DOUBLE == nType )
				||	( DataType::NUMERIC == nType )
				||	( DataType::DECIMAL == nType )
				)
			{
				const IParseContext& rParseContext = m_aParser.getContext();
				// get the separators for the locale of our parse context
				sal_Unicode nCtxDecSep;
				sal_Unicode nCtxThdSep;
				getSeparatorChars( rParseContext.getPreferredLocale(), nCtxDecSep, nCtxThdSep );

				// determine the locale of the column we're building a predicate string for
				sal_Unicode nFmtDecSep( nCtxDecSep );
				sal_Unicode nFmtThdSep( nCtxThdSep );
				try
				{
					Reference< XPropertySetInfo > xPSI( _rxField->getPropertySetInfo() );
					if ( xPSI.is() && xPSI->hasPropertyByName( ::rtl::OUString::createFromAscii( "FormatKey" ) ) )
					{
						sal_Int32 nFormatKey = 0;
						_rxField->getPropertyValue( ::rtl::OUString::createFromAscii( "FormatKey" ) ) >>= nFormatKey;
						if ( nFormatKey && m_xFormatter.is() )
						{
							Locale aFormatLocale;
							::comphelper::getNumberFormatProperty(
								m_xFormatter,
								nFormatKey,
								::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Locale" ) )
							) >>= aFormatLocale;

							// valid locale
							if ( aFormatLocale.Language.getLength() )
							{
								getSeparatorChars( aFormatLocale, nFmtDecSep, nCtxThdSep );
							}
						}
					}
				}
				catch( const Exception& )
				{
					OSL_ENSURE( sal_False, "OPredicateInputController::implPredicateTree: caught an exception while dealing with the formats!" );
				}

				sal_Bool bDecDiffers = ( nCtxDecSep != nFmtDecSep );
				sal_Bool bFmtDiffers = ( nCtxThdSep != nFmtThdSep );
				if ( bDecDiffers || bFmtDiffers )
				{	// okay, at least one differs
					// "translate" the value into the "format locale"
					::rtl::OUString sTranslated( _rStatement );
					const sal_Unicode nIntermediate( '_' );
					sTranslated = sTranslated.replace( nCtxDecSep,	nIntermediate );
					sTranslated = sTranslated.replace( nCtxThdSep,	nFmtThdSep );
					sTranslated = sTranslated.replace( nIntermediate, nFmtDecSep );

					pReturn = const_cast< OSQLParser& >( m_aParser ).predicateTree( _rErrorMessage, sTranslated, m_xFormatter, _rxField );
				}
			}
		}
		return pReturn;
	}

	//---------------------------------------------------------------------
	sal_Bool OPredicateInputController::normalizePredicateString(
		::rtl::OUString& _rPredicateValue, const Reference< XPropertySet > & _rxField, ::rtl::OUString* _pErrorMessage ) const
	{
		OSL_ENSURE( m_xConnection.is() && m_xFormatter.is() && _rxField.is(),
			"OPredicateInputController::normalizePredicateString: invalid state or params!" );

		sal_Bool bSuccess = sal_False;
		if ( m_xConnection.is() && m_xFormatter.is() && _rxField.is() )
		{
			// parse the string
			::rtl::OUString sError;
			::rtl::OUString sTransformedText( _rPredicateValue );
			OSQLParseNode* pParseNode = implPredicateTree( sError, sTransformedText, _rxField );
			if ( _pErrorMessage ) *_pErrorMessage = sError;

			if ( pParseNode )
			{
				const IParseContext& rParseContext = m_aParser.getContext();
				sal_Unicode nDecSeparator, nThousandSeparator;
				getSeparatorChars( rParseContext.getPreferredLocale(), nDecSeparator, nThousandSeparator );

				// translate it back into a string
				sTransformedText = ::rtl::OUString();
				pParseNode->parseNodeToPredicateStr(
					sTransformedText, m_xConnection, m_xFormatter, _rxField,
					rParseContext.getPreferredLocale(), (sal_Char)nDecSeparator, &rParseContext
				);
				_rPredicateValue = sTransformedText;
				delete pParseNode;

				bSuccess = sal_True;
			}
		}

		return bSuccess;
	}

	//---------------------------------------------------------------------
	::rtl::OUString OPredicateInputController::getPredicateValue(
		const ::rtl::OUString& _rPredicateValue, const Reference< XPropertySet > & _rxField,
		sal_Bool _bForStatementUse, ::rtl::OUString* _pErrorMessage ) const
	{
		OSL_ENSURE( _rxField.is(), "OPredicateInputController::getPredicateValue: invalid params!" );
		::rtl::OUString sReturn;
		if ( _rxField.is() )
		{
			::rtl::OUString sValue( _rPredicateValue );

			// a little problem : if the field is a text field, the normalizePredicateString added two
			// '-characters to the text. If we would give this to predicateTree this would add
			// two  additional '-characters which we don't want. So check the field format.
			// FS - 06.01.00 - 71532
			sal_Bool bValidQuotedText =	( sValue.getLength() >= 2 )
									&&	( sValue.getStr()[0] == '\'' )
									&&	( sValue.getStr()[ sValue.getLength() - 1 ] == '\'' );
				// again : as normalizePredicateString always did a conversion on the value text,
				// bValidQuotedText == sal_True implies that we have a text field, as no other field
				// values will be formatted with the quote characters
			if ( bValidQuotedText )
			{
				sValue = sValue.copy( 1, sValue.getLength() - 2 );
				static const ::rtl::OUString sSingleQuote( RTL_CONSTASCII_USTRINGPARAM( "'" ) );
				static const ::rtl::OUString sDoubleQuote( RTL_CONSTASCII_USTRINGPARAM( "''" ) );

				sal_Int32 nIndex = -1;
				sal_Int32 nTemp = 0;
				while ( -1 != ( nIndex = sValue.indexOf( sDoubleQuote,nTemp ) ) )
				{
					sValue = sValue.replaceAt( nIndex, 2, sSingleQuote );
					nTemp = nIndex+2;
				}
			}

			// The following is mostly stolen from the former implementation in the parameter dialog
			// (dbaccess/source/ui/dlg/paramdialog.cxx). I do not fully understand this .....

			::rtl::OUString sError;
			OSQLParseNode* pParseNode = implPredicateTree( sError, sValue, _rxField );
			if ( _pErrorMessage ) 
                *_pErrorMessage = sError;

			sReturn = implParseNode(pParseNode,_bForStatementUse);
		}

		return sReturn;
	}

    ::rtl::OUString OPredicateInputController::getPredicateValue(
		const ::rtl::OUString& _sField, const ::rtl::OUString& _rPredicateValue, sal_Bool _bForStatementUse, ::rtl::OUString* _pErrorMessage ) const
	{
		::rtl::OUString sReturn = _rPredicateValue;
        ::rtl::OUString sError;
        ::rtl::OUString sField = _sField;
        sal_Int32 nIndex = 0;
        sField = sField.getToken(0,'(',nIndex);
        if(nIndex == -1)
			sField = _sField;
        sal_Int32 nType = ::connectivity::OSQLParser::getFunctionReturnType(sField,&m_aParser.getContext());
        if ( nType == DataType::OTHER || !sField.getLength() )
        {
            // first try the international version
            ::rtl::OUString sSql;
            sSql += ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("SELECT * "));
            sSql += ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(" FROM x WHERE "));
            sSql += sField;
            sSql += _rPredicateValue;
            ::std::auto_ptr<OSQLParseNode> pParseNode( const_cast< OSQLParser& >( m_aParser ).parseTree( sError, sSql, sal_True ) );
            nType = DataType::DOUBLE;
            if ( pParseNode.get() )
            {
                OSQLParseNode* pColumnRef = pParseNode->getByRule(OSQLParseNode::column_ref);
                if ( pColumnRef )
                {
                }
            }
        }

        Reference<XDatabaseMetaData> xMeta = m_xConnection->getMetaData();
        parse::OParseColumn* pColumn = new parse::OParseColumn(	sField,
												                ::rtl::OUString(),
												                ::rtl::OUString(),
                                                                ::rtl::OUString(),
												                ColumnValue::NULLABLE_UNKNOWN,
												                0,
												                0,
												                nType,
												                sal_False,
												                sal_False,
												                xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers());
        Reference<XPropertySet> xColumn = pColumn;
        pColumn->setFunction(sal_True);
        pColumn->setRealName(sField);
        
		OSQLParseNode* pParseNode = implPredicateTree( sError, _rPredicateValue, xColumn );
		if ( _pErrorMessage ) 
            *_pErrorMessage = sError;
        return pParseNode ? implParseNode(pParseNode,_bForStatementUse) : sReturn;
    }

    ::rtl::OUString OPredicateInputController::implParseNode(OSQLParseNode* pParseNode,sal_Bool _bForStatementUse) const
    {
        ::rtl::OUString sReturn;
        if ( pParseNode )
		{
            ::std::auto_ptr<OSQLParseNode> pTemp(pParseNode);
			OSQLParseNode* pOdbcSpec = pParseNode->getByRule( OSQLParseNode::odbc_fct_spec );
			if ( pOdbcSpec )
			{
				if ( _bForStatementUse )
				{
                    OSQLParseNode* pFuncSpecParent = pOdbcSpec->getParent();
					OSL_ENSURE( pFuncSpecParent, "OPredicateInputController::getPredicateValue: an ODBC func spec node without parent?" );
					if ( pFuncSpecParent )
						pFuncSpecParent->parseNodeToStr(sReturn, m_xConnection, &m_aParser.getContext(), sal_False, sal_True);						
				}
				else
				{
					OSQLParseNode* pValueNode = pOdbcSpec->getChild(1);
					if ( SQL_NODE_STRING == pValueNode->getNodeType() )
						sReturn = pValueNode->getTokenValue();
					else
						pValueNode->parseNodeToStr(sReturn, m_xConnection, &m_aParser.getContext(), sal_False, sal_True);						
					// sReturn = pOdbcSpec->getChild(1)->getTokenValue();
				}
			}
			else
			{
				if	( pParseNode->count() >= 3 )
				{
					OSQLParseNode* pValueNode = pParseNode->getChild(2);
					OSL_ENSURE( pValueNode, "OPredicateInputController::getPredicateValue: invalid node child!" );
					if ( !_bForStatementUse )
					{
						if ( SQL_NODE_STRING == pValueNode->getNodeType() )
							sReturn = pValueNode->getTokenValue();
						else
							pValueNode->parseNodeToStr(
								sReturn, m_xConnection, &m_aParser.getContext(), sal_False, sal_True
							);
					}
					else
						pValueNode->parseNodeToStr(
							sReturn, m_xConnection, &m_aParser.getContext(), sal_False, sal_True
						);
				}
				else
					OSL_ENSURE( sal_False, "OPredicateInputController::getPredicateValue: unknown/invalid structure (noodbc)!" );
			}
		}
        return sReturn;
    }
//.........................................................................
}	// namespace dbtools
//.........................................................................


