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

#include <cppuhelper/implbase3.hxx>

#include "comphelper/servicedecl.hxx"

#include "com/sun/star/deployment/UpdateInformationProvider.hpp"
#include "com/sun/star/deployment/XPackage.hpp"
#include "com/sun/star/deployment/XPackageInformationProvider.hpp"
#include "com/sun/star/deployment/ExtensionManager.hpp"
#include "com/sun/star/deployment/XUpdateInformationProvider.hpp"
#include "com/sun/star/lang/XServiceInfo.hpp"
#include "com/sun/star/registry/XRegistryKey.hpp"
#include "com/sun/star/task/XAbortChannel.hpp"
#include "com/sun/star/uno/XComponentContext.hpp"
#include "com/sun/star/ucb/XCommandEnvironment.hpp"
#include "com/sun/star/xml/dom/XElement.hpp"
#include "com/sun/star/xml/dom/XNode.hpp"

#include "com/sun/star/uno/Reference.hxx"
#include "rtl/ustring.hxx"
#include "ucbhelper/content.hxx"

#include "dp_dependencies.hxx"
#include "dp_descriptioninfoset.hxx"
#include "dp_identifier.hxx"
#include "dp_version.hxx"
#include "dp_misc.h"
#include "dp_update.hxx"

namespace beans      = com::sun::star::beans ;
namespace deployment = com::sun::star::deployment ;
namespace lang       = com::sun::star::lang ;
namespace registry   = com::sun::star::registry ;
namespace task       = com::sun::star::task ;
namespace css_ucb    = com::sun::star::ucb ;
namespace uno        = com::sun::star::uno ;
namespace xml = com::sun::star::xml ;

#define UNISTRING(s) rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(s))

namespace dp_info {

class PackageInformationProvider : 
        public ::cppu::WeakImplHelper1< deployment::XPackageInformationProvider >
                                  
{
    public:
                 PackageInformationProvider( uno::Reference< uno::XComponentContext >const& xContext);
    virtual     ~PackageInformationProvider();

    static uno::Sequence< rtl::OUString > getServiceNames();
    static rtl::OUString getImplName();

    // XPackageInformationProvider
    virtual rtl::OUString SAL_CALL getPackageLocation( const rtl::OUString& extensionId )
        throw ( uno::RuntimeException );
    virtual uno::Sequence< uno::Sequence< rtl::OUString > > SAL_CALL isUpdateAvailable( const rtl::OUString& extensionId )
        throw ( uno::RuntimeException );
    virtual uno::Sequence< uno::Sequence< rtl::OUString > > SAL_CALL getExtensionList()
        throw ( uno::RuntimeException );
//---------
private:
    
    uno::Reference< uno::XComponentContext> mxContext;

    rtl::OUString getPackageLocation( const rtl::OUString& repository,
                                      const rtl::OUString& _sExtensionId );

    uno::Reference< deployment::XUpdateInformationProvider > mxUpdateInformation;
};

//------------------------------------------------------------------------------

PackageInformationProvider::PackageInformationProvider( uno::Reference< uno::XComponentContext > const& xContext) : 
    mxContext( xContext ),
    mxUpdateInformation( deployment::UpdateInformationProvider::create( xContext ) )
{
}

//------------------------------------------------------------------------------

PackageInformationProvider::~PackageInformationProvider()
{
}

//------------------------------------------------------------------------------
rtl::OUString PackageInformationProvider::getPackageLocation(
    const rtl::OUString & repository,
    const rtl::OUString& _rExtensionId )
{
    rtl::OUString aLocationURL;
    uno::Reference<deployment::XExtensionManager> xManager =
        deployment::ExtensionManager::get(mxContext);
    
    if ( xManager.is() )
    {
        const uno::Sequence< uno::Reference< deployment::XPackage > > packages(
                xManager->getDeployedExtensions(
                    repository,
                    uno::Reference< task::XAbortChannel >(),
                    uno::Reference< css_ucb::XCommandEnvironment > () ) );

        for ( int pos = packages.getLength(); pos--; )
        {
            try
            {
                const rtl::OUString aName = packages[ pos ]->getName();
                const beans::Optional< rtl::OUString > aID = packages[ pos ]->getIdentifier();
                if ( aID.IsPresent && aID.Value.compareTo( _rExtensionId ) == 0 )
                {
                    aLocationURL = packages[ pos ]->getURL();
                    break;
                }
            }
            catch ( uno::RuntimeException & ) {}
        }
    }

    return aLocationURL;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

rtl::OUString SAL_CALL
PackageInformationProvider::getPackageLocation( const rtl::OUString& _sExtensionId )
    throw ( uno::RuntimeException )
{
    rtl::OUString aLocationURL = getPackageLocation( UNISTRING("user"), _sExtensionId );

    if ( aLocationURL.getLength() == 0 )
    {
        aLocationURL = getPackageLocation( UNISTRING("shared"), _sExtensionId );
    }
    if ( aLocationURL.getLength() == 0 )
    {
        aLocationURL = getPackageLocation( UNISTRING("bundled"), _sExtensionId );
    }
    if ( aLocationURL.getLength() == 0 )
    {
        aLocationURL = getPackageLocation( UNISTRING("bundled_prereg"), _sExtensionId );
    }
    if ( aLocationURL.getLength() )
    {
        ::ucbhelper::Content aContent( aLocationURL, NULL );
        aLocationURL = aContent.getURL();
    }
    return aLocationURL;
}

//------------------------------------------------------------------------------

uno::Sequence< uno::Sequence< rtl::OUString > > SAL_CALL
PackageInformationProvider::isUpdateAvailable( const rtl::OUString& _sExtensionId )
    throw ( uno::RuntimeException )
{
    uno::Sequence< uno::Sequence< rtl::OUString > > aList;
     
    uno::Reference<deployment::XExtensionManager> extMgr =
        deployment::ExtensionManager::get(mxContext);

    if (!extMgr.is())
    {
        OSL_ASSERT(0);
        return aList;
    }
    std::vector<std::pair<uno::Reference<deployment::XPackage>, uno::Any > > errors;
    dp_misc::UpdateInfoMap updateInfoMap;
    if (_sExtensionId.getLength())
    {
        std::vector<uno::Reference<deployment::XPackage> > vecExtensions;
        uno::Reference<deployment::XPackage> extension;
        try
        {
            extension = dp_misc::getExtensionWithHighestVersion(
                extMgr->getExtensionsWithSameIdentifier(
                    _sExtensionId, _sExtensionId, uno::Reference<css_ucb::XCommandEnvironment>()));
            vecExtensions.push_back(extension);
        }
        catch (lang::IllegalArgumentException &)
        {
            OSL_ASSERT(0);
        }
        updateInfoMap = dp_misc::getOnlineUpdateInfos(
            mxContext, extMgr, mxUpdateInformation, &vecExtensions, errors);
    }
    else
    {
        updateInfoMap = dp_misc::getOnlineUpdateInfos(
            mxContext, extMgr, mxUpdateInformation, NULL, errors);
    }

    int nCount = 0;
    for (dp_misc::UpdateInfoMap::iterator i(updateInfoMap.begin()); i != updateInfoMap.end(); i++)
    {
        dp_misc::UpdateInfo const & info = i->second;

        rtl::OUString sOnlineVersion;
        if (info.info.is())
        {
            // check, if there are unsatisfied dependencies and ignore this online update
            dp_misc::DescriptionInfoset infoset(mxContext, info.info);
            uno::Sequence< uno::Reference< xml::dom::XElement > >
                ds( dp_misc::Dependencies::check( infoset ) );
            if ( ! ds.getLength() )
                sOnlineVersion = info.version;
        }
        
        rtl::OUString sVersionUser;
        rtl::OUString sVersionShared;
        rtl::OUString sVersionBundled;
        uno::Sequence< uno::Reference< deployment::XPackage> > extensions;
        try {
            extensions = extMgr->getExtensionsWithSameIdentifier(
                dp_misc::getIdentifier(info.extension), info.extension->getName(),
                uno::Reference<css_ucb::XCommandEnvironment>());
        } catch (lang::IllegalArgumentException& ) {
            OSL_ASSERT(0);
        }
        OSL_ASSERT(extensions.getLength() == 3);
        if (extensions[0].is() )
            sVersionUser = extensions[0]->getVersion();
        if (extensions[1].is() )
            sVersionShared = extensions[1]->getVersion();
        if (extensions[2].is() )
            sVersionBundled = extensions[2]->getVersion();
            
        bool bSharedReadOnly = extMgr->isReadOnlyRepository(OUSTR("shared"));
            
        dp_misc::UPDATE_SOURCE sourceUser = dp_misc::isUpdateUserExtension(
            bSharedReadOnly, sVersionUser, sVersionShared, sVersionBundled, sOnlineVersion);
        dp_misc::UPDATE_SOURCE sourceShared = dp_misc::isUpdateSharedExtension(
            bSharedReadOnly, sVersionShared, sVersionBundled, sOnlineVersion);

        rtl::OUString updateVersionUser;
        rtl::OUString updateVersionShared;
        if (sourceUser != dp_misc::UPDATE_SOURCE_NONE)
            updateVersionUser = dp_misc::getHighestVersion(
                rtl::OUString(), sVersionShared, sVersionBundled, sOnlineVersion);
        if (sourceShared  != dp_misc::UPDATE_SOURCE_NONE)
            updateVersionShared = dp_misc::getHighestVersion(
                rtl::OUString(), rtl::OUString(), sVersionBundled, sOnlineVersion);
        rtl::OUString updateVersion;
        if (dp_misc::compareVersions(updateVersionUser, updateVersionShared) == dp_misc::GREATER)
            updateVersion = updateVersionUser;
        else
            updateVersion = updateVersionShared;
        if (updateVersion.getLength())
        {
            
            rtl::OUString aNewEntry[2];
            aNewEntry[0] = i->first;
            aNewEntry[1] = updateVersion;
            aList.realloc( ++nCount );
            aList[ nCount-1 ] = ::uno::Sequence< rtl::OUString >( aNewEntry, 2 );
        }                
    }
    return aList;
}

//------------------------------------------------------------------------------
uno::Sequence< uno::Sequence< rtl::OUString > > SAL_CALL PackageInformationProvider::getExtensionList()
    throw ( uno::RuntimeException )
{
    const uno::Reference<deployment::XExtensionManager> mgr =
        deployment::ExtensionManager::get(mxContext);

    if (!mgr.is())
        return uno::Sequence< uno::Sequence< rtl::OUString > >();

    const uno::Sequence< uno::Sequence< uno::Reference<deployment::XPackage > > >
        allExt =  mgr->getAllExtensions(
            uno::Reference< task::XAbortChannel >(),
            uno::Reference< css_ucb::XCommandEnvironment > () );
    
    uno::Sequence< uno::Sequence< rtl::OUString > > retList;
    
    sal_Int32 cAllIds = allExt.getLength();
    retList.realloc(cAllIds);

    for (sal_Int32 i = 0; i < cAllIds; i++)
    {
        //The inner sequence contains extensions with the same identifier from
        //all the different repositories, that is user, share, bundled.
        const uno::Sequence< uno::Reference< deployment::XPackage > > &
            seqExtension = allExt[i];
        sal_Int32 cExt = seqExtension.getLength();
        OSL_ASSERT(cExt == 3);
        for (sal_Int32 j = 0; j < cExt; j++)
        {
            //ToDo according to the old code the first found extenions is used
            //even if another one with the same id has a better version.
            uno::Reference< deployment::XPackage > const & xExtension( seqExtension[j] );
            if (xExtension.is())
            {
                rtl::OUString aNewEntry[2];
                aNewEntry[0] = dp_misc::getIdentifier(xExtension);
                aNewEntry[1] = xExtension->getVersion();
                retList[i] = ::uno::Sequence< rtl::OUString >( aNewEntry, 2 );
                break;
            }
        }
    }
    return retList;
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

namespace sdecl = comphelper::service_decl;
sdecl::class_<PackageInformationProvider> servicePIP;
extern sdecl::ServiceDecl const serviceDecl(
    servicePIP,
    // a private one:
    "com.sun.star.comp.deployment.PackageInformationProvider",
    "com.sun.star.comp.deployment.PackageInformationProvider" );

//------------------------------------------------------------------------------
bool singleton_entries(
    uno::Reference< registry::XRegistryKey > const & xRegistryKey )
{
    try {
        uno::Reference< registry::XRegistryKey > xKey(
            xRegistryKey->createKey(
                serviceDecl.getImplementationName() +
                // xxx todo: use future generated function to get singleton name
                UNISTRING("/UNO/SINGLETONS/"
                      "com.sun.star.deployment.PackageInformationProvider") ) );
        xKey->setStringValue( serviceDecl.getSupportedServiceNames()[0] );
        return true;
    }
    catch (registry::InvalidRegistryException & exc) {
        (void) exc; // avoid warnings
        OSL_ENSURE( 0, ::rtl::OUStringToOString(
                        exc.Message, RTL_TEXTENCODING_UTF8 ).getStr() );
        return false;
    }
}

} // namespace dp_info


