/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 .
 */

// include own header
#include <jobs/helponstartup.hxx>
#include <services.h>
#include <targets.h>

#include <officecfg/Office/Common.hxx>
#include <officecfg/Setup.hxx>

// include others
#include <comphelper/sequenceashashmap.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>

// include interfaces
#include <com/sun/star/frame/FrameSearchFlag.hpp>
#include <com/sun/star/frame/ModuleManager.hpp>
#include <com/sun/star/frame/XFramesSupplier.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <cppuhelper/supportsservice.hxx>

namespace framework{

// XInterface, XTypeProvider, XServiceInfo

OUString SAL_CALL HelpOnStartup::getImplementationName()
{
    return u"com.sun.star.comp.framework.HelpOnStartup"_ustr;
}

bool SAL_CALL HelpOnStartup::supportsService( const OUString& sServiceName )
{
    return cppu::supportsService(this, sServiceName);
}

cpo::uno::Sequence< OUString > SAL_CALL HelpOnStartup::getSupportedServiceNames()
{
    return { SERVICENAME_JOB };
}

HelpOnStartup::HelpOnStartup(css::uno::Reference< css::uno::XComponentContext > xContext)
    : m_xContext    (std::move(xContext))
{
    // create some needed uno services and cache it
    m_xModuleManager = css::frame::ModuleManager::create( m_xContext );

    m_xDesktop = css::frame::Desktop::create(m_xContext);

    // ask for office locale
    m_sLocale = officecfg::Setup::L10N::ooLocale::get();

    // detect system
    m_sSystem = officecfg::Office::Common::Help::System::get();

    // Start listening for disposing events of these services,
    // so we can react e.g. for an office shutdown
    css::uno::Reference< css::lang::XComponent > xComponent;
    xComponent.set(m_xModuleManager, css::uno::UNO_QUERY);
    if (xComponent.is())
        xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this));
    if (m_xDesktop.is())
        m_xDesktop->addEventListener(static_cast< css::lang::XEventListener* >(this));
    xComponent.set(m_xConfig, css::uno::UNO_QUERY);
    if (xComponent.is())
        xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this));
}

HelpOnStartup::~HelpOnStartup()
{
}

// css.task.XJob
cpo::uno::Any SAL_CALL HelpOnStartup::execute(const cpo::uno::Sequence< css::beans::NamedValue >& lArguments)
{
    // Analyze the given arguments; try to locate a model there and
    // classify it's used application module.
    OUString sModule = its_getModuleIdFromEnv(lArguments);

    // Attention: we are bound to events for opening any document inside the office.
    // That includes e.g. the help module itself. But we have to do nothing then!
    if (sModule.isEmpty())
        return cpo::uno::Any();

    // check current state of the help module
    // a) help isn't open                       => show default page for the detected module
    // b) help shows any other default page(!) => show default page for the detected module
    // c) help shows any other content         => do nothing (user travelled to any other content and leaved the set of default pages)
    OUString sCurrentHelpURL                = its_getCurrentHelpURL();
    bool        bCurrentHelpURLIsAnyDefaultURL = its_isHelpUrlADefaultOne(sCurrentHelpURL);
    bool        bShowIt                        = false;

    // a)
    if (sCurrentHelpURL.isEmpty())
        bShowIt = true;
    // b)
    else if (bCurrentHelpURLIsAnyDefaultURL)
        bShowIt = true;

    if (bShowIt)
    {
        // retrieve the help URL for the detected application module
        OUString sModuleDependentHelpURL = its_checkIfHelpEnabledAndGetURL(sModule);
        if (!sModuleDependentHelpURL.isEmpty())
        {
            // Show this help page.
            // Note: The help window brings itself to front ...
            Help* pHelp = Application::GetHelp();
            if (pHelp)
                pHelp->Start(sModuleDependentHelpURL);
        }
    }

    return cpo::uno::Any();
}

void SAL_CALL HelpOnStartup::disposing(const css::lang::EventObject& aEvent)
{
    std::unique_lock g(m_mutex);
    if (aEvent.Source == m_xModuleManager)
        m_xModuleManager.clear();
    else if (aEvent.Source == m_xDesktop)
        m_xDesktop.clear();
    else if (aEvent.Source == m_xConfig)
        m_xConfig.clear();
}

OUString HelpOnStartup::its_getModuleIdFromEnv(const cpo::uno::Sequence< css::beans::NamedValue >& lArguments)
{
    ::comphelper::SequenceAsHashMap lArgs        (lArguments);
    ::comphelper::SequenceAsHashMap lEnvironment = lArgs.getUnpackedValueOrDefault(u"Environment"_ustr, cpo::uno::Sequence< css::beans::NamedValue >());

    // check for right environment.
    // If it's not a DocumentEvent, which triggered this job,
    // we can't work correctly! => return immediately and do nothing
    OUString sEnvType = lEnvironment.getUnpackedValueOrDefault(u"EnvType"_ustr, OUString());
    if (sEnvType != "DOCUMENTEVENT")
        return OUString();

    css::uno::Reference< css::frame::XModel > xDoc = lEnvironment.getUnpackedValueOrDefault(u"Model"_ustr, css::uno::Reference< css::frame::XModel >());
    if (!xDoc.is())
        return OUString();

    // be sure that we work on top level documents only, which are registered
    // on the desktop instance. Ignore e.g. life previews, which are top frames too ...
    // but not registered at this global desktop instance.
    css::uno::Reference< css::frame::XDesktop >    xDesktopCheck;
    css::uno::Reference< css::frame::XFrame >      xFrame;
    css::uno::Reference< css::frame::XController > xController  = xDoc->getCurrentController();
    if (xController.is())
        xFrame = xController->getFrame();
    if (xFrame.is() && xFrame->isTop())
        xDesktopCheck.set(xFrame->getCreator(), css::uno::UNO_QUERY);
    if (!xDesktopCheck.is())
        return OUString();

    // OK - now we are sure this document is a top level document.
    // Classify it.
    // SAFE ->
    std::unique_lock aLock(m_mutex);
    css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = m_xModuleManager;
    aLock.unlock();
    // <- SAFE

    OUString sModuleId;
    try
    {
        sModuleId = xModuleManager->identify(xDoc);
    }
    catch(const css::uno::RuntimeException&)
        { throw; }
    catch(const css::uno::Exception&)
        { sModuleId.clear(); }

    return sModuleId;
}

OUString HelpOnStartup::its_getCurrentHelpURL()
{
    // SAFE ->
    std::unique_lock aLock(m_mutex);
    css::uno::Reference< css::frame::XDesktop2 > xDesktop = m_xDesktop;
    aLock.unlock();
    // <- SAFE

    if (!xDesktop.is())
        return OUString();

    css::uno::Reference< css::frame::XFrame > xHelp = xDesktop->findFrame(SPECIALTARGET_HELPTASK, css::frame::FrameSearchFlag::CHILDREN);
    if (!xHelp.is())
        return OUString();

    OUString sCurrentHelpURL;
    try
    {
        css::uno::Reference< css::frame::XFramesSupplier >  xHelpRoot  (xHelp                 , css::uno::UNO_QUERY_THROW);
        css::uno::Reference< css::container::XIndexAccess > xHelpChildren(xHelpRoot->getFrames(), css::uno::UNO_QUERY_THROW);

        css::uno::Reference< css::frame::XFrame >      xHelpChild;
        css::uno::Reference< css::frame::XController > xHelpView;
        css::uno::Reference< css::frame::XModel >      xHelpContent;

        xHelpChildren->getByIndex(0) >>= xHelpChild;
        if (xHelpChild.is())
            xHelpView = xHelpChild->getController();
        if (xHelpView.is())
            xHelpContent = xHelpView->getModel();
        if (xHelpContent.is())
            sCurrentHelpURL = xHelpContent->getURL();
    }
    catch(const css::uno::RuntimeException&)
        { throw; }
    catch(const css::uno::Exception&)
        { sCurrentHelpURL.clear(); }

    return sCurrentHelpURL;
}

bool HelpOnStartup::its_isHelpUrlADefaultOne(std::u16string_view sHelpURL)
{
    if (sHelpURL.empty())
        return false;

    // SAFE ->
    std::unique_lock aLock(m_mutex);
    css::uno::Reference< css::container::XNameAccess >     xConfig = m_xConfig;
    OUString                                        sLocale = m_sLocale;
    OUString                                        sSystem = m_sSystem;
    aLock.unlock();
    // <- SAFE

    if (!xConfig.is())
        return false;

    // check given help url against all default ones
    for (const OUString& rName : xConfig->getElementNames())
    {
        try
        {
            auto xModuleConfig = xConfig->getByName(rName).query<css::container::XNameAccess>();
            if (!xModuleConfig.is())
                continue;

            OUString sHelpBaseURL;
            xModuleConfig->getByName(u"ooSetupFactoryHelpBaseURL"_ustr) >>= sHelpBaseURL;
            OUString sHelpURLForModule = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem);
            if (sHelpURL == sHelpURLForModule)
                return true;
        }
        catch(const css::uno::RuntimeException&)
            { throw; }
        catch(const css::uno::Exception&)
            {}
    }

    return false;
}

OUString HelpOnStartup::its_checkIfHelpEnabledAndGetURL(const OUString& sModule)
{
    // SAFE ->
    std::unique_lock aLock(m_mutex);
    css::uno::Reference< css::container::XNameAccess > xConfig = m_xConfig;
    OUString                                    sLocale = m_sLocale;
    OUString                                    sSystem = m_sSystem;
    aLock.unlock();
    // <- SAFE

    OUString sHelpURL;

    try
    {
        css::uno::Reference< css::container::XNameAccess > xModuleConfig;
        if (xConfig.is())
            xConfig->getByName(sModule) >>= xModuleConfig;

        bool bHelpEnabled = false;
        if (xModuleConfig.is())
            xModuleConfig->getByName(u"ooSetupFactoryHelpOnOpen"_ustr) >>= bHelpEnabled;

        if (bHelpEnabled)
        {
            OUString sHelpBaseURL;
            xModuleConfig->getByName(u"ooSetupFactoryHelpBaseURL"_ustr) >>= sHelpBaseURL;
            sHelpURL = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem);
        }
    }
    catch(const css::uno::RuntimeException&)
        { throw; }
    catch(const css::uno::Exception&)
        { sHelpURL.clear(); }

    return sHelpURL;
}

OUString HelpOnStartup::ist_createHelpURL(std::u16string_view sBaseURL,
                                                 std::u16string_view sLocale ,
                                                 std::u16string_view sSystem )
{
    return OUString::Concat(sBaseURL) + "?Language=" + sLocale + "&System=" + sSystem;
}

} // namespace framework

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
framework_HelpOnStartup_get_implementation(
    css::uno::XComponentContext* context, cpo::uno::Sequence<cpo::uno::Any> const& )
{
    return cppu::acquire(new framework::HelpOnStartup(context));
}

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