/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "Application.h"

#include "Autorun.h"
#include "CompileInfo.h"
#include "DatabaseManager.h"
#include "FileItem.h"
#include "FileItemList.h"
#include "GUIInfoManager.h"
#include "GUILargeTextureManager.h"
#include "GUIPassword.h"
#include "GUIUserMessages.h"
#include "HDRStatus.h"
#include "LangInfo.h"
#include "PartyModeManager.h"
#include "PlayListPlayer.h"
#include "SectionLoader.h"
#include "SeekHandler.h"
#include "ServiceBroker.h"
#include "ServiceManager.h"
#include "TextureCache.h"
#include "URL.h"
#include "Util.h"
#include "addons/AddonManager.h"
#include "addons/AddonSystemSettings.h"
#include "addons/RepositoryUpdater.h"
#include "addons/Service.h"
#include "addons/Skin.h"
#include "addons/VFSEntry.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "addons/gui/GUIDialogAddonSettings.h"
#include "application/AppInboundProtocol.h"
#include "application/AppParams.h"
#include "application/ApplicationActionListeners.h"
#include "application/ApplicationMessageHandling.h"
#include "application/ApplicationPlay.h"
#include "application/ApplicationPlayer.h"
#include "application/ApplicationPowerHandling.h"
#include "application/ApplicationSkinHandling.h"
#include "application/ApplicationStackHelper.h"
#include "application/ApplicationVolumeHandling.h"
#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
#include "cores/FFmpeg.h"
#include "cores/playercorefactory/PlayerCoreFactory.h"
#include "dialogs/GUIDialogBusy.h"
#include "dialogs/GUIDialogCache.h"
#include "dialogs/GUIDialogKaiToast.h"
#include "events/EventLog.h"
#include "events/NotificationEvent.h"
#ifdef HAVE_LIBBLURAY
#include "filesystem/BlurayDiscCache.h"
#endif
#include "filesystem/Directory.h"
#include "filesystem/DirectoryCache.h"
#include "filesystem/DllLibCurl.h"
#include "filesystem/File.h"
#include "music/MusicFileItemClassify.h"
#include "network/DNSNameCache.h"
#include "network/NetworkFileItemClassify.h"
#include "playlists/PlayListFileItemClassify.h"
#include "video/VideoFileItemClassify.h"
#ifdef HAS_FILESYSTEM_NFS
#include "filesystem/NFSFile.h"
#endif
#include "filesystem/PluginDirectory.h"
#include "filesystem/SpecialProtocol.h"
#include "guilib/GUIAudioManager.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIControlProfiler.h"
#include "guilib/GUIFontManager.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/StereoscopicsManager.h"
#include "guilib/TextureManager.h"
#include "input/InertialScrollingHandler.h"
#include "input/InputManager.h"
#include "input/actions/Action.h"
#include "input/actions/ActionIDs.h"
#include "input/actions/ActionTranslator.h"
#include "input/keyboard/KeyboardLayoutManager.h"
#include "interfaces/AnnouncementManager.h"
#include "interfaces/builtins/Builtins.h"
#include "interfaces/generic/ScriptInvocationManager.h"
#include "interfaces/json-rpc/JSONRPC.h"
#ifdef HAS_PYTHON
#include "interfaces/python/XBPython.h"
#endif
#include "messaging/ApplicationMessenger.h"
#include "messaging/ThreadMessage.h"
#include "messaging/helpers/DialogHelper.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "music/MusicLibraryQueue.h"
#include "music/MusicThumbLoader.h"
#include "music/MusicUtils.h"
#include "music/infoscanner/MusicInfoScanner.h"
#include "music/tags/MusicInfoTag.h"
#include "network/EventServer.h"
#include "network/Network.h"
#include "network/ZeroconfBrowser.h"
#ifdef HAS_UPNP
#include "filesystem/UPnPDirectory.h"
#include "network/upnp/UPnP.h"
#endif
#include "jobs/JobManager.h"
#include "peripherals/Peripherals.h"
#include "pictures/SlideShowDelegator.h"
#include "platform/Environment.h"
#include "playlists/PlayList.h"
#include "playlists/PlayListFactory.h"
#include "playlists/SmartPlayList.h"
#include "powermanagement/PowerManager.h"
#include "profiles/ProfileManager.h"
#include "pvr/PVRManager.h"
#include "pvr/guilib/PVRGUIActionsPlayback.h"
#include "pvr/guilib/PVRGUIActionsPowerManagement.h"
#include "rendering/RenderSystem.h"
#include "rendering/capture/CaptureMetadata.h"
#include "rendering/capture/CapturePixels.h"
#include "rendering/capture/CaptureService.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/AdvancedSettings.h"
#include "settings/DisplaySettings.h"
#include "settings/MediaSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "settings/lib/Setting.h"
#include "speech/ISpeechRecognition.h"
#include "storage/MediaManager.h"
#include "utils/AlarmClock.h"
#include "utils/CPUInfo.h"
#include "utils/CharsetConverter.h"
#include "utils/ContentUtils.h"
#include "utils/FileExtensionProvider.h"
#include "utils/LangCodeExpander.h"
#include "utils/PlayerUtils.h"
#include "utils/RegExp.h"
#include "utils/Screenshot.h"
#include "utils/StringUtils.h"
#include "utils/SystemInfo.h"
#include "utils/TimeUtils.h"
#include "utils/URIUtils.h"
#include "utils/Variant.h"
#include "utils/log.h"
#include "video/PlayerController.h"
#include "video/VideoLibraryQueue.h"
#include "video/dialogs/GUIDialogVideoBookmarks.h"
#ifdef TARGET_WINDOWS
#include "win32util.h"
#endif
#include "windowing/GraphicContext.h"
#include "windowing/WinSystem.h"
#include "windowing/WindowSystemFactory.h"
#if defined(TARGET_ANDROID)
#include "platform/android/activity/XBMCApp.h"
#endif
#ifdef TARGET_DARWIN
#include "platform/darwin/DarwinUtils.h"
#endif
#ifdef TARGET_POSIX
#include "platform/posix/PlatformPosix.h"
#include "platform/posix/XHandle.h"
#endif
#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
#include "platform/posix/filesystem/SMBFile.h"
#endif
#ifndef TARGET_POSIX
#include "platform/win32/threads/Win32Exception.h"
#endif

#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <memory>
#include <mutex>

#include <tinyxml.h>

//TODO: XInitThreads
#ifdef HAVE_X11
#include <X11/Xlib.h>
#endif
#ifdef HAS_OPTICAL_DRIVE
#include <cdio/logging.h>
#endif

using namespace ADDON;
using namespace XFILE;
#ifdef HAS_OPTICAL_DRIVE
using namespace MEDIA_DETECT;
#endif
using namespace MUSIC_INFO;
using namespace EVENTSERVER;
using namespace JSONRPC;
using namespace PVR;
using namespace PERIPHERALS;
using namespace KODI;
using namespace KODI::MESSAGING;
using namespace ActiveAE;

using namespace XbmcThreads;
using namespace std::chrono_literals;

using KODI::MESSAGING::HELPERS::DialogResponse;

using namespace std::chrono_literals;

#define MAX_FFWD_SPEED 5

CApplication::CApplication(void)
  :
#ifdef HAS_OPTICAL_DRIVE
    m_Autorun(new CAutorun()),
#endif
    m_pInertialScrollingHandler(new CInertialScrollingHandler()),
    m_WaitingExternalCalls(0),
    m_itemCurrentFile(std::make_shared<CFileItem>()),
    m_playerEvent(true, true)
{
  TiXmlBase::SetCondenseWhiteSpace(false);

#ifdef HAVE_X11
  XInitThreads();
#endif

  // register application components
  RegisterComponent(std::make_shared<CApplicationActionListeners>(m_critSection));
  RegisterComponent(std::make_shared<CApplicationPlayer>());
  RegisterComponent(std::make_shared<CApplicationPowerHandling>());
  RegisterComponent(std::make_shared<CApplicationSkinHandling>(this, this, m_bInitializing));
  RegisterComponent(std::make_shared<CApplicationVolumeHandling>());
  RegisterComponent(std::make_shared<CApplicationStackHelper>());
}

CApplication::~CApplication(void)
{
  DeregisterComponent(typeid(CApplicationStackHelper));
  DeregisterComponent(typeid(CApplicationVolumeHandling));
  DeregisterComponent(typeid(CApplicationSkinHandling));
  DeregisterComponent(typeid(CApplicationPowerHandling));
  DeregisterComponent(typeid(CApplicationPlayer));
  DeregisterComponent(typeid(CApplicationActionListeners));
}

extern "C" void __stdcall init_emu_environ();
extern "C" void __stdcall update_emu_environ();
extern "C" void __stdcall cleanup_emu_environ();

bool CApplication::Create()
{
  m_bStop = false;

  RegisterSettings();

  CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo());

  // Register JobManager service
  CServiceBroker::RegisterJobManager(std::make_shared<CJobManager>());

  // Screen capture service
  CServiceBroker::RegisterCaptureService(
      std::make_shared<KODI::RENDERING::CAPTURE::CCaptureService>());

  // Announcement service
  m_pAnnouncementManager = std::make_shared<ANNOUNCEMENT::CAnnouncementManager>();
  m_pAnnouncementManager->Start();
  CServiceBroker::RegisterAnnouncementManager(m_pAnnouncementManager);

  const auto appMessenger = std::make_shared<CApplicationMessenger>();
  CServiceBroker::RegisterAppMessenger(appMessenger);

  const auto keyboardLayoutManager = std::make_shared<KEYBOARD::CKeyboardLayoutManager>();
  CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager);

  CServiceBroker::RegisterDNSNameCache(std::make_shared<CDNSNameCache>());

  m_ServiceManager = std::make_unique<CServiceManager>();

  if (!m_ServiceManager->InitStageOne())
  {
    return false;
  }

  // here we register all global classes for the CApplicationMessenger,
  // after that we can send messages to the corresponding modules
  appMessenger->RegisterReceiver(this);
  appMessenger->RegisterReceiver(&CServiceBroker::GetPlaylistPlayer());
  appMessenger->SetGUIThread(CThread::GetCurrentThreadId());
  appMessenger->SetProcessThread(CThread::GetCurrentThreadId());

  // copy required files
  CUtil::CopyUserDataIfNeeded("special://masterprofile/", "RssFeeds.xml");
  CUtil::CopyUserDataIfNeeded("special://masterprofile/", "favourites.xml");
  CUtil::CopyUserDataIfNeeded("special://masterprofile/", "Lircmap.xml");

  CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://logpath"));

#ifdef TARGET_POSIX //! @todo Win32 has no special://home/ mapping by default, so we
  //!       must create these here. Ideally this should be using special://home/ and
  //!      be platform agnostic (i.e. unify the InitDirectories*() functions)
  if (CServiceBroker::GetAppParams()->GetUserDirectoriesLocation() !=
      UserDirectoriesLocation::PLATFORM)
#endif
  {
    CDirectory::Create("special://xbmc/addons");
  }

  // Init our DllLoaders emu env
  init_emu_environ();

  PrintStartupLog();

  // initialize network protocols
  avformat_network_init();
  // set avutil callback
  av_log_set_callback(ff_avutil_log);

  CLog::Log(LOGINFO, "loading settings");
  const auto settingsComponent = CServiceBroker::GetSettingsComponent();
  if (!settingsComponent->Load())
    return false;

  // Log Cache GUI settings (replacement of cache in advancedsettings.xml)
  const auto settings = settingsComponent->GetSettings();
  const float readFactor = settings->GetInt(CSettings::SETTING_FILECACHE_READFACTOR) / 100.0f;
  CLog::Log(LOGINFO,
            "New Cache GUI Settings (replacement of cache in advancedsettings.xml) are:\n  Buffer "
            "Mode: {}\n  Memory Size: {} MB\n  Read "
            "Factor: {:.2f} x {}\n  Chunk Size : {} bytes",
            settings->GetInt(CSettings::SETTING_FILECACHE_BUFFERMODE),
            settings->GetInt(CSettings::SETTING_FILECACHE_MEMORYSIZE), readFactor,
            (readFactor < 1.0f) ? "(adaptive)" : "",
            settings->GetInt(CSettings::SETTING_FILECACHE_CHUNKSIZE));

  CLog::Log(LOGINFO, "creating subdirectories");
  const std::shared_ptr<CProfileManager> profileManager = settingsComponent->GetProfileManager();
  CLog::Log(LOGINFO, "userdata folder: {}",
            CURL::GetRedacted(profileManager->GetProfileUserDataFolder()));
  CLog::Log(LOGINFO, "recording folder: {}",
            CURL::GetRedacted(settings->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH)));
  CLog::Log(LOGINFO, "screenshots folder: {}",
            CURL::GetRedacted(settings->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH)));
  CDirectory::Create(profileManager->GetUserDataFolder());
  CDirectory::Create(profileManager->GetProfileUserDataFolder());
  profileManager->CreateProfileFolders();

  update_emu_environ();//apply the GUI settings

  // application message handling service
  m_pMsgHandling = std::make_shared<CApplicationMessageHandling>(*this);
  CServiceBroker::RegisterAppPort(std::static_pointer_cast<CAppInboundProtocol>(m_pMsgHandling));

#ifdef HAVE_LIBBLURAY
  CServiceBroker::RegisterBlurayDiscCache(std::make_shared<CBlurayDiscCache>());
#endif

  if (!m_ServiceManager->InitStageTwo(
          settingsComponent->GetProfileManager()->GetProfileUserDataFolder()))
  {
    return false;
  }

  m_pActiveAE = std::make_unique<ActiveAE::CActiveAE>();
  CServiceBroker::RegisterAE(m_pActiveAE.get());

  // initialize m_replayGainSettings
  GetComponent<CApplicationVolumeHandling>()->CacheReplayGainSettings(*settings);

  // load the keyboard layouts
  if (!keyboardLayoutManager->Load())
  {
    CLog::Log(LOGFATAL, "CApplication::Create: Unable to load keyboard layouts");
    return false;
  }

  // set user defined CA trust bundle
  std::string caCert =
      CSpecialProtocol::TranslatePath(settingsComponent->GetAdvancedSettings()->m_caTrustFile);
  if (!caCert.empty())
  {
    if (XFILE::CFile::Exists(caCert))
    {
      CEnvironment::setenv("SSL_CERT_FILE", caCert, 1);
      CLog::Log(LOGDEBUG, "CApplication::Create - SSL_CERT_FILE: {}", caCert);
    }
    else
    {
      CLog::Log(LOGDEBUG, "CApplication::Create - Error reading SSL_CERT_FILE: {} -> ignored",
                caCert);
    }
  }

  CUtil::InitRandomSeed();

  m_lastRenderTime = std::chrono::steady_clock::now();
  return true;
}

bool CApplication::CreateGUI()
{
  m_frameMoveGuard.lock();

  const auto appPower = GetComponent<CApplicationPowerHandling>();
  appPower->SetRenderGUI(true);

  auto windowSystems = KODI::WINDOWING::CWindowSystemFactory::GetWindowSystems();

  const std::string& windowing = CServiceBroker::GetAppParams()->GetWindowing();

  if (!windowing.empty())
    windowSystems = {windowing};

  for (auto& windowSystem : windowSystems)
  {
    CLog::Log(LOGDEBUG, "CApplication::{} - trying to init {} windowing system", __FUNCTION__,
              windowSystem);
    m_pWinSystem = KODI::WINDOWING::CWindowSystemFactory::CreateWindowSystem(windowSystem);

    if (!m_pWinSystem)
      continue;

    if (!windowing.empty() && windowing != windowSystem)
      continue;

    CServiceBroker::RegisterWinSystem(m_pWinSystem.get());

    if (!m_pWinSystem->InitWindowSystem())
    {
      CLog::Log(LOGDEBUG, "CApplication::{} - unable to init {} windowing system", __FUNCTION__,
                windowSystem);
      m_pWinSystem->DestroyWindowSystem();
      m_pWinSystem.reset();
      CServiceBroker::UnregisterWinSystem();
      continue;
    }
    else
    {
      CLog::Log(LOGINFO, "CApplication::{} - using the {} windowing system", __FUNCTION__,
                windowSystem);
      break;
    }
  }

  if (!m_pWinSystem)
  {
    CLog::Log(LOGFATAL, "CApplication::{} - unable to init windowing system", __FUNCTION__);
    CServiceBroker::UnregisterWinSystem();
    return false;
  }

  // Retrieve the matching resolution based on GUI settings
  bool sav_res = false;
  CDisplaySettings::GetInstance().SetCurrentResolution(CDisplaySettings::GetInstance().GetDisplayResolution());
  CLog::Log(LOGINFO, "Checking resolution {}",
            CDisplaySettings::GetInstance().GetCurrentResolution());
  if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution()))
  {
    CLog::Log(LOGINFO, "Setting safe mode {}", RES_DESKTOP);
    // defer saving resolution after window was created
    CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
    sav_res = true;
  }

  // update the window resolution
  const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  CServiceBroker::GetWinSystem()->SetWindowResolution(settings->GetInt(CSettings::SETTING_WINDOW_WIDTH), settings->GetInt(CSettings::SETTING_WINDOW_HEIGHT));

  if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_startFullScreen && CDisplaySettings::GetInstance().GetCurrentResolution() == RES_WINDOW)
  {
    // defer saving resolution after window was created
    CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
    sav_res = true;
  }

  if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsValidResolution(CDisplaySettings::GetInstance().GetCurrentResolution()))
  {
    // Oh uh - doesn't look good for starting in their wanted screenmode
    CLog::Log(LOGERROR, "The screen resolution requested is not valid, resetting to a valid mode");
    CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP);
    sav_res = true;
  }
  if (!InitWindow())
  {
    return false;
  }

  // Set default screen saver mode
  auto screensaverModeSetting = std::static_pointer_cast<CSettingString>(settings->GetSetting(CSettings::SETTING_SCREENSAVER_MODE));
  // Can only set this after windowing has been initialized since it depends on it
  if (CServiceBroker::GetWinSystem()->GetOSScreenSaver())
  {
    // If OS has a screen saver, use it by default
    screensaverModeSetting->SetDefault("");
  }
  else
  {
    // If OS has no screen saver, use Kodi one by default
    screensaverModeSetting->SetDefault("screensaver.xbmc.builtin.dim");
  }

  if (sav_res)
    CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true);

  m_pGUI = std::make_unique<CGUIComponent>();
  m_pGUI->Init();

  // Splash requires gui component!!
  CServiceBroker::GetRenderSystem()->ShowSplash("");

  // The key mappings may already have been loaded by a peripheral
  CLog::Log(LOGINFO, "load keymapping");
  if (!CServiceBroker::GetInputManager().LoadKeymaps())
    return false;

  RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
  CLog::Log(LOGINFO, "GUI format {}x{}, Display {}", info.iWidth, info.iHeight, info.strMode);

  return true;
}

bool CApplication::InitWindow(RESOLUTION res)
{
  if (res == RES_INVALID)
    res = CDisplaySettings::GetInstance().GetCurrentResolution();

  bool bFullScreen = res != RES_WINDOW;
  if (!CServiceBroker::GetWinSystem()->CreateNewWindow(CSysInfo::GetAppName(),
                                                      bFullScreen, CDisplaySettings::GetInstance().GetResolutionInfo(res)))
  {
    CLog::Log(LOGFATAL, "CApplication::Create: Unable to create window");
    return false;
  }

  if (!CServiceBroker::GetRenderSystem()->InitRenderSystem())
  {
    CLog::Log(LOGFATAL, "CApplication::Create: Unable to init rendering system");
    return false;
  }
  // set GUI res and force the clear of the screen
  CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(res, false);
  return true;
}

bool CApplication::Initialize()
{
  m_pActiveAE->Start();
  // restore AE's previous volume state

  const auto appVolume = GetComponent<CApplicationVolumeHandling>();
  const auto level = appVolume->GetVolumeRatio();
  const auto muted = appVolume->IsMuted();
  appVolume->SetHardwareVolume(level);
  CServiceBroker::GetActiveAE()->SetMute(muted);

#if defined(HAS_OPTICAL_DRIVE) && \
    !defined(TARGET_WINDOWS) // somehow this throws an "unresolved external symbol" on win32
  // turn off cdio logging
  cdio_loglevel_default = CDIO_LOG_ERROR;
#endif

  // load the language and its translated strings
  if (!LoadLanguage(false))
    return false;

  // load media manager sources (e.g. root addon type sources depend on language strings to be available)
  CServiceBroker::GetMediaManager().LoadSources();

  const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();

  profileManager->GetEventLog().Add(EventPtr(new CNotificationEvent(
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(177),
                          g_sysinfo.GetAppName()),
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(178),
                          g_sysinfo.GetAppName()),
      "special://xbmc/media/icon256x256.png", EventLevel::Basic)));

  m_ServiceManager->GetNetwork().WaitForNet();

  // initialize (and update as needed) our databases
  CDatabaseManager &databaseManager = m_ServiceManager->GetDatabaseManager();

  bool allDatabasesInitialized{false};
  CEvent event(true);
  CServiceBroker::GetJobManager()->Submit(
      [&allDatabasesInitialized, &databaseManager, &event]()
      {
        allDatabasesInitialized = databaseManager.Initialize();
        event.Set();
      });

  const std::string& connecting{
      CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(24186)};
  const std::string& updating{
      CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(24150)};
  int iDots = 1;
  while (!event.Wait(1000ms))
  {
    if (databaseManager.IsConnecting() || databaseManager.IsUpgrading())
    {
      CServiceBroker::GetRenderSystem()->ShowSplash(
          std::string(iDots, ' ') + (databaseManager.IsConnecting() ? connecting : updating) +
          std::string(iDots, '.'));
    }

    if (iDots == 3)
      iDots = 1;
    else
      ++iDots;
  }
  CServiceBroker::GetRenderSystem()->ShowSplash("");

  if (!allDatabasesInitialized)
  {
    // Bail out if any of the databases failed to initialize properly.
    CLog::Log(LOGFATAL, "Failed to initialize databases");

    const std::string& dbInitFailedExiting{
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(24187)};

    unsigned int secondsLeftUntilExit{10};
    while (secondsLeftUntilExit)
    {
      CServiceBroker::GetRenderSystem()->ShowSplash(
          StringUtils::Format(dbInitFailedExiting, secondsLeftUntilExit));
      KODI::TIME::Sleep(1s);
      secondsLeftUntilExit--;
    }
    return false;
  }

  // Initialize GUI font manager to build/update fonts cache
  //! @todo Move GUIFontManager into service broker and drop the global reference
  event.Reset();
  GUIFontManager& guiFontManager = g_fontManager;
  CServiceBroker::GetJobManager()->Submit([&guiFontManager, &event]() {
    guiFontManager.Initialize();
    event.Set();
  });

  std::string localizedStr{CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(39175)};
  iDots = 1;
  while (!event.Wait(1000ms))
  {
    if (g_fontManager.IsUpdating())
      CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr +
                                                    std::string(iDots, '.'));

    if (iDots == 3)
      iDots = 1;
    else
      ++iDots;
  }
  CServiceBroker::GetRenderSystem()->ShowSplash("");

  // GUI depends on seek handler
  GetComponent<CApplicationPlayer>()->GetSeekHandler().Configure();

  const auto skinHandling = GetComponent<CApplicationSkinHandling>();

  const bool guiCreated = CServiceBroker::GetGUI()->GetWindowManager().Initialized();
  bool uiInitializationFinished = !guiCreated;

  if (guiCreated)
  {
    const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();

    CServiceBroker::GetGUI()->GetWindowManager().CreateWindows();

    skinHandling->m_confirmSkinChange = false;

    std::vector<AddonInfoPtr> incompatibleAddons;
    event.Reset();

    // Addon migration
    if (CServiceBroker::GetAddonMgr().GetIncompatibleEnabledAddonInfos(incompatibleAddons))
    {
      if (CAddonSystemSettings::GetInstance().GetAddonAutoUpdateMode() == AUTO_UPDATES_ON)
      {
        CServiceBroker::GetJobManager()->Submit(
            [&event, &incompatibleAddons]() {
              if (CServiceBroker::GetRepositoryUpdater().CheckForUpdates())
                CServiceBroker::GetRepositoryUpdater().Await();

              incompatibleAddons = CServiceBroker::GetAddonMgr().MigrateAddons();
              event.Set();
            },
            CJob::PRIORITY_DEDICATED);
        localizedStr = CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(24151);
        iDots = 1;
        while (!event.Wait(1000ms))
        {
          CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr +
                                                        std::string(iDots, '.'));
          if (iDots == 3)
            iDots = 1;
          else
            ++iDots;
        }
        m_incompatibleAddons = incompatibleAddons;
      }
      else
      {
        // If no update is active disable all incompatible addons during start
        m_incompatibleAddons =
            CServiceBroker::GetAddonMgr().DisableIncompatibleAddons(incompatibleAddons);
      }
    }

    // Start splashscreen and load skin
    CServiceBroker::GetRenderSystem()->ShowSplash("");
    skinHandling->m_confirmSkinChange = true;

    auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN);
    if (!setting)
    {
      CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN);
      return false;
    }

    CServiceBroker::RegisterTextureCache(std::make_shared<CTextureCache>());

    std::string skinId = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN);
    if (!skinHandling->LoadSkin(skinId))
    {
      CLog::Log(LOGERROR, "Failed to load skin '{}'", skinId);
      std::string defaultSkin =
          std::static_pointer_cast<const CSettingString>(setting)->GetDefault();
      if (!skinHandling->LoadSkin(defaultSkin))
      {
        CLog::Log(LOGFATAL, "Default skin '{}' could not be loaded! Terminating..", defaultSkin);
        return false;
      }
    }

    // initialize splash window after splash screen disappears
    // because we need a real window in the background which gets
    // rendered while we load the main window or enter the master lock key
    CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SPLASH);
  }

  // Must stay above the window activation below: that can raise a modal dialog, whose nested
  // render loop reaches anything after it only once the dialog has been dismissed.
  CJSONRPC::Initialize();

  CServiceBroker::RegisterSpeechRecognition(speech::ISpeechRecognition::CreateInstance());

  if (!m_ServiceManager->InitStageThree(profileManager))
  {
    CLog::Log(LOGERROR, "Application - Init3 failed");
  }

  if (guiCreated)
  {
    const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();

    if (settings->GetBool(CSettings::SETTING_MASTERLOCK_STARTUPLOCK) &&
        profileManager->GetMasterProfile().getLockMode() != LockMode::EVERYONE &&
        !profileManager->GetMasterProfile().getLockCode().empty())
    {
      g_passwordManager.CheckStartUpLock();
    }

    // check if we should use the login screen
    if (profileManager->UsingLoginScreen())
    {
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_LOGIN_SCREEN);
    }
    else
    {
      // activate the configured start window
      auto skin = CServiceBroker::GetGUI()->GetSkinInfo();
      int firstWindow = skin ? skin->GetFirstWindow() : WINDOW_HOME;
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(firstWindow);

      if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_STARTUP_ANIM))
      {
        CLog::Log(LOGWARNING, "CApplication::Initialize - startup.xml taints init process");
      }

      // the startup window is considered part of the initialization as it most likely switches to the final window
      uiInitializationFinished = firstWindow != WINDOW_STARTUP_ANIM;
    }
  }

  g_sysinfo.Refresh();

  CLog::Log(LOGINFO, "removing tempfiles");
  CUtil::RemoveTempFiles();

  if (!profileManager->UsingLoginScreen())
  {
    UpdateLibraries();
    SetLoggingIn(false);
  }

  m_slowTimer.StartZero();

  // register action listeners
  const auto appListener = GetComponent<CApplicationActionListeners>();
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  appListener->RegisterActionListener(&appPlayer->GetSeekHandler());
  appListener->RegisterActionListener(&CPlayerController::GetInstance());

  CServiceBroker::GetRepositoryUpdater().Start();
  if (!profileManager->UsingLoginScreen())
    CServiceBroker::GetServiceAddons().Start();

  CLog::Log(LOGINFO, "initialize done");

  const auto appPower = GetComponent<CApplicationPowerHandling>();
  appPower->CheckOSScreenSaverInhibitionSetting();
  // reset our screensaver (starts timers etc.)
  appPower->ResetScreenSaver();

  // if the user interfaces has been fully initialized let everyone know
  if (uiInitializationFinished)
  {
    CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UI_READY);
    CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
  }

  return true;
}

bool CApplication::OnSettingsSaving() const
{
  // don't save settings when we're busy stopping the application
  // a lot of screens try to save settings on deinit and deinit is
  // called for every screen when the application is stopping
  return !m_bStop;
}

namespace
{
// capture tap: serve pending requests from the finished frame before it is
// presented.
void ServiceCaptureTaps()
{
  using namespace KODI::RENDERING::CAPTURE;

  const auto captureService = CServiceBroker::GetCaptureService();
  if (!captureService)
    return;

  const auto requests = captureService->TakeActive(CaptureContent::COMPOSITE);
  if (requests.empty())
    return;
  const auto& request = requests.front(); // at most one consumer per frame

  auto* winSystem = CServiceBroker::GetWinSystem();
  auto surface = CScreenShot::CreateSurface();
  if (!winSystem || !surface)
  {
    captureService->Fail(request);
    return;
  }

  const ScreenshotContext ctx{*winSystem};
  if (!surface->Read(ctx))
  {
    captureService->Fail(request);
    return;
  }

  CaptureResult result;
  result.pixels =
      std::make_shared<CHeapCapturePixels>(std::unique_ptr<uint8_t[]>(surface->TakeBuffer()));
  result.width = static_cast<unsigned int>(surface->GetWidth());
  result.height = static_cast<unsigned int>(surface->GetHeight());
  result.stride = surface->GetStride();
  result.format = surface->GetFormat();
  result.color = GetOutputColorMetadata(*winSystem);
  result.content = CaptureContent::COMPOSITE; // this tap is the composite half

  captureService->Complete(request, result);
}

} // namespace

void CApplication::Render()
{
  // do not render if we are stopped or in background
  if (m_bStop)
    return;

  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto appPower = GetComponent<CApplicationPowerHandling>();

  bool hasRendered = false;

  // Whether externalplayer is playing and we're unfocused
  bool extPlayerActive = appPlayer->IsExternalPlaying() && !m_AppFocused;

  if (!extPlayerActive && CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() &&
      !appPlayer->IsPausedPlayback())
  {
    appPower->ResetScreenSaver();
  }

  if (!CServiceBroker::GetRenderSystem()->BeginRender())
    return;

  // render gui layer
  const bool guiWillRender = appPower->GetRenderGUI() && !m_skipGuiRender;
  bool compositing = CServiceBroker::GetWinSystem()->BeginGuiComposite(guiWillRender);

  if (guiWillRender)
  {
    if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RenderStereoMode::OFF)
    {
      CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RenderStereoView::LEFT);
      hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();

      if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RenderStereoMode::MONO)
      {
        CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RenderStereoView::RIGHT);
        hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
      }
      CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RenderStereoView::OFF);
    }
    else
    {
      hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
    }
    // execute post rendering actions (finalize window closing)
    CServiceBroker::GetGUI()->GetWindowManager().AfterRender();

    m_lastRenderTime = std::chrono::steady_clock::now();
  }

  if (compositing)
    CServiceBroker::GetWinSystem()->EndGuiComposite();

  // render video layer
  CServiceBroker::GetGUI()->GetWindowManager().RenderEx();

  if (compositing)
    CServiceBroker::GetWinSystem()->CompositeGui();

  // serve pending requests from the finished frame, then fail any left
  // unserved. Both need a frame that really drew: on a skipped frame nothing
  // could be served, so FrameComplete would kill live requests.
  if (hasRendered)
  {
    ServiceCaptureTaps();
    if (const auto captureService = CServiceBroker::GetCaptureService())
      captureService->FrameComplete();
  }

  CServiceBroker::GetRenderSystem()->EndRender();

  // reset our info cache - we do this at the end of Render so that it is
  // fresh for the next process(), or after a windowclose animation (where process()
  // isn't called)
  CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
  infoMgr.ResetCache();
  infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache();

  if (hasRendered)
  {
    infoMgr.GetInfoProviders().GetSystemInfoProvider().UpdateFPS();
  }

  CServiceBroker::GetWinSystem()->GetGfxContext().Flip(hasRendered,
                                                       appPlayer->IsRenderingVideoLayer());

  CTimeUtils::UpdateFrameTime(hasRendered);
}

bool CApplication::OnAction(const CAction &action)
{
  // special case for switching between GUI & fullscreen mode.
  if (action.GetID() == ACTION_SHOW_GUI)
  { // Switch to fullscreen mode if we can
    CGUIComponent* gui = CServiceBroker::GetGUI();
    if (gui)
    {
      if (gui->GetWindowManager().SwitchToFullScreen())
      {
        GetComponent<CApplicationPowerHandling>()->m_navigationTimer.StartZero();
        return true;
      }
    }
  }

  const auto appPlayer = GetComponent<CApplicationPlayer>();

  if (action.GetID() == ACTION_TOGGLE_FULLSCREEN)
  {
    CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen();
    appPlayer->TriggerUpdateResolution();
    return true;
  }

  if (action.IsMouse())
    CServiceBroker::GetInputManager().SetMouseActive(true);

  if (action.GetID() == ACTION_CREATE_EPISODE_BOOKMARK)
  {
    CGUIDialogVideoBookmarks::OnAddEpisodeBookmark();
  }
  if (action.GetID() == ACTION_CREATE_BOOKMARK)
  {
    CGUIDialogVideoBookmarks::OnAddBookmark();
  }

  // The action PLAYPAUSE behaves as ACTION_PAUSE if we are currently
  // playing or ACTION_PLAYER_PLAY if we are seeking (FF/RW) or not playing.
  if (action.GetID() == ACTION_PLAYER_PLAYPAUSE)
  {
    CSlideShowDelegator& slideShow = CServiceBroker::GetSlideShowDelegator();
    if ((appPlayer->IsPlaying() && appPlayer->GetPlaySpeed() == 1) ||
        (slideShow.InSlideShow() && !slideShow.IsPaused()))
      return OnAction(CAction(ACTION_PAUSE));
    else
      return OnAction(CAction(ACTION_PLAYER_PLAY));
  }

  //if the action would start or stop inertial scrolling
  //by gesture - bypass the normal OnAction handler of current window
  if( !m_pInertialScrollingHandler->CheckForInertialScrolling(&action) )
  {
    // in normal case
    // just pass the action to the current window and let it handle it
    if (CServiceBroker::GetGUI()->GetWindowManager().OnAction(action))
    {
      GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer();
      return true;
    }
  }

  // handle extra global presses

  // notify action listeners
  if (GetComponent<CApplicationActionListeners>()->NotifyActionListeners(action))
    return true;

  // screenshot : take a screenshot :)
  if (action.GetID() == ACTION_TAKE_SCREENSHOT)
  {
    CScreenShot::TakeScreenshot();
    return true;
  }
  // Display HDR : toggle HDR on/off
  if (action.GetID() == ACTION_HDR_TOGGLE)
  {
    // Only enables manual HDR toggle if no video is playing or auto HDR switch is disabled
    if (appPlayer->IsPlayingVideo() && CServiceBroker::GetWinSystem()->IsHDRDisplaySettingEnabled())
      return true;

    HDR_STATUS hdrStatus = CServiceBroker::GetWinSystem()->ToggleHDR();

    if (hdrStatus == HDR_STATUS::HDR_OFF)
    {
      CGUIDialogKaiToast::QueueNotification(
          CGUIDialogKaiToast::Info,
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(34220),
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(34221));
    }
    else if (hdrStatus == HDR_STATUS::HDR_ON)
    {
      CGUIDialogKaiToast::QueueNotification(
          CGUIDialogKaiToast::Info,
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(34220),
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(34222));
    }
    return true;
  }
  // Tone Mapping : switch to next tone map method
  if (action.GetID() == ACTION_CYCLE_TONEMAP_METHOD)
  {
    // Only enables tone mapping switch if display is not HDR capable or HDR is not enabled
    if (CServiceBroker::GetWinSystem()->IsHDRDisplaySettingEnabled())
      return true;

    if (appPlayer->IsPlayingVideo())
    {
      CVideoSettings vs = appPlayer->GetVideoSettings();
      vs.m_ToneMapMethod = static_cast<ETONEMAPMETHOD>(static_cast<int>(vs.m_ToneMapMethod) + 1);
      if (vs.m_ToneMapMethod >= VS_TONEMAPMETHOD_MAX)
        vs.m_ToneMapMethod =
            static_cast<ETONEMAPMETHOD>(static_cast<int>(VS_TONEMAPMETHOD_OFF) + 1);

      appPlayer->SetVideoSettings(vs);

      int code = 0;
      switch (vs.m_ToneMapMethod)
      {
        case VS_TONEMAPMETHOD_REINHARD:
          code = 36555;
          break;
        case VS_TONEMAPMETHOD_ACES:
          code = 36557;
          break;
        case VS_TONEMAPMETHOD_HABLE:
          code = 36558;
          break;
        default:
          throw std::logic_error("Tonemapping method not found. Did you forget to add a mapping?");
      }
      CGUIDialogKaiToast::QueueNotification(
          CGUIDialogKaiToast::Info,
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(34224),
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(code), 1000, false, 500);
    }
    return true;
  }
  // built in functions : execute the built-in
  if (action.GetID() == ACTION_BUILT_IN_FUNCTION)
  {
    if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(action.GetName()) ||
        CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
    {
      CBuiltins::GetInstance().Execute(action.GetName());
      GetComponent<CApplicationPowerHandling>()->ResetNavigationTimer();
    }
    return true;
  }

  // reload keymaps
  if (action.GetID() == ACTION_RELOAD_KEYMAPS)
    CServiceBroker::GetInputManager().ReloadKeymaps();

  // show info : Shows the current video or song information
  if (action.GetID() == ACTION_SHOW_INFO)
  {
    CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPlayerInfoProvider().ToggleShowInfo();
    return true;
  }

  if (action.GetID() == ACTION_SET_RATING && appPlayer->IsPlayingAudio())
  {
    int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_itemCurrentFile->GetMusicInfoTag()->GetUserrating());
    if (userrating < 0) // Nothing selected, so user rating unchanged
      return true;
    userrating = std::min(userrating, 10);
    if (userrating != m_itemCurrentFile->GetMusicInfoTag()->GetUserrating())
    {
      m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating);
      // Mirror changes to GUI item
      CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);

      // Asynchronously update song userrating in music library
      MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, userrating);

      // Tell all windows (e.g. playlistplayer, media windows) to update the fileitem
      CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
      CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
    }
    return true;
  }

  else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) &&
           appPlayer->IsPlayingAudio())
  {
    int userrating = m_itemCurrentFile->GetMusicInfoTag()->GetUserrating();
    bool needsUpdate(false);
    if (userrating > 0 && action.GetID() == ACTION_DECREASE_RATING)
    {
      m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating - 1);
      needsUpdate = true;
    }
    else if (userrating < 10 && action.GetID() == ACTION_INCREASE_RATING)
    {
      m_itemCurrentFile->GetMusicInfoTag()->SetUserrating(userrating + 1);
      needsUpdate = true;
    }
    if (needsUpdate)
    {
      // Mirror changes to current GUI item
      CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);

      // Asynchronously update song userrating in music library
      MUSIC_UTILS::UpdateSongRatingJob(m_itemCurrentFile, m_itemCurrentFile->GetMusicInfoTag()->GetUserrating());

      // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows)
      CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
      CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
    }

    return true;
  }
  else if ((action.GetID() == ACTION_INCREASE_RATING || action.GetID() == ACTION_DECREASE_RATING) &&
           appPlayer->IsPlayingVideo())
  {
    int rating = m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating;
    bool needsUpdate(false);
    if (rating > 1 && action.GetID() == ACTION_DECREASE_RATING)
    {
      m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating - 1;
      needsUpdate = true;
    }
    else if (rating < 10 && action.GetID() == ACTION_INCREASE_RATING)
    {
      m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating = rating + 1;
      needsUpdate = true;
    }
    if (needsUpdate)
    {
      // Mirror changes to GUI item
      CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);

      CVideoDatabase db;
      if (db.Open())
      {
        db.SetVideoUserRating(m_itemCurrentFile->GetVideoInfoTag()->m_iDbId,
                              m_itemCurrentFile->GetVideoInfoTag()->m_iUserRating,
                              m_itemCurrentFile->GetVideoInfoTag()->m_type);
        db.Close();
      }
      // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows)
      CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_itemCurrentFile);
      CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
    }
    return true;
  }

  // Now check with the playlist player if action can be handled.
  // In case of ACTION_PREV_ITEM, we only allow the playlist player to take it if we're less than ACTION_PREV_ITEM_THRESHOLD seconds into playback.
  if (!(action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek() &&
        GetTime() > ACTION_PREV_ITEM_THRESHOLD))
  {
    if (CServiceBroker::GetPlaylistPlayer().OnAction(action))
      return true;
  }

  // Now check with the player if action can be handled.
  bool bIsPlayingPVRChannel = (CServiceBroker::GetPVRManager().IsStarted() &&
                               CurrentFileItem().IsPVRChannel());

  bool bNotifyPlayer = false;
  if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
    bNotifyPlayer = true;
  else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME)
    bNotifyPlayer = true;
  else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION && bIsPlayingPVRChannel)
    bNotifyPlayer = true;
  else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_VIDEO_OSD ||
          (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_DIALOG_MUSIC_OSD && bIsPlayingPVRChannel))
  {
    switch (action.GetID())
    {
      case ACTION_NEXT_ITEM:
      case ACTION_PREV_ITEM:
      case ACTION_CHANNEL_UP:
      case ACTION_CHANNEL_DOWN:
        bNotifyPlayer = true;
        break;
      default:
        break;
    }
  }
  else if (action.GetID() == ACTION_STOP)
    bNotifyPlayer = true;

  if (bNotifyPlayer)
  {
    if (appPlayer->OnAction(action))
      return true;
  }

  // stop : stops playing current audio song
  if (action.GetID() == ACTION_STOP)
  {
    StopPlaying();
    return true;
  }

  // In case the playlist player nor the player didn't handle PREV_ITEM, because we are past the ACTION_PREV_ITEM_THRESHOLD secs limit.
  // If so, we just jump to the start of the track.
  if (action.GetID() == ACTION_PREV_ITEM && appPlayer->CanSeek())
  {
    SeekTime(0);
    appPlayer->SetPlaySpeed(1);
    return true;
  }

  // forward action to graphic context and see if it can handle it
  if (CServiceBroker::GetGUI()->GetStereoscopicsManager().OnAction(action))
    return true;

  if (appPlayer->IsPlaying())
  {
    // forward channel switches to the player - he knows what to do
    if (action.GetID() == ACTION_CHANNEL_UP || action.GetID() == ACTION_CHANNEL_DOWN)
    {
      appPlayer->OnAction(action);
      return true;
    }

    // pause : toggle pause action
    if (action.GetID() == ACTION_PAUSE)
    {
      appPlayer->Pause();
      // go back to normal play speed on unpause
      if (!appPlayer->IsPaused() && appPlayer->GetPlaySpeed() != 1)
        appPlayer->SetPlaySpeed(1);

      CGUIComponent *gui = CServiceBroker::GetGUI();
      if (gui)
        gui->GetAudioManager().Enable(appPlayer->IsPaused());
      return true;
    }
    // play: unpause or set playspeed back to normal
    if (action.GetID() == ACTION_PLAYER_PLAY)
    {
      // if currently paused - unpause
      if (appPlayer->IsPaused())
        return OnAction(CAction(ACTION_PAUSE));
      // if we do a FF/RW then go back to normal speed
      if (appPlayer->GetPlaySpeed() != 1)
        appPlayer->SetPlaySpeed(1);
      return true;
    }
    if (!appPlayer->IsPaused())
    {
      if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND)
      {
        float playSpeed = appPlayer->GetPlaySpeed();

        if (action.GetID() == ACTION_PLAYER_REWIND && (playSpeed == 1)) // Enables Rewinding
          playSpeed *= -2;
        else if (action.GetID() == ACTION_PLAYER_REWIND && playSpeed > 1) //goes down a notch if you're FFing
          playSpeed /= 2;
        else if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed < 1) //goes up a notch if you're RWing
          playSpeed /= 2;
        else
          playSpeed *= 2;

        if (action.GetID() == ACTION_PLAYER_FORWARD && playSpeed == -1) //sets iSpeed back to 1 if -1 (didn't plan for a -1)
          playSpeed = 1;
        if (playSpeed > 32 || playSpeed < -32)
          playSpeed = 1;

        appPlayer->SetPlaySpeed(playSpeed);
        return true;
      }
      else if ((action.GetAmount() || appPlayer->GetPlaySpeed() != 1) &&
               (action.GetID() == ACTION_ANALOG_REWIND || action.GetID() == ACTION_ANALOG_FORWARD))
      {
        // calculate the speed based on the amount the button is held down
        int iPower = (int)(action.GetAmount() * MAX_FFWD_SPEED + 0.5f);
        // amount can be negative, for example rewind and forward share the same axis
        iPower = std::abs(iPower);
        // returns 0 -> MAX_FFWD_SPEED
        int iSpeed = 1 << iPower;
        if (iSpeed != 1 && action.GetID() == ACTION_ANALOG_REWIND)
          iSpeed = -iSpeed;
        appPlayer->SetPlaySpeed(static_cast<float>(iSpeed));
        if (iSpeed == 1)
          CLog::Log(LOGDEBUG,"Resetting playspeed");
        return true;
      }
      else if (action.GetID() == ACTION_PLAYER_INCREASE_TEMPO)
      {
        CPlayerUtils::AdvanceTempoStep(appPlayer, TempoStepChange::INCREASE);
        return true;
      }
      else if (action.GetID() == ACTION_PLAYER_DECREASE_TEMPO)
      {
        CPlayerUtils::AdvanceTempoStep(appPlayer, TempoStepChange::DECREASE);
        return true;
      }
    }
    // allow play to unpause
    else
    {
      if (action.GetID() == ACTION_PLAYER_PLAY)
      {
        // unpause, and set the playspeed back to normal
        appPlayer->Pause();

        CGUIComponent *gui = CServiceBroker::GetGUI();
        if (gui)
          gui->GetAudioManager().Enable(appPlayer->IsPaused());

        appPlayer->SetPlaySpeed(1);
        return true;
      }
    }
  }


  if (action.GetID() == ACTION_SWITCH_PLAYER)
  {
    const CPlayerCoreFactory &playerCoreFactory = m_ServiceManager->GetPlayerCoreFactory();

    if (appPlayer->IsPlaying())
    {
      std::vector<std::string> players;
      CFileItem item(*m_itemCurrentFile.get());
      playerCoreFactory.GetPlayers(item, players);
      std::string player = playerCoreFactory.SelectPlayerDialog(players);
      if (!player.empty())
      {
        item.SetStartOffset(CUtil::ConvertSecsToMilliSecs(GetTime()));
        PlayFile(item, player, true);
      }
    }
    else
    {
      std::vector<std::string> players;
      playerCoreFactory.GetRemotePlayers(players);
      std::string player = playerCoreFactory.SelectPlayerDialog(players);
      if (!player.empty())
      {
        PlayFile(CFileItem(), player, false);
      }
    }
  }

  if (CServiceBroker::GetPeripherals().OnAction(action))
    return true;

  if (action.GetID() == ACTION_MUTE)
  {
    const auto appVolume = GetComponent<CApplicationVolumeHandling>();
    appVolume->ToggleMute();
    appVolume->ShowVolumeBar(&action);
    return true;
  }

  if (action.GetID() == ACTION_TOGGLE_DIGITAL_ANALOG)
  {
    const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
    bool passthrough = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH);
    settings->SetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH, !passthrough);

    if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SETTINGS_SYSTEM)
    {
      CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0,0,WINDOW_INVALID,CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
      CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
    }
    return true;
  }

  // Check for global volume control
  if ((action.GetAmount() && (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN)) || action.GetID() == ACTION_VOLUME_SET)
  {
    const auto appVolume = GetComponent<CApplicationVolumeHandling>();
    if (!appPlayer->IsPassthrough())
    {
      if (appVolume->IsMuted())
        appVolume->UnMute();
      float volume = appVolume->GetVolumeRatio();
      int volumesteps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS);
      // sanity check
      if (volumesteps == 0)
        volumesteps = 90;

// Android has steps based on the max available volume level
#if defined(TARGET_ANDROID)
      float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM -
                    CApplicationVolumeHandling::VOLUME_MINIMUM) /
                   CXBMCApp::GetMaxSystemVolume();
#else
      float step = (CApplicationVolumeHandling::VOLUME_MAXIMUM -
                    CApplicationVolumeHandling::VOLUME_MINIMUM) /
                   volumesteps;

      if (action.GetRepeat())
        step *= action.GetRepeat() * 50; // 50 fps
#endif
      if (action.GetID() == ACTION_VOLUME_UP)
        volume += action.GetAmount() * action.GetAmount() * step;
      else if (action.GetID() == ACTION_VOLUME_DOWN)
        volume -= action.GetAmount() * action.GetAmount() * step;
      else
        volume = action.GetAmount() * step;
      if (volume != appVolume->GetVolumeRatio())
        appVolume->SetVolume(volume, false);
    }
    // show visual feedback of volume or passthrough indicator
    appVolume->ShowVolumeBar(&action);
    return true;
  }

  if (action.GetID() == ACTION_GUIPROFILE_BEGIN)
  {
    CGUIControlProfiler::Instance().SetOutputFile(CSpecialProtocol::TranslatePath("special://home/guiprofiler.xml"));
    CGUIControlProfiler::Instance().Start();
    return true;
  }
  if (action.GetID() == ACTION_SHOW_PLAYLIST)
  {
    const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
    if (playlistId == PLAYLIST::Id::TYPE_VIDEO &&
        CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_VIDEO_PLAYLIST)
    {
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
    }
    else if (playlistId == PLAYLIST::Id::TYPE_MUSIC &&
             CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() !=
                 WINDOW_MUSIC_PLAYLIST)
    {
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST);
    }
    return true;
  }
  return false;
}

int CApplication::GetMessageMask()
{
  return TMSG_MASK_APPLICATION;
}

void CApplication::OnApplicationMessage(ThreadMessage* pMsg)
{
  m_pMsgHandling->OnApplicationMessage(pMsg);
}

void CApplication::LockFrameMoveGuard()
{
  ++m_WaitingExternalCalls;
  m_frameMoveGuard.lock();
  ++m_ProcessedExternalCalls;
  CServiceBroker::GetWinSystem()->GetGfxContext().lock();
}

void CApplication::UnlockFrameMoveGuard()
{
  --m_WaitingExternalCalls;
  CServiceBroker::GetWinSystem()->GetGfxContext().unlock();
  m_frameMoveGuard.unlock();
}

void CApplication::FrameMove(bool processEvents, bool processGUI)
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
  if (processEvents)
  {
    // currently we calculate the repeat time (ie time from last similar keypress) just global as fps
    float frameTime = m_frameTime.GetElapsedSeconds();
    m_frameTime.StartZero();
    // never set a frametime less than 2 fps to avoid problems when debugging and on breaks
    if (frameTime > 0.5f)
      frameTime = 0.5f;

    if (processGUI && renderGUI)
    {
      std::unique_lock lock(CServiceBroker::GetWinSystem()->GetGfxContext());
      // check if there are notifications to display
      CGUIDialogKaiToast *toast = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>(WINDOW_DIALOG_KAI_TOAST);
      if (toast && toast->DoWork())
      {
        if (!toast->IsDialogRunning())
        {
          toast->Open();
        }
      }
    }

    m_pMsgHandling->HandleEvents();
    CServiceBroker::GetInputManager().Process(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(), frameTime);

    if (processGUI && renderGUI)
    {
      m_pInertialScrollingHandler->ProcessInertialScroll(frameTime);
      appPlayer->GetSeekHandler().FrameMove();
    }

    // Open the door for external calls e.g python exactly here.
    // Window size can be between 2 and 10ms and depends on number of continuous requests
    if (m_WaitingExternalCalls)
    {
      CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext());
      m_frameMoveGuard.unlock();

      // Calculate a window size between 2 and 10ms, 4 continuous requests let the window grow by 1ms
      // When not playing video we allow it to increase to 80ms
      unsigned int max_sleep = 10;
      if (!appPlayer->IsPlayingVideo() || appPlayer->IsPausedPlayback())
        max_sleep = 80;
      unsigned int sleepTime = std::max(static_cast<unsigned int>(2), std::min(m_ProcessedExternalCalls >> 2, max_sleep));
      KODI::TIME::Sleep(std::chrono::milliseconds(sleepTime));
      m_frameMoveGuard.lock();
      m_ProcessedExternalDecay = 5;
    }
    if (m_ProcessedExternalDecay && --m_ProcessedExternalDecay == 0)
      m_ProcessedExternalCalls = 0;
  }

  if (processGUI && renderGUI)
  {
    m_skipGuiRender = false;

    // a new request, or a latched one-shot still awaiting service, marks the
    // window manager dirty so an otherwise-idle GUI really renders a frame to tap
    if (const auto captureService = CServiceBroker::GetCaptureService();
        captureService && captureService->LatchFrame())
      CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();

    /*! @todo look into the possibility to use this for GBM
    int fps = 0;

    // This code reduces rendering fps of the GUI layer when playing videos in fullscreen mode
    // it makes only sense on architectures with multiple layers
    if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() && !m_appPlayer.IsPausedPlayback() && m_appPlayer.IsRenderingVideoLayer())
      fps = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_LIMITGUIUPDATE);

    auto now = std::chrono::steady_clock::now();

    auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastRenderTime).count();
    if (fps > 0 && frameTime * fps < 1000)
      m_skipGuiRender = true;
    */

    if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiSmartRedraw && m_guiRefreshTimer.IsTimePast())
    {
      CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_TIMER, 0, 0);
      m_guiRefreshTimer.Set(500ms);
    }

    if (!m_bStop)
      CServiceBroker::GetGUI()->GetWindowManager().Process(CTimeUtils::GetFrameTime());

    // Dirty-driven skip: on paths with a persistent framebuffer (D2P plane or
    // HDR GUI compositing FBO), skip Render when no controls dirtied themselves
    // this frame. The persistence keeps the previous OSD on screen for free.
    if (!m_skipGuiRender && appPlayer->IsRenderingVideoLayer() &&
        !CServiceBroker::GetGUI()->GetWindowManager().HasDirtyRegions())
      m_skipGuiRender = true;
    CServiceBroker::GetGUI()->GetWindowManager().FrameMove();
  }

  appPlayer->FrameMove();

  // this will go away when render systems gets its own thread
  CServiceBroker::GetWinSystem()->DriveRenderLoop();
}


void CApplication::ResetCurrentItem()
{
  m_itemCurrentFile = std::make_unique<CFileItem>();
  if (m_pGUI)
    m_pGUI->GetInfoManager().ResetCurrentItem();
}

int CApplication::Run()
{
  CLog::Log(LOGINFO, "Running the application...");

  std::chrono::time_point<std::chrono::steady_clock> lastFrameTime;
  std::chrono::milliseconds frameTime;
  const unsigned int noRenderFrameTime = 15; // Simulates ~66fps

  CFileItemList& playlist = CServiceBroker::GetAppParams()->GetPlaylist();
  if (playlist.Size() > 0)
  {
    CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST::Id::TYPE_MUSIC, playlist);
    CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::TYPE_MUSIC);
    CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_PLAY, -1);
  }

  // Run the app
  while (!m_bStop)
  {
    // Animate and render a frame

    lastFrameTime = std::chrono::steady_clock::now();
    Process();

    bool renderGUI = GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
    if (!m_bStop)
    {
      FrameMove(true, renderGUI);
    }

    if (renderGUI && !m_bStop)
    {
      Render();
    }
    else if (!renderGUI)
    {
      auto now = std::chrono::steady_clock::now();
      frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFrameTime);
      if (frameTime.count() < noRenderFrameTime)
        KODI::TIME::Sleep(std::chrono::milliseconds(noRenderFrameTime - frameTime.count()));
    }
  }

  Cleanup();

  CLog::Log(LOGINFO, "Exiting the application...");
  return m_ExitCode;
}

bool CApplication::Cleanup()
{
  try
  {
    ResetCurrentItem();
    StopPlaying();

    if (m_ServiceManager)
      m_ServiceManager->DeinitStageThree();

#ifdef HAVE_LIBBLURAY
    CServiceBroker::UnregisterBlurayDiscCache();
#endif

    CServiceBroker::UnregisterSpeechRecognition();

    CLog::Log(LOGINFO, "unload skin");
    GetComponent<CApplicationSkinHandling>()->UnloadSkin();

    CServiceBroker::UnregisterTextureCache();

    CServiceBroker::UnregisterCaptureService();

    // stop all remaining scripts; must be done after skin has been unloaded,
    // not before some windows still need it when deinitializing during skin
    // unloading
    CScriptInvocationManager::GetInstance().Uninitialize();

    const auto appPower = GetComponent<CApplicationPowerHandling>();
    appPower->m_globalScreensaverInhibitor.Release();
    appPower->m_screensaverInhibitor.Release();

    CRenderSystemBase *renderSystem = CServiceBroker::GetRenderSystem();
    if (renderSystem)
      renderSystem->DestroyRenderSystem();

    CWinSystemBase *winSystem = CServiceBroker::GetWinSystem();
    if (winSystem)
      winSystem->DestroyWindow();

    if (m_pGUI)
      m_pGUI->GetWindowManager().DestroyWindows();

    CLog::Log(LOGINFO, "unload sections");

    //  Shutdown as much as possible of the
    //  application, to reduce the leaks dumped
    //  to the vc output window before calling
    //  _CrtDumpMemoryLeaks(). Most of the leaks
    //  shown are no real leaks, as parts of the app
    //  are still allocated.

    CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Clear();
    CLangCodeExpander::Clear();
    g_charsetConverter.clear();
    g_directoryCache.Clear();
    //CServiceBroker::GetInputManager().ClearKeymaps(); //! @todo
    CEventServer::RemoveInstance();
    CServiceBroker::GetPlaylistPlayer().Clear();

    if (m_ServiceManager)
      m_ServiceManager->DeinitStageTwo();

#ifdef TARGET_POSIX
    CXHandle::DumpObjectTracker();

#ifdef HAS_OPTICAL_DRIVE
    CLibcdio::ReleaseInstance();
#endif
#endif
#ifdef _CRTDBG_MAP_ALLOC
    _CrtDumpMemoryLeaks();
    while(1); // execution ends
#endif

    if (m_pGUI)
    {
      m_pGUI->Deinit();
      m_pGUI.reset();
    }

    if (winSystem)
    {
      winSystem->DestroyWindowSystem();
      CServiceBroker::UnregisterWinSystem();
      winSystem = nullptr;
      m_pWinSystem.reset();
    }

    // Cleanup was called more than once on exit during my tests
    if (m_ServiceManager)
    {
      m_ServiceManager->DeinitStageOne();
      m_ServiceManager.reset();
    }

    CServiceBroker::UnregisterDNSNameCache();

    CServiceBroker::UnregisterKeyboardLayoutManager();

    CServiceBroker::UnregisterAppMessenger();

    CServiceBroker::UnregisterAnnouncementManager();
    m_pAnnouncementManager->Deinitialize();
    m_pAnnouncementManager.reset();

    CServiceBroker::UnregisterJobManager();
    CServiceBroker::UnregisterCPUInfo();

    UnregisterSettings();

    m_bInitializing = true;

    return true;
  }
  catch (...)
  {
    CLog::Log(LOGERROR, "Exception in CApplication::Cleanup()");
    return false;
  }
}

bool CApplication::Stop(int exitCode)
{
#if defined(TARGET_ANDROID)
  // Note: On Android, the app must be stopped asynchronously, once Android has
  // signalled that the app shall be destroyed. See android_main() implementation.
  if (!CXBMCApp::Get().Stop(exitCode))
    return false;
#endif

  CLog::Log(LOGINFO, "Stopping the application...");

  bool success = true;

  CLog::Log(LOGINFO, "Stopping player");
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  appPlayer->ClosePlayer();

  {
    // close inbound port
    CServiceBroker::UnregisterAppPort();
    XbmcThreads::EndTime<> timer(1000ms);
    while (m_pMsgHandling.use_count() > 1)
    {
      KODI::TIME::Sleep(100ms);
      if (timer.IsTimePast())
      {
        CLog::Log(LOGERROR, "CApplication::Stop - CAppPort still in use, app may crash");
        break;
      }
    }
    m_pMsgHandling->Close();
  }

  try
  {
    m_frameMoveGuard.unlock();

    CVariant vExitCode(CVariant::VariantTypeObject);
    vExitCode["exitcode"] = exitCode;
    CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnQuit", vExitCode);

    // Abort any active screensaver
    GetComponent<CApplicationPowerHandling>()->WakeUpScreenSaverAndDPMS();

    g_alarmClock.StopThread();

    CLog::Log(LOGINFO, "Storing total System Uptime");
    g_sysinfo.SetTotalUptime(g_sysinfo.GetTotalUptime() + (int)(CTimeUtils::GetFrameTime() / 60000));

    // Update the settings information (volume, uptime etc. need saving)
    if (CFile::Exists(CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetSettingsFile()))
    {
      CLog::Log(LOGINFO, "Saving settings");
      CServiceBroker::GetSettingsComponent()->GetSettings()->Save();
    }
    else
      CLog::Log(LOGINFO, "Not saving settings (settings.xml is not present)");

    // kodi may crash or deadlock during exit (shutdown / reboot) due to
    // either a bug in core or misbehaving addons. so try saving
    // skin settings early
    CLog::Log(LOGINFO, "Saving skin settings");
    if (CGUIComponent* gui = CServiceBroker::GetGUI())
    {
      auto skin = gui->GetSkinInfo();
      if (skin)
        skin->SaveSettings();
    }

    m_bStop = true;
    // Add this here to keep the same ordering behaviour for now
    // Needs cleaning up
    CServiceBroker::GetAppMessenger()->Stop();
    m_AppFocused = false;
    m_ExitCode = exitCode;
    CLog::Log(LOGINFO, "Stopping all");

    // Stop scanning before the job manager is cancelled below
    // otherwise scans underway are not stopped
    CMusicLibraryQueue::GetInstance().CancelAllJobs();
    CVideoLibraryQueue::GetInstance().CancelAllJobs();

    // cancel any jobs from the jobmanager
    CServiceBroker::GetJobManager()->CancelJobs();

    CServiceBroker::GetAppMessenger()->Cleanup();

    m_ServiceManager->GetNetwork().NetworkMessage(CNetworkBase::SERVICES_DOWN, 0);

#ifdef HAS_ZEROCONF
    if(CZeroconfBrowser::IsInstantiated())
    {
      CLog::Log(LOGINFO, "Stopping zeroconf browser");
      CZeroconfBrowser::GetInstance()->Stop();
      CZeroconfBrowser::ReleaseInstance();
    }
#endif

    for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
      vfsAddon->DisconnectAll();

#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
    smb.Deinit();
#endif

    // Stop services before unloading Python
    CServiceBroker::GetServiceAddons().Stop();

    // Stop any other python scripts that may be looping waiting for monitor.abortRequested()
    CScriptInvocationManager::GetInstance().StopRunningScripts();

    // unregister action listeners
    const auto appListener = GetComponent<CApplicationActionListeners>();
    appListener->UnregisterActionListener(&GetComponent<CApplicationPlayer>()->GetSeekHandler());
    appListener->UnregisterActionListener(&CPlayerController::GetInstance());

    if (CGUIComponent* gui = CServiceBroker::GetGUI())
      gui->GetAudioManager().DeInitialize();

    // shutdown the AudioEngine
    CServiceBroker::UnregisterAE();
    m_pActiveAE->Shutdown();
    m_pActiveAE.reset();

    CLog::Log(LOGINFO, "Application stopped");
  }
  catch (...)
  {
    CLog::Log(LOGERROR, "Exception in CApplication::Stop()");
    success = false;
  }

  cleanup_emu_environ();

  KODI::TIME::Sleep(200ms);

  return success;
}

namespace
{
class CCreateAndLoadPlayList : public IRunnable
{
public:
  CCreateAndLoadPlayList(const CFileItem& item, std::unique_ptr<PLAYLIST::CPlayList>& playlist)
    : m_item(item),
      m_playlist(playlist)
  {
  }

  void Run() override
  {
    std::unique_ptr<PLAYLIST::CPlayList> playlist(PLAYLIST::CPlayListFactory::Create(m_item));
    if (playlist)
    {
      if (playlist->Load(m_item.GetPath()))
        m_playlist = std::move(playlist);
    }
  }

private:
  const CFileItem& m_item;
  std::unique_ptr<PLAYLIST::CPlayList>& m_playlist;
};
} // namespace

bool CApplication::PlayMedia(CFileItem& item, const std::string& player, PLAYLIST::Id playlistId)
{
  // if the item is a plugin we need to resolve the plugin paths
  if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item))
    return false;

  if (PLAYLIST::IsSmartPlayList(item))
  {
    CFileItemList items;
    CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS);
    if (items.Size())
    {
      PLAYLIST::CSmartPlaylist smartpl;
      //get name and type of smartplaylist, this will always succeed as GetDirectory also did this.
      smartpl.OpenAndReadName(item.GetURL());
      PLAYLIST::CPlayList playlist;
      playlist.Add(items);
      PLAYLIST::Id smartplPlaylistId = PLAYLIST::Id::TYPE_VIDEO;

      if (smartpl.GetType() == "songs" || smartpl.GetType() == "albums" ||
          smartpl.GetType() == "artists")
        smartplPlaylistId = PLAYLIST::Id::TYPE_MUSIC;

      return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId);
    }
  }
  else if ((PLAYLIST::IsPlayList(item) && !item.IsGame()) || NETWORK::IsInternetStream(item))
  {
    // Not owner. Dialog auto-deletes itself.
    CGUIDialogCache* dlgCache = new CGUIDialogCache(
        5s, CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(10214),
        item.GetLabel());

    //is or could be a playlist
    std::unique_ptr<PLAYLIST::CPlayList> playlist;
    CCreateAndLoadPlayList getPlaylist(item, playlist);
    bool cancelled = !CGUIDialogBusy::Wait(&getPlaylist, 100, true);

    if (dlgCache)
    {
      dlgCache->Close();
      if (dlgCache->IsCanceled())
        cancelled = true;
    }

    if (cancelled)
      return true;

    if (playlist)
    {

      if (playlistId != PLAYLIST::Id::TYPE_NONE)
      {
        int track=0;
        if (item.HasProperty("playlist_starting_track"))
          track = (int)item.GetProperty("playlist_starting_track").asInteger();
        return ProcessAndStartPlaylist(item.GetPath(), *playlist, playlistId, track);
      }
      else
      {
        CLog::Log(LOGWARNING,
                  "CApplication::PlayMedia called to play a playlist {} but no idea which playlist "
                  "to use, playing first item",
                  item.GetPath());
        if (playlist->size())
          return PlayFile(*(*playlist)[0], "", false);
      }
    }
  }
  else if (item.IsPVR())
  {
    return CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia(item);
  }

  CURL path(item.GetPath());
  if (path.GetProtocol() == "game")
  {
    AddonPtr addon;
    if (CServiceBroker::GetAddonMgr().GetAddon(path.GetHostName(), addon, AddonType::GAMEDLL,
                                               OnlyEnabled::CHOICE_YES))
    {
      CFileItem addonItem(addon);
      return PlayFile(addonItem, player, false);
    }
  }

  //nothing special just play
  return PlayFile(item, player, false);
}

bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRestart /* = false */)
{
  const auto appPlayer{GetComponent<CApplicationPlayer>()};
  const auto stackHelper{GetComponent<CApplicationStackHelper>()};

  if (!bRestart)
  {
    appPlayer->SetPlaySpeed(1);

    m_nextPlaylistItem = -1;
    stackHelper->Clear();

    if (VIDEO::IsVideo(item))
      CUtil::ClearSubtitles();
  }

  using enum CApplicationPlay::GatherPlaybackDetailsResult;

  CApplicationPlay appPlay{*stackHelper};
  if (const auto result{appPlay.GatherPlaybackDetails(item, player, bRestart)};
      result == RESULT_ERROR)
    return false;
  else if (result == RESULT_NO_PLAYLIST_SELECTED)
  {
    m_cancelPlayback = true;
    return true; // Special case; not to be treated as error.
  }

  // Special handling for disc stubs.
  //! @todo Shouldn't disc stubs also be handled via appPlayer->OpenFile()?
  const CFileItem& resolvedItem{appPlay.GetResolvedItem()};
  if (VIDEO::IsDiscStub(resolvedItem))
    return CServiceBroker::GetMediaManager().playStubFile(resolvedItem);

  // Reset VideoStartWindowed as it's a temp setting
  CMediaSettings::GetInstance().SetMediaStartWindowed(false);

  // For playing a new item, previous playing item's callback may already
  // pushed some delay message into the threadmessage list, they are not
  // expected be processed after or during the new item playback starting.
  // so we clean up previous playing item's playback callback delay messages here.
  static constexpr std::array previousMsgsIgnoredByNewPlaying{GUI_MSG_PLAYBACK_STARTED,
                                                              GUI_MSG_PLAYBACK_ENDED,
                                                              GUI_MSG_PLAYBACK_STOPPED,
                                                              GUI_MSG_PLAYLIST_CHANGED,
                                                              GUI_MSG_PLAYLISTPLAYER_STOPPED,
                                                              GUI_MSG_PLAYLISTPLAYER_STARTED,
                                                              GUI_MSG_PLAYLISTPLAYER_CHANGED,
                                                              GUI_MSG_QUEUE_NEXT_ITEM,
                                                              0};
  if (const int dMsgCount{
          CServiceBroker::GetGUI()->GetWindowManager().RemoveThreadMessageByMessageIds(
              &previousMsgsIgnoredByNewPlaying[0])};
      dMsgCount > 0)
    CLog::LogF(LOGDEBUG, "Ignored {} playback thread messages", dMsgCount);

  appPlayer->OpenFile(resolvedItem, appPlay.GetPlayerOptions(),
                      m_ServiceManager->GetPlayerCoreFactory(), appPlay.GetResolvedPlayer(), *this);

  const auto appVolume{GetComponent<CApplicationVolumeHandling>()};
  appPlayer->SetVolume(appVolume->GetVolumeRatio());
  appPlayer->SetMute(appVolume->IsMuted());

#if !defined(TARGET_POSIX)
  if (CGUIComponent * gui{CServiceBroker::GetGUI()}; gui)
    gui->GetAudioManager().Enable(false);
#endif

  if (item.HasPVRChannelInfoTag())
    CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::TYPE_NONE);

  return true;
}

void CApplication::PlaybackCleanup()
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (!appPlayer->IsPlaying())
  {
    CGUIComponent *gui = CServiceBroker::GetGUI();
    if (gui)
      CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
    appPlayer->OpenNext(m_ServiceManager->GetPlayerCoreFactory());
  }

  if (!appPlayer->IsPlayingVideo())
  {
    if(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO ||
       CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME)
    {
      CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
    }
    else
    {
      //  resets to res_desktop or look&feel resolution (including refreshrate)
      CServiceBroker::GetWinSystem()->GetGfxContext().SetFullScreenVideo(false);
    }
#ifdef TARGET_DARWIN_EMBEDDED
    CDarwinUtils::SetScheduling(false);
#endif
  }

  const auto appPower = GetComponent<CApplicationPowerHandling>();

  if (!appPlayer->IsPlayingAudio() &&
      CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::Id::TYPE_NONE &&
      CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION)
  {
    CServiceBroker::GetSettingsComponent()->GetSettings()->Save();  // save vis settings
    appPower->WakeUpScreenSaverAndDPMS();
    CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
  }

  // DVD ejected while playing in vis ?
  if (!appPlayer->IsPlayingAudio() &&
      (MUSIC::IsCDDA(*m_itemCurrentFile) || m_itemCurrentFile->IsOnDVD()) &&
      !CServiceBroker::GetMediaManager().IsDiscInDrive() &&
      CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION)
  {
    // yes, disable vis
    CServiceBroker::GetSettingsComponent()->GetSettings()->Save();    // save vis settings
    appPower->WakeUpScreenSaverAndDPMS();
    CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
  }

  if (!appPlayer->IsPlaying())
  {
    stackHelper->Clear();
    appPlayer->ResetPlayer();
  }

  if (CServiceBroker::GetAppParams()->IsTestMode())
    CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
}

bool CApplication::IsPlayingFullScreenVideo() const
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  return appPlayer->IsPlayingVideo() &&
         CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo();
}

bool CApplication::IsFullScreen()
{
  return IsPlayingFullScreenVideo() ||
        (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION) ||
         CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW;
}

void CApplication::StopPlaying()
{
  CGUIComponent *gui = CServiceBroker::GetGUI();

  if (gui)
  {
    int iWin = gui->GetWindowManager().GetActiveWindow();
    const auto appPlayer = GetComponent<CApplicationPlayer>();
    if (appPlayer->IsPlaying())
    {
      appPlayer->ClosePlayer();

      // turn off visualisation window when stopping
      if ((iWin == WINDOW_VISUALISATION ||
           iWin == WINDOW_FULLSCREEN_VIDEO ||
           iWin == WINDOW_FULLSCREEN_GAME) &&
           !m_bStop)
        gui->GetWindowManager().PreviousWindow();

      g_partyModeManager.Disable();
    }
  }
}

bool CApplication::OnMessage(CGUIMessage& message)
{
  return m_pMsgHandling->OnMessage(message);
}

bool CApplication::ExecuteXBMCAction(std::string actionStr,
                                     const std::shared_ptr<CGUIListItem>& item /* = NULL */)
{
  // see if it is a user set string

  //We don't know if there is unsecure information in this yet, so we
  //postpone any logging
  const std::string in_actionStr(actionStr);
  if (item)
    actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetItemLabel(actionStr, item.get());
  else
    actionStr = GUILIB::GUIINFO::CGUIInfoLabel::GetLabel(actionStr, INFO::DEFAULT_CONTEXT);

  // user has asked for something to be executed
  if (CBuiltins::GetInstance().HasCommand(actionStr))
  {
    if (!CBuiltins::GetInstance().IsSystemPowerdownCommand(actionStr) ||
        CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
      CBuiltins::GetInstance().Execute(actionStr, item);
  }
  else
  {
    // try translating the action from our ButtonTranslator
    unsigned int actionID;
    if (ACTION::CActionTranslator::TranslateString(actionStr, actionID))
    {
      OnAction(CAction(actionID));
      return true;
    }
    CFileItem item(actionStr, false);
#ifdef HAS_PYTHON
    if (item.IsPythonScript())
    { // a python script
      CScriptInvocationManager::GetInstance().ExecuteAsync(item.GetPath());
    }
    else
#endif
        if (MUSIC::IsAudio(item) || VIDEO::IsVideo(item) || item.IsGame())
    { // an audio or video file
      PlayFile(item, "");
    }
    else
    {
      //At this point we have given up to translate, so even though
      //there may be insecure information, we log it.
      CLog::LogF(LOGDEBUG, "Tried translating, but failed to understand {}", in_actionStr);
      return false;
    }
  }
  return true;
}

void CApplication::ConfigureAndEnableAddons()
{
  if (!m_incompatibleAddons.empty())
  {
    // filter addons that are not dependencies
    std::vector<std::string> disabledAddonNames;
    for (const auto& addoninfo : m_incompatibleAddons)
    {
      if (!CAddonType::IsDependencyType(addoninfo->MainType()))
        disabledAddonNames.emplace_back(addoninfo->Name());
    }

    // migration (incompatible addons) dialog
    auto addonList = StringUtils::Join(disabledAddonNames, ", ");
    auto msg = StringUtils::Format(
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(24149), addonList);
    HELPERS::ShowOKDialogText(CVariant{24148}, CVariant{std::move(msg)});
    m_incompatibleAddons.clear();
  }

  std::vector<std::shared_ptr<IAddon>>
      disabledAddons; /*!< Installed addons, but not auto-enabled via manifest */

  auto& addonMgr = CServiceBroker::GetAddonMgr();

  if (addonMgr.GetDisabledAddons(disabledAddons) && !disabledAddons.empty())
  {
    // this applies to certain platforms only:
    // look at disabled addons with disabledReason == NONE, usually those are installed via package managers or manually.
    // also try to enable add-ons with disabledReason == INCOMPATIBLE at startup for all platforms.

    bool isConfigureAddonsAtStartupEnabled =
        m_ServiceManager->GetPlatform().IsConfigureAddonsAtStartupEnabled();

    for (const auto& addon : disabledAddons)
    {
      if (addonMgr.IsAddonDisabledWithReason(addon->ID(), ADDON::AddonDisabledReason::INCOMPATIBLE))
      {
        auto addonInfo = addonMgr.GetAddonInfo(addon->ID(), AddonType::UNKNOWN);
        if (addonInfo && addonMgr.IsCompatible(addonInfo))
        {
          CLog::Log(LOGDEBUG, "CApplication::{}: enabling the compatible version of [{}].",
                    __FUNCTION__, addon->ID());
          addonMgr.EnableAddon(addon->ID());
        }
        continue;
      }

      if (addonMgr.IsAddonDisabledExcept(addon->ID(), ADDON::AddonDisabledReason::NONE) ||
          CAddonType::IsDependencyType(addon->MainType()))
      {
        continue;
      }

      if (isConfigureAddonsAtStartupEnabled)
      {
        if (HELPERS::ShowYesNoDialogLines(CVariant{24039}, // Disabled add-ons
                                          CVariant{24059}, // Would you like to enable this add-on?
                                          CVariant{addon->Name()}) == DialogResponse::CHOICE_YES)
        {
          if (addon->CanHaveAddonOrInstanceSettings())
          {
            if (CGUIDialogAddonSettings::ShowForAddon(addon))
            {
              // only enable if settings dialog hasn't been cancelled
              addonMgr.EnableAddon(addon->ID());
            }
          }
          else
          {
            addonMgr.EnableAddon(addon->ID());
          }
        }
        else
        {
          // user chose not to configure/enable so we're not asking anymore
          addonMgr.UpdateDisabledReason(addon->ID(), ADDON::AddonDisabledReason::USER);
        }
      }
    }
  }
}

void CApplication::Process()
{
  // dispatch the messages generated by python or other threads to the current window
  CServiceBroker::GetGUI()->GetWindowManager().DispatchThreadMessages();

  // process messages which have to be send to the gui
  // (this can only be done after CServiceBroker::GetGUI()->GetWindowManager().Render())
  CServiceBroker::GetAppMessenger()->ProcessWindowMessages();

  // handle any active scripts

  {
    // Allow processing of script threads to let them shut down properly.
    CSingleExit ex(CServiceBroker::GetWinSystem()->GetGfxContext());
    m_frameMoveGuard.unlock();
    CScriptInvocationManager::GetInstance().Process();
    m_frameMoveGuard.lock();
  }

  // process messages, even if a movie is playing
  CServiceBroker::GetAppMessenger()->ProcessMessages();
  if (m_bStop) return; //we're done, everything has been unloaded

  // the main loop reaches this with no GUI message handler suspended on the stack, which
  // is what a deferred skin reload is waiting for
  GetComponent<CApplicationSkinHandling>()->ProcessPendingSkinReload();

  // do any processing that isn't needed on each run
  if( m_slowTimer.GetElapsedMilliseconds() > 500 )
  {
    m_slowTimer.Reset();
    ProcessSlow();
  }
}

// We get called every 500ms
void CApplication::ProcessSlow()
{
  // process skin resources (skin timers)
  GetComponent<CApplicationSkinHandling>()->ProcessSkin();

  CServiceBroker::GetPowerManager().ProcessEvents();

  // Temporarily pause pausable jobs when viewing video/picture
  int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
  if (VIDEO::IsVideo(CurrentFileItem()) || CurrentFileItem().IsPicture() ||
      currentWindow == WINDOW_FULLSCREEN_VIDEO || currentWindow == WINDOW_FULLSCREEN_GAME ||
      currentWindow == WINDOW_SLIDESHOW)
  {
    CServiceBroker::GetJobManager()->PauseJobs();
  }
  else
  {
    CServiceBroker::GetJobManager()->UnPauseJobs();
  }

  // Check if we need to activate the screensaver / DPMS.
  const auto appPower = GetComponent<CApplicationPowerHandling>();
  appPower->CheckScreenSaverAndDPMS();

  // Check if we need to shutdown (if enabled).
#if defined(TARGET_DARWIN)
  if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) &&
      CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
#else
  if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME))
#endif
  {
    appPower->CheckShutdown();
  }

#if defined(TARGET_POSIX)
  if (CPlatformPosix::TestQuitFlag())
  {
    CLog::Log(LOGINFO, "Quitting due to POSIX signal");
    CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
  }
#endif

  // check if we should restart the player
  CheckDelayedPlayerRestart();

  //  check if we can unload any unreferenced dlls or sections
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  if (!appPlayer->IsPlayingVideo())
    CSectionLoader::UnloadDelayed();

#ifdef TARGET_ANDROID
  // Pass the slow loop to droid
  CXBMCApp::Get().ProcessSlow();
#endif

  // check for any idle curl connections
  g_curlInterface.CheckIdle();

  CServiceBroker::GetGUI()->GetLargeTextureManager().CleanupUnusedImages();

  CServiceBroker::GetGUI()->GetTextureManager().FreeUnusedTextures(5000);

#ifdef HAS_OPTICAL_DRIVE
  // checks whats in the DVD drive and tries to autostart the content (xbox games, dvd, cdda, avi files...)
  if (!appPlayer->IsPlayingVideo())
    m_Autorun->HandleAutorun();
#endif

  // update upnp server/renderer states
#ifdef HAS_UPNP
  if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_UPNP) && UPNP::CUPnP::IsInstantiated())
    UPNP::CUPnP::GetInstance()->UpdateState();
#endif

#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
  smb.CheckIfIdle();
#endif

#ifdef HAS_FILESYSTEM_NFS
  gNfsConnection.CheckIfIdle();
#endif

  for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
    vfsAddon->ClearOutIdle();

  CServiceBroker::GetMediaManager().ProcessEvents();

  // if we don't render the gui there's no reason to start the screensaver.
  // that way the screensaver won't kick in if we maximize the XBMC window
  // after the screensaver start time.
  if (!appPower->GetRenderGUI())
    appPower->ResetScreenSaverTimer();
}

void CApplication::DelayedPlayerRestart()
{
  m_restartPlayerTimer.StartZero();
}

void CApplication::CheckDelayedPlayerRestart()
{
  if (m_restartPlayerTimer.GetElapsedSeconds() > 3)
  {
    m_restartPlayerTimer.Stop();
    m_restartPlayerTimer.Reset();
    Restart(true);
  }
}

void CApplication::Restart(bool bSamePosition)
{
  // this function gets called when the user changes a setting (like noninterleaved)
  // and which means we gotta close & reopen the current playing file

  // first check if we're playing a file
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  if (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio())
    return ;

  if (!appPlayer->HasPlayer())
    return ;

  // do we want to return to the current position in the file
  if (!bSamePosition)
  {
    // no, then just reopen the file and start at the beginning
    PlayFile(*m_itemCurrentFile, "", true);
    return ;
  }

  // else get current position
  double time = GetTime();

  // get player state, needed for dvd's
  std::string state = appPlayer->GetPlayerState();

  // set the requested starttime
  m_itemCurrentFile->SetStartOffset(CUtil::ConvertSecsToMilliSecs(time));

  // reopen the file
  if (PlayFile(*m_itemCurrentFile, "", true))
    appPlayer->SetPlayerState(state);
}

const std::string& CApplication::CurrentFile()
{
  return m_itemCurrentFile->GetPath();
}

std::shared_ptr<CFileItem> CApplication::CurrentFileItemPtr()
{
  return m_itemCurrentFile;
}

CFileItem& CApplication::CurrentFileItem()
{
  return *m_itemCurrentFile;
}

const CFileItem& CApplication::CurrentUnstackedItem()
{
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (stackHelper->IsPlayingStack())
    return stackHelper->GetCurrentStackPart();
  else
    return *m_itemCurrentFile;
}

// Returns the total time in seconds of the current media.  Fractional
// portions of a second are possible - but not necessarily supported by the
// player class.  This returns a double to be consistent with GetTime() and
// SeekTime().
double CApplication::GetTotalTime() const
{
  double rc = 0.0;

  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying())
  {
    if (stackHelper->IsPlayingRegularStack() || stackHelper->IsPlayingResolvedDiscStack())
      rc = static_cast<double>(stackHelper->GetStackTotalTime().count()) / 1000.0;
    else
      rc = static_cast<double>(appPlayer->GetTotalTime()) / 1000.0;
  }

  return rc;
}

// Returns the current time in seconds of the currently playing media.
// Fractional portions of a second are possible.  This returns a double to
// be consistent with GetTotalTime() and SeekTime().
double CApplication::GetTime() const
{
  double rc = 0.0;

  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying())
  {
    if (stackHelper->IsPlayingRegularStack() || stackHelper->IsPlayingResolvedDiscStack())
    {
      uint64_t startOfCurrentFile =
          static_cast<uint64_t>(stackHelper->GetCurrentStackPartStartTime().count());
      rc = (startOfCurrentFile + appPlayer->GetTime()) * 0.001;
    }
    else
      rc = appPlayer->GetTime() * 0.001;
  }

  return rc;
}

// Sets the current position of the currently playing media to the specified
// time in seconds.  Fractional portions of a second are valid.  The passed
// time is the time offset from the beginning of the file as opposed to a
// delta from the current position.  This method accepts a double to be
// consistent with GetTime() and GetTotalTime().
void CApplication::SeekTime( double dTime )
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying() && (dTime >= 0.0))
  {
    if (!appPlayer->CanSeek())
      return;

    if (stackHelper->IsPlayingRegularStack() || stackHelper->IsPlayingResolvedDiscStack())
    {
      // find the item in the stack we are seeking to, and load the new
      // file if necessary, and calculate the correct seek within the new
      // file.  Otherwise, just fall through to the usual routine if the
      // time is higher than our total time.
      int partNumberToPlay = stackHelper->GetStackPartNumberAtTime(
          std::chrono::milliseconds(static_cast<int64_t>(dTime * 1000.0)));
      uint64_t startOfNewFile = stackHelper->GetStackPartStartTime(partNumberToPlay).count();
      if (partNumberToPlay == stackHelper->GetCurrentPartNumber())
        appPlayer->SeekTime(static_cast<int64_t>(dTime * 1000.0) -
                            static_cast<int64_t>(startOfNewFile));
      else
      {
        // seeking to a new file
        stackHelper->SetStackPartAsCurrent(partNumberToPlay);
        stackHelper->SetSeekingParts(true);
        CFileItem* item = new CFileItem(stackHelper->GetCurrentStackPart());
        item->SetStartOffset(static_cast<int64_t>(dTime * 1000.0) -
                             static_cast<int64_t>(startOfNewFile));
        // don't just call "PlayFile" here, as we are quite likely called from the
        // player thread, so we won't be able to delete ourselves.
        CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0, static_cast<void*>(item));
      }
      return;
    }
    // convert to milliseconds and perform seek
    appPlayer->SeekTime(static_cast<int64_t>(dTime * 1000.0));
  }
}

float CApplication::GetPercentage() const
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying())
  {
    if (appPlayer->GetTotalTime() == 0 && appPlayer->IsPlayingAudio() &&
        m_itemCurrentFile->HasMusicInfoTag())
    {
      const CMusicInfoTag& tag = *m_itemCurrentFile->GetMusicInfoTag();
      if (tag.GetDuration() > 0)
        return (float)(GetTime() / tag.GetDuration() * 100);
    }

    if (stackHelper->IsPlayingRegularStack() || stackHelper->IsPlayingResolvedDiscStack())
    {
      double totalTime = GetTotalTime();
      if (totalTime > 0.0)
        return (float)(GetTime() / totalTime * 100);
    }
    else
      return appPlayer->GetPercentage();
  }
  return 0.0f;
}

float CApplication::GetCachePercentage() const
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying())
  {
    // Note that the player returns a relative cache percentage and we want an absolute percentage
    if (stackHelper->IsPlayingRegularStack())
    {
      float stackedTotalTime = (float) GetTotalTime();
      // We need to take into account the stack's total time vs. currently playing file's total time
      if (stackedTotalTime > 0.0f)
        return std::min(100.0f,
                        GetPercentage() + (appPlayer->GetCachePercentage() *
                                           appPlayer->GetTotalTime() * 0.001f / stackedTotalTime));
    }
    else
      return std::min(100.0f, appPlayer->GetPercentage() + appPlayer->GetCachePercentage());
  }
  return 0.0f;
}

void CApplication::SeekPercentage(float percent)
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  const auto stackHelper = GetComponent<CApplicationStackHelper>();

  if (appPlayer->IsPlaying() && (percent >= 0.0f))
  {
    if (!appPlayer->CanSeek())
      return;
    if (stackHelper->IsPlayingRegularStack() || stackHelper->IsPlayingResolvedDiscStack())
      SeekTime(static_cast<double>(percent) * 0.01 * GetTotalTime());
    else
      appPlayer->SeekPercentage(percent);
  }
}

std::string CApplication::GetCurrentPlayer()
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  return appPlayer->GetCurrentPlayer();
}

void CApplication::UpdateLibraries()
{
  const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  if (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_UPDATEONSTARTUP))
  {
    CLog::LogF(LOGINFO, "Starting video library startup scan");
    CVideoLibraryQueue::GetInstance().ScanLibrary(
        "", false, !settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_BACKGROUNDUPDATE));
  }

  if (settings->GetBool(CSettings::SETTING_MUSICLIBRARY_UPDATEONSTARTUP))
  {
    CLog::LogF(LOGINFO, "Starting music library startup scan");
    CMusicLibraryQueue::GetInstance().ScanLibrary(
        "", MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL,
        !settings->GetBool(CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE));
  }
}

void CApplication::UpdateCurrentPlayArt()
{
  const auto appPlayer = GetComponent<CApplicationPlayer>();
  if (!appPlayer->IsPlayingAudio())
    return;
  //Clear and reload the art for the currently playing item to show updated art on OSD
  m_itemCurrentFile->ClearArt();
  CMusicThumbLoader loader;
  loader.LoadItem(m_itemCurrentFile.get());
  // Mirror changes to GUI item
  CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
}

bool CApplication::ProcessAndStartPlaylist(const std::string& strPlayList,
                                           PLAYLIST::CPlayList& playlist,
                                           PLAYLIST::Id playlistId,
                                           int track)
{
  CLog::Log(LOGDEBUG, "CApplication::ProcessAndStartPlaylist({}, {})", strPlayList,
            static_cast<int>(playlistId));

  // initial exit conditions
  // no songs in playlist just return
  if (playlist.size() == 0)
    return false;

  // illegal playlist
  if (playlistId == PLAYLIST::Id::TYPE_NONE || playlistId == PLAYLIST::Id::TYPE_PICTURE)
    return false;

  // setup correct playlist
  CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);

  // if the playlist contains an internet stream, this file will be used
  // to generate a thumbnail for musicplayer.cover
  m_strPlayListFile = strPlayList;

  // add the items to the playlist player
  CServiceBroker::GetPlaylistPlayer().Add(playlistId, playlist);

  // if we have a playlist
  if (CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size())
  {
    // start playing it
    CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId);
    CServiceBroker::GetPlaylistPlayer().Reset();
    CServiceBroker::GetPlaylistPlayer().Play(track, "");
    return true;
  }
  return false;
}

bool CApplication::GetRenderGUI() const
{
  return GetComponent<CApplicationPowerHandling>()->GetRenderGUI();
}

bool CApplication::SetLanguage(const std::string &strLanguage)
{
  // nothing to be done if the language hasn't changed
  if (strLanguage == CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE))
    return true;

  return CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOCALE_LANGUAGE, strLanguage);
}

bool CApplication::LoadLanguage(bool reload)
{
  // load the configured language
  if (!g_langInfo.SetLanguage("", reload))
    return false;

  // set the proper audio and subtitle languages
  const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  g_langInfo.SetAudioLanguage(settings->GetString(CSettings::SETTING_LOCALE_AUDIOLANGUAGE));
  g_langInfo.SetSubtitleLanguage(settings->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE));

  return true;
}

void CApplication::SetLoggingIn(bool switchingProfiles)
{
  // don't save skin settings on unloading when logging into another profile
  // because in that case we have already loaded the new profile and
  // would therefore write the previous skin's settings into the new profile
  // instead of into the previous one
  GetComponent<CApplicationSkinHandling>()->m_saveSkinOnUnloading = !switchingProfiles;
}

void CApplication::PrintStartupLog()
{
  CLog::Log(LOGINFO, "-----------------------------------------------------------------------");
  CLog::Log(LOGINFO, "Starting {} ({}). Platform: {} {} {}-bit", CSysInfo::GetAppName(),
            CSysInfo::GetVersion(), g_sysinfo.GetBuildTargetPlatformName(),
            g_sysinfo.GetBuildTargetCpuFamily(), g_sysinfo.GetXbmcBitness());

  std::string buildType;
#if defined(_DEBUG)
  buildType = "Debug";
#elif defined(NDEBUG)
  buildType = "Release";
#else
  buildType = "Unknown";
#endif

  CLog::Log(LOGINFO, "Using {} {} x{}", buildType, CSysInfo::GetAppName(),
            g_sysinfo.GetXbmcBitness());
  CLog::Log(LOGINFO, "{} compiled {} by {} for {} {} {}-bit {} ({})", CSysInfo::GetAppName(),
            CSysInfo::GetBuildDate(), g_sysinfo.GetUsedCompilerNameAndVer(),
            g_sysinfo.GetBuildTargetPlatformName(), g_sysinfo.GetBuildTargetCpuFamily(),
            g_sysinfo.GetXbmcBitness(), g_sysinfo.GetBuildTargetPlatformVersionDecoded(),
            g_sysinfo.GetBuildTargetPlatformVersion());

  std::string deviceModel(g_sysinfo.GetModelName());
  if (!g_sysinfo.GetManufacturerName().empty())
    deviceModel = g_sysinfo.GetManufacturerName() + " " +
                  (deviceModel.empty() ? std::string("device") : deviceModel);
  if (!deviceModel.empty())
    CLog::Log(LOGINFO, "Running on {} with {}, kernel: {} {} {}-bit version {}", deviceModel,
              g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(),
              g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(),
              g_sysinfo.GetKernelVersionFull());
  else
    CLog::Log(LOGINFO, "Running on {}, kernel: {} {} {}-bit version {}",
              g_sysinfo.GetOsPrettyNameWithVersion(), g_sysinfo.GetKernelName(),
              g_sysinfo.GetKernelCpuFamily(), g_sysinfo.GetKernelBitness(),
              g_sysinfo.GetKernelVersionFull());

  CLog::Log(LOGINFO, "FFmpeg version/source: {}", av_version_info());

  std::string cpuModel(CServiceBroker::GetCPUInfo()->GetCPUModel());
  if (!cpuModel.empty())
  {
    CLog::Log(LOGINFO, "Host CPU: {}, {} core{} available", cpuModel,
              CServiceBroker::GetCPUInfo()->GetCPUCount(),
              (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s");
  }
  else
    CLog::Log(LOGINFO, "{} CPU core{} available", CServiceBroker::GetCPUInfo()->GetCPUCount(),
              (CServiceBroker::GetCPUInfo()->GetCPUCount() == 1) ? "" : "s");

  // Any system info logging that is unique to a platform
  m_ServiceManager->GetPlatform().PlatformSyslog();

#if defined(__arm__) || defined(__aarch64__)
  CLog::Log(LOGINFO, "ARM Features: Neon {}",
            (CServiceBroker::GetCPUInfo()->GetCPUFeatures() & CPU_FEATURE_NEON) ? "enabled"
                                                                                : "disabled");
#endif
  CSpecialProtocol::LogPaths();

#ifdef HAS_WEB_SERVER
  CLog::Log(LOGINFO, "Webserver extra whitelist paths: {}",
            StringUtils::Join(CCompileInfo::GetWebserverExtraWhitelist(), ", "));
#endif

  // Check, whether libkodi.so was reused (happens on Android, where the system does not unload
  // the lib on activity end, but keeps it loaded (as long as there is enough memory) and reuses
  // it on next activity start.
  static bool firstRun = true;

  CLog::Log(LOGINFO, "The executable running is: {}{}", CUtil::ResolveExecutablePath(),
            firstRun ? "" : " [reused]");

  firstRun = false;

  std::string hostname("[unknown]");
  m_ServiceManager->GetNetwork().GetHostName(hostname);
  CLog::Log(LOGINFO, "Local hostname: {}", hostname);
  std::string lowerAppName = CCompileInfo::GetAppName();
  StringUtils::ToLower(lowerAppName);
  CLog::Log(LOGINFO, "Log File is located: {}.log",
            CSpecialProtocol::TranslatePath("special://logpath/" + lowerAppName));
  CRegExp::LogCheckUtf8Support();
  CLog::Log(LOGINFO, "-----------------------------------------------------------------------");
}

void CApplication::CloseNetworkShares()
{
  CLog::Log(LOGDEBUG,"CApplication::CloseNetworkShares: Closing all network shares");

#if defined(HAS_FILESYSTEM_SMB) && !defined(TARGET_WINDOWS)
  smb.Deinit();
#endif

#ifdef HAS_FILESYSTEM_NFS
  gNfsConnection.Deinit();
#endif

  for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
    vfsAddon->DisconnectAll();
}
