/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
#include "cellvaluebinding.hxx"
#include <tools/debug.hxx>
#include <rtl/math.hxx>
#include <com/sun/star/table/XCellRange.hpp>
#include <com/sun/star/sheet/XCellAddressable.hpp>
#include <com/sun/star/sheet/XCellRangeData.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
#include <com/sun/star/util/XNumberFormatTypes.hpp>
#include <com/sun/star/util/NumberFormat.hpp>

//.........................................................................
namespace calc
{
//.........................................................................

#define PROP_HANDLE_BOUND_CELL  1

    using namespace ::com::sun::star::uno;
    using namespace ::com::sun::star::lang;
    using namespace ::com::sun::star::table;
    using namespace ::com::sun::star::text;
    using namespace ::com::sun::star::sheet;
    using namespace ::com::sun::star::container;
    using namespace ::com::sun::star::beans;
    using namespace ::com::sun::star::util;
    using namespace ::com::sun::star::form::binding;

    //=====================================================================
    //= OCellValueBinding
    //=====================================================================
    DBG_NAME( OCellValueBinding )
    //---------------------------------------------------------------------
#ifdef DBG_UTIL
    const char* OCellValueBinding::checkConsistency_static( const void* _pThis )
    {
        return static_cast< const OCellValueBinding* >( _pThis )->checkConsistency( );
    }

    const char* OCellValueBinding::checkConsistency( ) const
    {
        const char* pAssertion = NULL;
        if ( m_xCellText.is() && !m_xCell.is() )
            // there are places (e.g. getSupportedTypes) which rely on the fact
            // that m_xCellText.is() implies m_xCell.is()
            pAssertion = "cell references inconsistent!";

        // TODO: place any additional checks here to ensure consistency of this instance
        return pAssertion;
    }
#endif

    //---------------------------------------------------------------------
    OCellValueBinding::OCellValueBinding( const Reference< XSpreadsheetDocument >& _rxDocument, sal_Bool _bListPos )
        :OCellValueBinding_Base( m_aMutex )
        ,OCellValueBinding_PBase( OCellValueBinding_Base::rBHelper )
        ,m_xDocument( _rxDocument )
        ,m_aModifyListeners( m_aMutex )
        ,m_bInitialized( sal_False )
        ,m_bListPos( _bListPos )
    {
        DBG_CTOR( OCellValueBinding, checkConsistency_static );

        // register our property at the base class
        CellAddress aInitialPropValue;
        registerPropertyNoMember(
            ::rtl::OUString::createFromAscii( "BoundCell" ),
            PROP_HANDLE_BOUND_CELL,
            PropertyAttribute::BOUND | PropertyAttribute::READONLY,
            ::getCppuType( &aInitialPropValue ),
            &aInitialPropValue
        );

        // TODO: implement a ReadOnly property as required by the service,
        // which probably maps to the cell being locked
    }

    //---------------------------------------------------------------------
    OCellValueBinding::~OCellValueBinding( )
    {
        if ( !OCellValueBinding_Base::rBHelper.bDisposed )
        {
            acquire();  // prevent duplicate dtor
            dispose();
        }

        DBG_DTOR( OCellValueBinding, checkConsistency_static );
    }

    //--------------------------------------------------------------------
    IMPLEMENT_FORWARD_XINTERFACE2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase )

    //--------------------------------------------------------------------
    IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCellValueBinding, OCellValueBinding_Base, OCellValueBinding_PBase )

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::disposing()
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY );
        if ( xBroadcaster.is() )
        {
            xBroadcaster->removeModifyListener( this );
        }

//        OCellValueBinding_Base::disposing();
        WeakAggComponentImplHelperBase::disposing();

        // TODO: clean up here whatever you need to clean up (e.g. deregister as XEventListener
        // for the cell)
    }

    //--------------------------------------------------------------------
    Reference< XPropertySetInfo > SAL_CALL OCellValueBinding::getPropertySetInfo(  ) throw(RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        return createPropertySetInfo( getInfoHelper() ) ;
    }

    //--------------------------------------------------------------------
    ::cppu::IPropertyArrayHelper& SAL_CALL OCellValueBinding::getInfoHelper()
    {
        return *OCellValueBinding_PABase::getArrayHelper();
    }

    //--------------------------------------------------------------------
    ::cppu::IPropertyArrayHelper* OCellValueBinding::createArrayHelper( ) const
    {
        Sequence< Property > aProps;
        describeProperties( aProps );
        return new ::cppu::OPropertyArrayHelper(aProps);
    }

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::getFastPropertyValue( Any& _rValue, sal_Int32 _nHandle ) const
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        DBG_ASSERT( _nHandle == PROP_HANDLE_BOUND_CELL, "OCellValueBinding::getFastPropertyValue: invalid handle!" );
            // we only have this one property ....
        (void)_nHandle;     // avoid warning in product version

        _rValue.clear();
        Reference< XCellAddressable > xCellAddress( m_xCell, UNO_QUERY );
        if ( xCellAddress.is() )
            _rValue <<= xCellAddress->getCellAddress( );
    }

    //--------------------------------------------------------------------
    Sequence< Type > SAL_CALL OCellValueBinding::getSupportedValueTypes(  ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        checkDisposed( );
        checkInitialized( );

        sal_Int32 nCount = m_xCellText.is() ? 3 : m_xCell.is() ? 1 : 0;
        if ( m_bListPos )
            ++nCount;

        Sequence< Type > aTypes( nCount );
        if ( m_xCell.is() )
        {
            // an XCell can be used to set/get "double" values
            aTypes[0] = ::getCppuType( static_cast< double* >( NULL ) );
            if ( m_xCellText.is() )
            {
                // an XTextRange can be used to set/get "string" values
                aTypes[1] = ::getCppuType( static_cast< ::rtl::OUString* >( NULL ) );
                // and additionally, we use it to handle booleans
                aTypes[2] = ::getCppuType( static_cast< sal_Bool* >( NULL ) );
            }

            // add sal_Int32 only if constructed as ListPositionCellBinding
            if ( m_bListPos )
                aTypes[nCount-1] = ::getCppuType( static_cast< sal_Int32* >( NULL ) );
        }

        return aTypes;
    }
        
    //--------------------------------------------------------------------
    sal_Bool SAL_CALL OCellValueBinding::supportsType( const Type& aType ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        checkDisposed( );
        checkInitialized( );

        // look up in our sequence
        Sequence< Type > aSupportedTypes( getSupportedValueTypes() );
        const Type* pTypes = aSupportedTypes.getConstArray();
        const Type* pTypesEnd = aSupportedTypes.getConstArray() + aSupportedTypes.getLength();
        while ( pTypes != pTypesEnd )
            if ( aType.equals( *pTypes++ ) )
                return sal_True;

        return sal_False;
    }
        
    //--------------------------------------------------------------------
    Any SAL_CALL OCellValueBinding::getValue( const Type& aType ) throw (IncompatibleTypesException, RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        checkDisposed( );
        checkInitialized( );
        checkValueType( aType );

        Any aReturn;
        switch ( aType.getTypeClass() )
        {
        case TypeClass_STRING:
            DBG_ASSERT( m_xCellText.is(), "OCellValueBinding::getValue: don't have a text!" );
            if ( m_xCellText.is() )
                aReturn <<= m_xCellText->getString();
            else
                aReturn <<= ::rtl::OUString();
            break;

        case TypeClass_BOOLEAN:
            DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
            if ( m_xCell.is() )
            {
                // check if the cell has a numeric value (this might go into a helper function):

                sal_Bool bHasValue = sal_False;
                CellContentType eCellType = m_xCell->getType();
                if ( eCellType == CellContentType_VALUE )
                    bHasValue = sal_True;
                else if ( eCellType == CellContentType_FORMULA )
                {
                    // check if the formula result is a value
                    if ( m_xCell->getError() == 0 )
                    {
                        Reference<XPropertySet> xProp( m_xCell, UNO_QUERY );
                        if ( xProp.is() )
                        {
                            CellContentType eResultType;
                            if ( (xProp->getPropertyValue(::rtl::OUString::createFromAscii( "FormulaResultType" ) ) >>= eResultType) && eResultType == CellContentType_VALUE )
                                bHasValue = sal_True;
                        }
                    }
                }

                if ( bHasValue )
                {
                    // 0 is "unchecked", any other value is "checked", regardless of number format
                    double nCellValue = m_xCell->getValue();
                    sal_Bool bBoolValue = ( nCellValue != 0.0 );
                    aReturn <<= bBoolValue;
                }
                // empty cells, text cells and text or error formula results: leave return value empty
            }
            break;

        case TypeClass_DOUBLE:
            DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
            if ( m_xCell.is() )
                aReturn <<= m_xCell->getValue();
            else
                aReturn <<= (double)0;
            break;

        case TypeClass_LONG:
            DBG_ASSERT( m_xCell.is(), "OCellValueBinding::getValue: don't have a double value supplier!" );
            if ( m_xCell.is() )
            {
                // The list position value in the cell is 1-based.
                // We subtract 1 from any cell value (no special handling for 0 or negative values).

                sal_Int32 nValue = (sal_Int32) rtl::math::approxFloor( m_xCell->getValue() );
                --nValue;

                aReturn <<= nValue;
            }
            else
                aReturn <<= (sal_Int32)0;
            break;

        default:
            DBG_ERROR( "OCellValueBinding::getValue: unreachable code!" );
                // a type other than double and string should never have survived the checkValueType
                // above
        }
        return aReturn;
    }
        
    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::setValue( const Any& aValue ) throw (IncompatibleTypesException, NoSupportException, RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );
        checkDisposed( );
        checkInitialized( );
        if ( aValue.hasValue() )
            checkValueType( aValue.getValueType() );

        switch ( aValue.getValueType().getTypeClass() )
        {
        case TypeClass_STRING:
            {
                DBG_ASSERT( m_xCellText.is(), "OCellValueBinding::setValue: don't have a text!" );

                ::rtl::OUString sText;
                aValue >>= sText;
                if ( m_xCellText.is() )
                    m_xCellText->setString( sText );
            }
            break;

        case TypeClass_BOOLEAN:
            {
                DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );

                // boolean is stored as values 0 or 1
                // TODO: set the number format to boolean if no format is set?

                sal_Bool bValue( sal_False );
                aValue >>= bValue;
                double nCellValue = bValue ? 1.0 : 0.0;

                if ( m_xCell.is() )
                    m_xCell->setValue( nCellValue );

                setBooleanFormat();
            }
            break;

        case TypeClass_DOUBLE:
            {
                DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );

                double nValue = 0;
                aValue >>= nValue;
                if ( m_xCell.is() )
                    m_xCell->setValue( nValue );
            }
            break;

        case TypeClass_LONG:
            {
                DBG_ASSERT( m_xCell.is(), "OCellValueBinding::setValue: don't have a double value supplier!" );

                sal_Int32 nValue = 0;
                aValue >>= nValue;      // list index from control layer (0-based)
                ++nValue;               // the list position value in the cell is 1-based
                if ( m_xCell.is() )
                    m_xCell->setValue( nValue );
            }
            break;

        case TypeClass_VOID:
            {
                // #N/A error value can only be set using XCellRangeData

            	Reference<XCellRangeData> xData( m_xCell, UNO_QUERY );
                DBG_ASSERT( xData.is(), "OCellValueBinding::setValue: don't have XCellRangeData!" );
                if ( xData.is() )
                {
                    Sequence<Any> aInner(1);                            // one empty element
                    Sequence< Sequence<Any> > aOuter( &aInner, 1 );     // one row
                    xData->setDataArray( aOuter );
                }
            }
            break;

        default:
            DBG_ERROR( "OCellValueBinding::setValue: unreachable code!" );
                // a type other than double and string should never have survived the checkValueType
                // above
        }
    }
    //--------------------------------------------------------------------
    void OCellValueBinding::setBooleanFormat()
    {
        // set boolean number format if not already set

        ::rtl::OUString sPropName( ::rtl::OUString::createFromAscii( "NumberFormat" ) );
        Reference<XPropertySet> xCellProp( m_xCell, UNO_QUERY );
        Reference<XNumberFormatsSupplier> xSupplier( m_xDocument, UNO_QUERY );
        if ( xSupplier.is() && xCellProp.is() )
        {
            Reference<XNumberFormats> xFormats(xSupplier->getNumberFormats());
        	Reference<XNumberFormatTypes> xTypes( xFormats, UNO_QUERY );
        	if ( xTypes.is() )
        	{
        		Locale aLocale;
        		sal_Bool bWasBoolean = sal_False;

        		sal_Int32 nOldIndex = ::comphelper::getINT32( xCellProp->getPropertyValue( sPropName ) );
				Reference<XPropertySet> xOldFormat;
                try
                {
        		    xOldFormat.set(xFormats->getByKey( nOldIndex ));
                }
                catch ( Exception& )
                {
                    // non-existing format - can happen, use defaults
                }
                if ( xOldFormat.is() )
                {
                    // use the locale of the existing format
                    xOldFormat->getPropertyValue( ::rtl::OUString::createFromAscii( "Locale" ) ) >>= aLocale;

                    sal_Int16 nOldType = ::comphelper::getINT16(
                    	xOldFormat->getPropertyValue( ::rtl::OUString::createFromAscii( "Type" ) ) );
                    if ( nOldType & NumberFormat::LOGICAL )
                    	bWasBoolean = sal_True;
                }

        		if ( !bWasBoolean )
        		{
        		    sal_Int32 nNewIndex = xTypes->getStandardFormat( NumberFormat::LOGICAL, aLocale );
        		    xCellProp->setPropertyValue( sPropName, makeAny( nNewIndex ) );
        		}
        	}
        }
    }

    //--------------------------------------------------------------------
    void OCellValueBinding::checkDisposed( ) const SAL_THROW( ( DisposedException ) )
    {
        if ( OCellValueBinding_Base::rBHelper.bInDispose || OCellValueBinding_Base::rBHelper.bDisposed )
            throw DisposedException();
            // TODO: is it worth having an error message here?
    }

    //--------------------------------------------------------------------
    void OCellValueBinding::checkInitialized() SAL_THROW( ( RuntimeException ) )
    {
        if ( !m_bInitialized )
            throw RuntimeException();
            // TODO: error message
    }

    //--------------------------------------------------------------------
    void OCellValueBinding::checkValueType( const Type& _rType ) const SAL_THROW( ( IncompatibleTypesException ) )
    {
        OCellValueBinding* pNonConstThis = const_cast< OCellValueBinding* >( this );
        if ( !pNonConstThis->supportsType( _rType ) )
        {
            ::rtl::OUString sMessage( RTL_CONSTASCII_USTRINGPARAM( "The given type (" ) );
            sMessage += _rType.getTypeName();
            sMessage += ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( ") is not supported by this binding." ) );
                // TODO: localize this error message

            throw IncompatibleTypesException( sMessage, *pNonConstThis );
                // TODO: alternatively use a type converter service for this?
        }
    }

    //--------------------------------------------------------------------
    ::rtl::OUString SAL_CALL OCellValueBinding::getImplementationName(  ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.comp.sheet.OCellValueBinding" ) );
    }
        
    //--------------------------------------------------------------------
    sal_Bool SAL_CALL OCellValueBinding::supportsService( const ::rtl::OUString& _rServiceName ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        Sequence< ::rtl::OUString > aSupportedServices( getSupportedServiceNames() );
        const ::rtl::OUString* pLookup = aSupportedServices.getConstArray();
        const ::rtl::OUString* pLookupEnd = aSupportedServices.getConstArray() + aSupportedServices.getLength();
        while ( pLookup != pLookupEnd )
            if ( *pLookup++ == _rServiceName )
                return sal_True;

        return sal_False;
    }
        
    //--------------------------------------------------------------------
    Sequence< ::rtl::OUString > SAL_CALL OCellValueBinding::getSupportedServiceNames(  ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        Sequence< ::rtl::OUString > aServices( m_bListPos ? 3 : 2 );
        aServices[ 0 ] =  ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.table.CellValueBinding" ) );
        aServices[ 1 ] =  ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.form.binding.ValueBinding" ) );
        if ( m_bListPos )
            aServices[ 2 ] =  ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.table.ListPositionCellBinding" ) );
        return aServices;
    }

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::addModifyListener( const Reference< XModifyListener >& _rxListener ) throw (RuntimeException)
    {
       if ( _rxListener.is() )
           m_aModifyListeners.addInterface( _rxListener );
    }
    
    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::removeModifyListener( const Reference< XModifyListener >& _rxListener ) throw (RuntimeException)
    {
       if ( _rxListener.is() )
           m_aModifyListeners.removeInterface( _rxListener );
    }

    //--------------------------------------------------------------------
    void OCellValueBinding::notifyModified()
    {
        EventObject aEvent;
        aEvent.Source.set(*this);

        ::cppu::OInterfaceIteratorHelper aIter( m_aModifyListeners );
        while ( aIter.hasMoreElements() )
        {
            try
            {
                static_cast< XModifyListener* >( aIter.next() )->modified( aEvent );
            }
            catch( const RuntimeException& )
            {
                // silent this
            }
            catch( const Exception& )
            {
                DBG_ERROR( "OCellValueBinding::notifyModified: caught a (non-runtime) exception!" );
            }
        }
    }

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::modified( const EventObject& /* aEvent */ ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        notifyModified();
    }

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::disposing( const EventObject& aEvent ) throw (RuntimeException)
    {
        DBG_CHKTHIS( OCellValueBinding, checkConsistency_static );

        Reference<XInterface> xCellInt( m_xCell, UNO_QUERY );
        if ( xCellInt == aEvent.Source )
        {
            // release references to cell object
            m_xCell.clear();
            m_xCellText.clear();
        }
    }

    //--------------------------------------------------------------------
    void SAL_CALL OCellValueBinding::initialize( const Sequence< Any >& _rArguments ) throw (Exception, RuntimeException)
    {
        if ( m_bInitialized )
            throw Exception();
            // TODO: error message

        // get the cell address
        CellAddress aAddress;
        sal_Bool bFoundAddress = sal_False;

        const Any* pLoop = _rArguments.getConstArray();
        const Any* pLoopEnd = _rArguments.getConstArray() + _rArguments.getLength();
        for ( ; ( pLoop != pLoopEnd ) && !bFoundAddress; ++pLoop )
        {
            NamedValue aValue;
            if ( *pLoop >>= aValue )
            {
                if ( aValue.Name.equalsAscii( "BoundCell" ) )
                {
                    if ( aValue.Value >>= aAddress )
                        bFoundAddress = sal_True;
                }
            }
        }

        if ( !bFoundAddress )
            // TODO: error message
            throw Exception();

        // get the cell object
        try
        {
            // first the sheets collection
            Reference< XIndexAccess > xSheets;
            if ( m_xDocument.is() )
                xSheets.set(xSheets.query( m_xDocument->getSheets( ) ));
            DBG_ASSERT( xSheets.is(), "OCellValueBinding::initialize: could not retrieve the sheets!" );

            if ( xSheets.is() )
            {
                // the concrete sheet
                Reference< XCellRange > xSheet(xSheets->getByIndex( aAddress.Sheet ), UNO_QUERY);
                DBG_ASSERT( xSheet.is(), "OCellValueBinding::initialize: NULL sheet, but no exception!" );

                // the concrete cell
                if ( xSheet.is() )
                {
                    m_xCell.set(xSheet->getCellByPosition( aAddress.Column, aAddress.Row ));
                    Reference< XCellAddressable > xAddressAccess( m_xCell, UNO_QUERY );
                    DBG_ASSERT( xAddressAccess.is(), "OCellValueBinding::initialize: either NULL cell, or cell without address access!" );
                }
            }
        }
        catch( const Exception& )
        {
            DBG_ERROR( "OCellValueBinding::initialize: caught an exception while retrieving the cell object!" );
        }

        if ( !m_xCell.is() )
            throw Exception();
            // TODO error message

        m_xCellText.set(m_xCellText.query( m_xCell ));

        Reference<XModifyBroadcaster> xBroadcaster( m_xCell, UNO_QUERY );
        if ( xBroadcaster.is() )
        {
            xBroadcaster->addModifyListener( this );
        }

        // TODO: add as XEventListener to the cell, so we get notified when it dies,
        // and can dispose ourself then

        // TODO: somehow add as listener so we get notified when the address of the cell changes
        // We need to forward this as change in our BoundCell property to our property change listeners

        // TODO: be an XModifyBroadcaster, so that changes in our cell can be notified
        // to the BindableValue which is/will be bound to this instance.

        m_bInitialized = sal_True;
        // TODO: place your code here
    }


//.........................................................................
}   // namespace calc
//.........................................................................