/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */


//=========================================================================
// Todo:
//
// - closeKey() calls (according to JSC not really needed because XRegistry
//   implementation closes key in it's dtor.
//
//=========================================================================
#include <osl/diagnose.h>
#include <rtl/ustrbuf.hxx>
#include "com/sun/star/reflection/XPublished.hpp"
#include "cppuhelper/implbase1.hxx"
#include "registry/reader.hxx"
#include "registry/version.h"
#include "base.hxx"
#include "rdbtdp_tdenumeration.hxx"

using namespace com::sun::star;

namespace {

class IndividualConstantTypeDescriptionImpl:
    public cppu::ImplInheritanceHelper1<
        stoc_rdbtdp::ConstantTypeDescriptionImpl,
        com::sun::star::reflection::XPublished >
{
public:
    IndividualConstantTypeDescriptionImpl(
        rtl::OUString const & name, com::sun::star::uno::Any const & value,
        bool published):
        cppu::ImplInheritanceHelper1<
            stoc_rdbtdp::ConstantTypeDescriptionImpl,
            com::sun::star::reflection::XPublished >(name, value),
        m_published(published) {}

    virtual sal_Bool SAL_CALL isPublished()
        throw (::com::sun::star::uno::RuntimeException)
    { return m_published; }

private:
    bool m_published;
};

}

namespace stoc_rdbtdp
{

//=========================================================================
//=========================================================================
//
// TypeDescriptionEnumerationImpl Implementation.
//
//=========================================================================
//=========================================================================

// static
rtl::Reference< TypeDescriptionEnumerationImpl >
TypeDescriptionEnumerationImpl::createInstance(
        const uno::Reference< container::XHierarchicalNameAccess > & xTDMgr,
        const rtl::OUString & rModuleName,
        const uno::Sequence< uno::TypeClass > & rTypes,
        reflection::TypeDescriptionSearchDepth eDepth,
        const RegistryKeyList & rBaseKeys )
    throw ( reflection::NoSuchTypeNameException,
            reflection::InvalidTypeNameException,
            uno::RuntimeException )
{
    if ( rModuleName.isEmpty() )
    {
        // Enumeration for root requested.
        return rtl::Reference< TypeDescriptionEnumerationImpl >(
            new TypeDescriptionEnumerationImpl(
                xTDMgr, rBaseKeys, rTypes, eDepth ) );
    }

    RegistryKeyList aModuleKeys;

    rtl::OUString aKey( rModuleName.replace( '.', '/' ) );

    bool bOpenKeySucceeded = false;

    const RegistryKeyList::const_iterator end = rBaseKeys.end();
    RegistryKeyList::const_iterator it = rBaseKeys.begin();

    while ( it != end )
    {
        uno::Reference< registry::XRegistryKey > xKey;
        try
        {
            xKey = (*it)->openKey( aKey );
            if ( xKey.is() )
            {
                // closes key in it's dtor (which is
                // called even in case of exceptions).
                RegistryKeyCloser aCloser( xKey );

                if ( xKey->isValid() )
                {
                    bOpenKeySucceeded = true;

                    if ( xKey->getValueType()
                         == registry::RegistryValueType_BINARY )
                    {
                        uno::Sequence< sal_Int8 > aBytes(
                            xKey->getBinaryValue() );

                        typereg::Reader aReader(
                            aBytes.getConstArray(), aBytes.getLength(), false,
                            TYPEREG_VERSION_1);

                        rtl::OUString aName(
                            aReader.getTypeName().replace( '/', '.' ) );

                        if ( aReader.getTypeClass() == RT_TYPE_MODULE )
                        {
                            // Do not close xKey!
                            aCloser.reset();

                            aModuleKeys.push_back( xKey );
                        }
                    }
                }
                else
                {
                    OSL_FAIL(
                        "TypeDescriptionEnumerationImpl::createInstance "
                        "- Invalid registry key!" );
                }
            }
        }
        catch ( registry::InvalidRegistryException const & )
        {
            // openKey, getValueType, getBinaryValue

            OSL_FAIL( "TypeDescriptionEnumerationImpl::createInstance "
                        "- Caught InvalidRegistryException!" );
        }

        ++it;
    }

    if ( !bOpenKeySucceeded )
        throw reflection::NoSuchTypeNameException();

    if ( aModuleKeys.empty() )
        throw reflection::InvalidTypeNameException();

    return rtl::Reference< TypeDescriptionEnumerationImpl >(
        new TypeDescriptionEnumerationImpl(
                xTDMgr, aModuleKeys, rTypes, eDepth ) );
}

//=========================================================================
TypeDescriptionEnumerationImpl::TypeDescriptionEnumerationImpl(
    const uno::Reference< container::XHierarchicalNameAccess > & xTDMgr,
    const RegistryKeyList & rModuleKeys,
    const uno::Sequence< uno::TypeClass > & rTypes,
    reflection::TypeDescriptionSearchDepth eDepth )
: m_aModuleKeys( rModuleKeys ),
  m_aTypes( rTypes ),
  m_eDepth( eDepth ),
  m_xTDMgr( xTDMgr )
{
    g_moduleCount.modCnt.acquire( &g_moduleCount.modCnt );
}

//=========================================================================
// virtual
TypeDescriptionEnumerationImpl::~TypeDescriptionEnumerationImpl()
{
    RegistryKeyList::const_iterator it = m_aModuleKeys.begin();
    RegistryKeyList::const_iterator end = m_aModuleKeys.end();
/*
   @@@ in case we enumerate root and queryMore was never called, then
       m_aModuleKeys contains open root keys which where passed from
       tdprov and must not be closed by us.

    while ( it != end )
    {
        try
        {
            if ( (*it)->isValid() )
                (*it)->closeKey();
        }
        catch (...)
        {
            // No exceptions from dtors, please!
            OSL_FAIL( "TypeDescriptionEnumerationImpl::~TypeDescriptionEnumerationImpl "
            "- Caught exception!" );
        }

        it++;
    }
*/
    it = m_aCurrentModuleSubKeys.begin();
    end = m_aCurrentModuleSubKeys.end();
    while ( it != end )
    {
        try
        {
            if ( (*it)->isValid() )
                (*it)->closeKey();
        }
        catch (Exception &)
        {
            // No exceptions from dtors, please!
            OSL_FAIL( "TypeDescriptionEnumerationImpl::~TypeDescriptionEnumerationImpl "
            "- Caught exception!" );
        }

        ++it;
    }

    g_moduleCount.modCnt.release( &g_moduleCount.modCnt );
}

//=========================================================================
//
// XEnumeration (base of XTypeDescriptionEnumeration) methods
//
//=========================================================================

// virtual
sal_Bool SAL_CALL TypeDescriptionEnumerationImpl::hasMoreElements()
    throw ( uno::RuntimeException )
{
    return queryMore();
}

//=========================================================================
// virtual
uno::Any SAL_CALL TypeDescriptionEnumerationImpl::nextElement()
    throw ( container::NoSuchElementException,
            lang::WrappedTargetException,
            uno::RuntimeException )
{
    return uno::Any( uno::makeAny( nextTypeDescription() ) );
}

//=========================================================================
//
// XTypeDescriptionEnumeration methods
//
//=========================================================================

// virtual
uno::Reference< reflection::XTypeDescription > SAL_CALL
TypeDescriptionEnumerationImpl::nextTypeDescription()
    throw ( container::NoSuchElementException,
            uno::RuntimeException )
{
    uno::Reference< reflection::XTypeDescription > xTD( queryNext() );

    if ( xTD.is() )
        return xTD;

    throw container::NoSuchElementException(
        rtl::OUString(
            RTL_CONSTASCII_USTRINGPARAM("No further elements in enumeration!") ),
        static_cast< cppu::OWeakObject * >( this  ) );
}

//=========================================================================
bool TypeDescriptionEnumerationImpl::match(
    RTTypeClass eType1, uno::TypeClass eType2 )
{
    switch ( eType1 )
    {
    case RT_TYPE_INTERFACE:
        return eType2 == uno::TypeClass_INTERFACE;

    case RT_TYPE_MODULE:
        return eType2 == uno::TypeClass_MODULE;

    case RT_TYPE_STRUCT:
        return eType2 == uno::TypeClass_STRUCT;

    case RT_TYPE_ENUM:
        return eType2 == uno::TypeClass_ENUM;

    case RT_TYPE_EXCEPTION:
        return eType2 == uno::TypeClass_EXCEPTION;

    case RT_TYPE_TYPEDEF:
        return eType2 == uno::TypeClass_TYPEDEF;

    case RT_TYPE_SERVICE:
        return eType2 == uno::TypeClass_SERVICE;

    case RT_TYPE_SINGLETON:
        return eType2 == uno::TypeClass_SINGLETON;

    case RT_TYPE_CONSTANTS:
        return eType2 == uno::TypeClass_CONSTANTS;

    case RT_TYPE_UNION:
        return eType2 == uno::TypeClass_UNION;

    default:
        return false;
    }
}

//=========================================================================
bool TypeDescriptionEnumerationImpl::queryMore()
{
    osl::MutexGuard aGuard( m_aMutex );

    for (;;)
    {
        if ( !m_aCurrentModuleSubKeys.empty() || !m_aTypeDescs.empty() )
        {
            // Okay, there is at least one more element.
            return true;
        }

        if ( m_aModuleKeys.empty() )
        {
            // No module keys (therefore no elements) left.
            return false;
        }

        // Note: m_aCurrentModuleSubKeys is always empty AND m_aModuleKeys is
        //       never empty when ariving here.
        //       ==> select new module key, fill m_aCurrentModuleSubKeys

        uno::Sequence< uno::Reference< registry::XRegistryKey > > aKeys;
        try
        {
            aKeys = m_aModuleKeys.front()->openKeys();
            for ( sal_Int32 n = 0; n < aKeys.getLength(); ++n )
            {
                uno::Reference< registry::XRegistryKey > xKey = aKeys[ n ];

                // closes key in it's dtor (which is
                // called even in case of exceptions).
                RegistryKeyCloser aCloser( xKey );

                try
                {
                    if ( xKey->isValid() )
                    {
                        if ( xKey->getValueType()
                                == registry::RegistryValueType_BINARY )
                        {
                            bool bIncludeIt = (m_aTypes.getLength() == 0);
                            bool bNeedTypeClass =
                                ((m_aTypes.getLength() > 0) ||
                                 (m_eDepth
                                    == reflection::TypeDescriptionSearchDepth_INFINITE));
                            if ( bNeedTypeClass )
                            {
                                uno::Sequence< sal_Int8 > aBytes(
                                    xKey->getBinaryValue() );

                                typereg::Reader aReader(
                                    aBytes.getConstArray(), aBytes.getLength(),
                                    false, TYPEREG_VERSION_1);

                                RTTypeClass eTypeClass = aReader.getTypeClass();

                                // Does key match requested types? Empty
                                // sequence means include all.
                                if ( m_aTypes.getLength() > 0 )
                                {
                                    for ( sal_Int32 m = 0;
                                          m < m_aTypes.getLength();
                                          ++m )
                                    {
                                        if ( match(eTypeClass, m_aTypes[ m ]) )
                                        {
                                            bIncludeIt = true;
                                            break;
                                        }
                                    }
                                }

                                if ( m_eDepth ==
                                        reflection::TypeDescriptionSearchDepth_INFINITE )
                                {
                                    if ( eTypeClass == RT_TYPE_MODULE )
                                    {
                                        // Do not close xKey!
                                        aCloser.reset();

                                        // Remember new module key.
                                        m_aModuleKeys.push_back( xKey );
                                    }
                                }
                            }

                            if ( bIncludeIt )
                            {
                                // Do not close xKey!
                                aCloser.reset();

                                m_aCurrentModuleSubKeys.push_back( xKey );
                            }
                        }
                    }
                    else
                    {
                        OSL_FAIL( "TypeDescriptionEnumerationImpl::queryMore "
                            "- Invalid registry key!" );
                    }

                }
                catch ( registry::InvalidRegistryException const & )
                {
                    // getValueType, getBinaryValue

                    OSL_FAIL( "TypeDescriptionEnumerationImpl::queryMore "
                                "- Caught InvalidRegistryException!" );

                    // Don't stop iterating!
                }
            }
        }
        catch ( registry::InvalidRegistryException const & )
        {
            // openKeys

            for ( sal_Int32 n = 0; n < aKeys.getLength(); ++n )
            {
                try
                {
                    aKeys[ n ]->closeKey();
                }
                catch ( registry::InvalidRegistryException const & )
                {
                    OSL_FAIL( "TypeDescriptionEnumerationImpl::queryMore "
                                "- Caught InvalidRegistryException!" );
                }
            }
        }

        /////////////////////////////////////////////////////////////////////
        // Special handling for constants contained directly in module.
        /////////////////////////////////////////////////////////////////////

        // Constants requested?
        bool bIncludeConstants = ( m_aTypes.getLength() == 0 );
        if ( !bIncludeConstants )
        {
            for ( sal_Int32 m = 0; m < m_aTypes.getLength(); ++m )
            {
                if ( m_aTypes[ m ] == uno::TypeClass_CONSTANT )
                {
                    bIncludeConstants = true;
                    break;
                }
            }

        }

        if ( bIncludeConstants )
        {
            if ( m_aModuleKeys.front()->getValueType()
                    == registry::RegistryValueType_BINARY )
            {
                try
                {
                    uno::Sequence< sal_Int8 > aBytes(
                        m_aModuleKeys.front()->getBinaryValue() );

                    typereg::Reader aReader(
                        aBytes.getConstArray(), aBytes.getLength(), false,
                        TYPEREG_VERSION_1);

                    if ( aReader.getTypeClass() == RT_TYPE_MODULE )
                    {
                        sal_uInt16 nFields = aReader.getFieldCount();
                        while ( nFields-- )
                        {
                            rtl::OUStringBuffer aName(
                                aReader.getTypeName().replace( '/', '.' ) );
                            aName.appendAscii( "." );
                            aName.append( aReader.getFieldName( nFields ) );

                            uno::Any aValue(
                                getRTValue(
                                    aReader.getFieldValue( nFields ) ) );

                            m_aTypeDescs.push_back(
                                new IndividualConstantTypeDescriptionImpl(
                                    aName.makeStringAndClear(), aValue,
                                    ( ( aReader.getFieldFlags( nFields )
                                        & RT_ACCESS_PUBLISHED )
                                      != 0 ) ) );
                        }
                    }
                }
                catch ( registry::InvalidRegistryException const & )
                {
                    // getBinaryValue

                    OSL_FAIL( "TypeDescriptionEnumerationImpl::queryMore "
                                "- Caught InvalidRegistryException!" );
                }
            }
        }

        /////////////////////////////////////////////////////////////////////

/*
   @@@ m_aModuleKeys.front() may have open sub keys (may be contained in
       both m_aModuleKeys and m_aCurrentModuleSubKeys)!

        try
        {
           m_aModuleKeys.front()->closeKey();
        }
        catch ( registry::InvalidRegistryException const & )
        {
            OSL_FAIL( "TypeDescriptionEnumerationImpl::queryMore "
                        "- Caught InvalidRegistryException!" );
        }
*/
        // We're done with this module key, even if there were errors.
        m_aModuleKeys.pop_front();
    }

    // unreachable
}

//=========================================================================
uno::Reference< reflection::XTypeDescription >
TypeDescriptionEnumerationImpl::queryNext()
{
    osl::MutexGuard aGuard( m_aMutex );

    for (;;)
    {
        if ( !queryMore() )
            return uno::Reference< reflection::XTypeDescription >();

        uno::Reference< reflection::XTypeDescription > xTD;

        if ( !m_aTypeDescs.empty() )
        {
            xTD = m_aTypeDescs.front();
            m_aTypeDescs.pop_front();
            return xTD;
        }

        // Note: xKey is already opened.
        uno::Reference< registry::XRegistryKey >
            xKey( m_aCurrentModuleSubKeys.front() );
/*
   @@@ xKey may still be contained in m_aModuleKeys, too

        // closes key in it's dtor (which is
        // called even in case of exceptions).
        RegistryKeyCloser aCloser( xKey );
*/
        try
        {
            {
                if ( xKey->isValid() )
                {
                    if ( xKey->getValueType()
                            == registry::RegistryValueType_BINARY )
                    {
                        uno::Sequence< sal_Int8 > aBytes(
                            xKey->getBinaryValue() );

                        xTD = createTypeDescription( aBytes,
                                                     m_xTDMgr,
                                                     false );
                        OSL_ENSURE( xTD.is(),
                            "TypeDescriptionEnumerationImpl::queryNext "
                            "- No XTypeDescription created!" );
                    }
                }
                else
                {
                    OSL_FAIL( "TypeDescriptionEnumerationImpl::queryNext "
                        "- Invalid registry key!" );
                }
            }
        }
        catch ( registry::InvalidRegistryException const & )
        {
            // getValueType, getBinaryValue

            OSL_FAIL( "TypeDescriptionEnumerationImpl::queryNext "
                        "- Caught InvalidRegistryException!" );
        }

        // We're done with this key, even if there were errors.
        m_aCurrentModuleSubKeys.pop_front();

        if ( xTD.is() )
            return xTD;

        // next try...

    } // for (;;)
}

} // namespace stoc_rdbtdp

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
