/*
 *  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 "GUIInfoManager.h"
#include "HDRStatus.h"
#include "LangInfo.h"
#include "PlayListPlayer.h"
#include "ServiceManager.h"
#include "URL.h"
#include "Util.h"
#include "addons/AddonManager.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 "application/AppInboundProtocol.h"
#include "application/AppParams.h"
#include "application/ApplicationActionListeners.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/IPlayer.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"
#include "filesystem/File.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIControlProfiler.h"
#include "guilib/GUIFontManager.h"
#include "guilib/StereoscopicsManager.h"
#include "guilib/TextureManager.h"
#include "interfaces/builtins/Builtins.h"
#include "interfaces/generic/ScriptInvocationManager.h"
#include "music/MusicLibraryQueue.h"
#include "music/tags/MusicInfoTag.h"
#include "network/EventServer.h"
#include "network/Network.h"
#include "platform/Environment.h"
#include "playlists/PlayListFactory.h"
#include "threads/SystemClock.h"
#include "utils/ContentUtils.h"
#include "utils/JobManager.h"
#include "utils/LangCodeExpander.h"
#include "utils/Screenshot.h"
#include "utils/Variant.h"
#include "video/Bookmark.h"
#include "video/VideoLibraryQueue.h"

#ifdef HAS_PYTHON
#include "interfaces/python/XBPython.h"
#endif
#include "GUILargeTextureManager.h"
#include "GUIPassword.h"
#include "GUIUserMessages.h"
#include "SectionLoader.h"
#include "SeekHandler.h"
#include "ServiceBroker.h"
#include "TextureCache.h"
#include "cores/DllLoader/DllLoaderContainer.h"
#include "filesystem/Directory.h"
#include "filesystem/DirectoryCache.h"
#include "filesystem/DllLibCurl.h"
#include "filesystem/PluginDirectory.h"
#include "filesystem/SpecialProtocol.h"
#include "guilib/GUIAudioManager.h"
#include "guilib/LocalizeStrings.h"
#include "input/InertialScrollingHandler.h"
#include "input/KeyboardLayoutManager.h"
#include "input/actions/ActionTranslator.h"
#include "messaging/ApplicationMessenger.h"
#include "messaging/ThreadMessage.h"
#include "messaging/helpers/DialogHelper.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "playlists/PlayList.h"
#include "playlists/SmartPlayList.h"
#include "powermanagement/PowerManager.h"
#include "profiles/ProfileManager.h"
#include "settings/AdvancedSettings.h"
#include "settings/DisplaySettings.h"
#include "settings/MediaSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "speech/ISpeechRecognition.h"
#include "threads/SingleLock.h"
#include "utils/CPUInfo.h"
#include "utils/FileExtensionProvider.h"
#include "utils/RegExp.h"
#include "utils/SystemInfo.h"
#include "utils/TimeUtils.h"
#include "utils/XTimeUtils.h"
#include "utils/log.h"
#include "windowing/WinSystem.h"
#include "windowing/WindowSystemFactory.h"

#include <cmath>

#ifdef HAS_UPNP
#include "network/upnp/UPnP.h"
#include "filesystem/UPnPDirectory.h"
#endif
#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB)
#include "platform/posix/filesystem/SMBFile.h"
#endif
#ifdef HAS_FILESYSTEM_NFS
#include "filesystem/NFSFile.h"
#endif
#include "PartyModeManager.h"
#include "network/ZeroconfBrowser.h"
#ifndef TARGET_POSIX
#include "platform/win32/threads/Win32Exception.h"
#endif
#include "interfaces/json-rpc/JSONRPC.h"
#include "interfaces/AnnouncementManager.h"
#include "peripherals/Peripherals.h"
#include "music/infoscanner/MusicInfoScanner.h"
#include "music/MusicUtils.h"
#include "music/MusicThumbLoader.h"

// Windows includes
#include "guilib/GUIWindowManager.h"
#include "video/PlayerController.h"

// Dialog includes
#include "addons/gui/GUIDialogAddonSettings.h"
#include "dialogs/GUIDialogKaiToast.h"
#include "dialogs/GUIDialogSimpleMenu.h"
#include "video/dialogs/GUIDialogVideoBookmarks.h"

// PVR related include Files
#include "pvr/PVRManager.h"
#include "pvr/guilib/PVRGUIActionsPlayback.h"
#include "pvr/guilib/PVRGUIActionsPowerManagement.h"

#ifdef TARGET_WINDOWS
#include "win32util.h"
#endif

#ifdef TARGET_DARWIN_OSX
#include "platform/darwin/osx/CocoaInterface.h"
#include "platform/darwin/osx/XBMCHelper.h"
#endif
#ifdef TARGET_DARWIN
#include "platform/darwin/DarwinUtils.h"
#endif

#ifdef HAS_DVD_DRIVE
#include <cdio/logging.h>
#endif

#include "DatabaseManager.h"
#include "input/InputManager.h"
#include "storage/MediaManager.h"
#include "utils/AlarmClock.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"

#ifdef TARGET_POSIX
#include "platform/posix/XHandle.h"
#include "platform/posix/PlatformPosix.h"
#endif

#if defined(TARGET_ANDROID)
#include "platform/android/activity/XBMCApp.h"
#endif

#ifdef TARGET_WINDOWS
#include "platform/Environment.h"
#endif

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

#include "FileItem.h"
#include "addons/AddonSystemSettings.h"
#include "cores/FFmpeg.h"
#include "pictures/GUIWindowSlideShow.h"
#include "utils/CharsetConverter.h"

#include <mutex>

using namespace ADDON;
using namespace XFILE;
#ifdef HAS_DVD_DRIVE
using namespace MEDIA_DETECT;
#endif
using namespace VIDEO;
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_DVD_DRIVE
    m_Autorun(new CAutorun()),
#endif
    m_pInertialScrollingHandler(new CInertialScrollingHandler()),
    m_WaitingExternalCalls(0)
{
  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));
}

bool CApplication::OnEvent(XBMC_Event& newEvent)
{
  std::unique_lock<CCriticalSection> lock(m_portSection);
  m_portEvents.push_back(newEvent);
  return true;
}

void CApplication::HandlePortEvents()
{
  std::unique_lock<CCriticalSection> lock(m_portSection);
  while (!m_portEvents.empty())
  {
    auto newEvent = m_portEvents.front();
    m_portEvents.pop_front();
    CSingleExit lock(m_portSection);
    switch(newEvent.type)
    {
      case XBMC_QUIT:
        if (!m_bStop)
          CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
        break;
      case XBMC_VIDEORESIZE:
        if (CServiceBroker::GetGUI()->GetWindowManager().Initialized())
        {
          if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
          {
            CServiceBroker::GetWinSystem()->GetGfxContext().ApplyWindowResize(newEvent.resize.w, newEvent.resize.h);

            const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
            settings->SetInt(CSettings::SETTING_WINDOW_WIDTH, newEvent.resize.w);
            settings->SetInt(CSettings::SETTING_WINDOW_HEIGHT, newEvent.resize.h);
            settings->Save();
          }
#ifdef TARGET_WINDOWS
          else
          {
            // this may occurs when OS tries to resize application window
            //CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true);
            //auto& gfxContext = CServiceBroker::GetWinSystem()->GetGfxContext();
            //gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true);
            // try to resize window back to it's full screen size
            //! TODO: DX windowing should emit XBMC_FULLSCREEN_UPDATE instead with the proper dimensions
            //! and position to avoid the ifdef in common code
            auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
            CServiceBroker::GetWinSystem()->ResizeWindow(res_info.iScreenWidth, res_info.iScreenHeight, 0, 0);
          }
#endif
        }
        break;
      case XBMC_FULLSCREEN_UPDATE:
      {
        if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
        {
          CServiceBroker::GetWinSystem()->ResizeWindow(newEvent.resize.w, newEvent.resize.h,
                                                       newEvent.move.x, newEvent.move.y);
        }
        break;
      }
      case XBMC_VIDEOMOVE:
      {
        CServiceBroker::GetWinSystem()->OnMove(newEvent.move.x, newEvent.move.y);
      }
        break;
      case XBMC_MODECHANGE:
        CServiceBroker::GetWinSystem()->GetGfxContext().ApplyModeChange(newEvent.mode.res);
        break;
      case XBMC_USEREVENT:
        CServiceBroker::GetAppMessenger()->PostMsg(static_cast<uint32_t>(newEvent.user.code));
        break;
      case XBMC_SETFOCUS:
      {
        // Reset the screensaver
        const auto appPower = GetComponent<CApplicationPowerHandling>();
        appPower->ResetScreenSaver();
        appPower->WakeUpScreenSaverAndDPMS();
        // Send a mouse motion event with no dx,dy for getting the current guiitem selected
        OnAction(CAction(ACTION_MOUSE_MOVE, 0, static_cast<float>(newEvent.focus.x), static_cast<float>(newEvent.focus.y), 0, 0));
        break;
      }
      default:
        CServiceBroker::GetInputManager().OnEvent(newEvent);
    }
  }
}

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>());

  // 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<CKeyboardLayoutManager>();
  CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager);

  m_ServiceManager.reset(new 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()->HasPlatformDirectories())
#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;

  CLog::Log(LOGINFO, "creating subdirectories");
  const std::shared_ptr<CProfileManager> profileManager = settingsComponent->GetProfileManager();
  const std::shared_ptr<CSettings> settings = settingsComponent->GetSettings();
  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 inbound service
  m_pAppPort = std::make_shared<CAppInboundProtocol>(*this);
  CServiceBroker::RegisterAppPort(m_pAppPort);

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

  m_pActiveAE.reset(new 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.reset(new 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_DVD_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(g_localizeStrings.Get(177), g_sysinfo.GetAppName()),
      StringUtils::Format(g_localizeStrings.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();

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

  std::string localizedStr = g_localizeStrings.Get(24150);
  int iDots = 1;
  while (!event.Wait(1000ms))
  {
    if (databaseManager.IsUpgrading())
      CServiceBroker::GetRenderSystem()->ShowSplash(std::string(iDots, ' ') + localizedStr + std::string(iDots, '.'));

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

  // 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();
  });
  localizedStr = g_localizeStrings.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>();

  bool uiInitializationFinished = false;

  if (CServiceBroker::GetGUI()->GetWindowManager().Initialized())
  {
    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 = g_localizeStrings.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);

    if (settings->GetBool(CSettings::SETTING_MASTERLOCK_STARTUPLOCK) &&
        profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_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
      int firstWindow = g_SkinInfo->GetFirstWindow();
      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;
    }
  }
  else //No GUI Created
  {
    uiInitializationFinished = true;
  }

  CJSONRPC::Initialize();

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

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

  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;
}

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
  if (appPower->GetRenderGUI() && !m_skipGuiRender)
  {
    if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode())
    {
      CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_LEFT);
      hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();

      if (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_MONO)
      {
        CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_RIGHT);
        hasRendered |= CServiceBroker::GetGUI()->GetWindowManager().Render();
      }
      CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_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();
  }

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

  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)
  {
    CGUIWindowSlideShow* pSlideShow = CServiceBroker::GetGUI()->
                         GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
    if ((appPlayer->IsPlaying() && appPlayer->GetPlaySpeed() == 1) ||
        (pSlideShow && pSlideShow->InSlideShow() && !pSlideShow->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::GetSettingsComponent()->GetSettings()->GetBool(
            CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY))
      return true;

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

    if (hdrStatus == HDR_STATUS::HDR_OFF)
    {
      CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220),
                                            g_localizeStrings.Get(34221));
    }
    else if (hdrStatus == HDR_STATUS::HDR_ON)
    {
      CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(34220),
                                            g_localizeStrings.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::GetSettingsComponent()->GetSettings()->GetBool(
            CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) &&
        CServiceBroker::GetWinSystem()->IsHDRDisplay())
      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, g_localizeStrings.Get(34224),
                                            g_localizeStrings.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;
      }
    }
    // 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::TYPE_VIDEO &&
        CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_VIDEO_PLAYLIST)
    {
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
    }
    else if (playlistId == PLAYLIST::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)
{
  uint32_t msg = pMsg->dwMessage;
  if (msg == TMSG_SYSTEM_POWERDOWN)
  {
    if (CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown())
      msg = pMsg->param1; // perform requested shutdown action
    else
      return; // no shutdown
  }

  const auto appPlayer = GetComponent<CApplicationPlayer>();

  switch (msg)
  {
  case TMSG_POWERDOWN:
    if (Stop(EXITCODE_POWERDOWN))
      CServiceBroker::GetPowerManager().Powerdown();
    break;

  case TMSG_QUIT:
    Stop(EXITCODE_QUIT);
    break;

  case TMSG_SHUTDOWN:
    GetComponent<CApplicationPowerHandling>()->HandleShutdownMessage();
    break;

  case TMSG_RENDERER_FLUSH:
    appPlayer->FlushRenderer();
    break;

  case TMSG_HIBERNATE:
    CServiceBroker::GetPowerManager().Hibernate();
    break;

  case TMSG_SUSPEND:
    CServiceBroker::GetPowerManager().Suspend();
    break;

  case TMSG_RESTART:
  case TMSG_RESET:
    if (Stop(EXITCODE_REBOOT))
      CServiceBroker::GetPowerManager().Reboot();
    break;

  case TMSG_RESTARTAPP:
#if defined(TARGET_WINDOWS) || defined(TARGET_LINUX)
    Stop(EXITCODE_RESTARTAPP);
#endif
    break;

  case TMSG_INHIBITIDLESHUTDOWN:
    GetComponent<CApplicationPowerHandling>()->InhibitIdleShutdown(pMsg->param1 != 0);
    break;

  case TMSG_INHIBITSCREENSAVER:
    GetComponent<CApplicationPowerHandling>()->InhibitScreenSaver(pMsg->param1 != 0);
    break;

  case TMSG_ACTIVATESCREENSAVER:
    GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver();
    break;

  case TMSG_RESETSCREENSAVER:
    GetComponent<CApplicationPowerHandling>()->m_bResetScreenSaver = true;
    break;

  case TMSG_VOLUME_SHOW:
  {
    CAction action(pMsg->param1);
    GetComponent<CApplicationVolumeHandling>()->ShowVolumeBar(&action);
  }
  break;

#ifdef TARGET_ANDROID
  case TMSG_DISPLAY_SETUP:
    // We might come from a refresh rate switch destroying the native window; use the context resolution
    *static_cast<bool*>(pMsg->lpVoid) = InitWindow(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
    GetComponent<CApplicationPowerHandling>()->SetRenderGUI(true);
    break;

  case TMSG_DISPLAY_DESTROY:
    *static_cast<bool*>(pMsg->lpVoid) = CServiceBroker::GetWinSystem()->DestroyWindow();
    GetComponent<CApplicationPowerHandling>()->SetRenderGUI(false);
    break;
#endif

  case TMSG_START_ANDROID_ACTIVITY:
  {
#if defined(TARGET_ANDROID)
    if (pMsg->params.size())
    {
      CXBMCApp::StartActivity(pMsg->params[0], pMsg->params.size() > 1 ? pMsg->params[1] : "",
                              pMsg->params.size() > 2 ? pMsg->params[2] : "",
                              pMsg->params.size() > 3 ? pMsg->params[3] : "",
                              pMsg->params.size() > 4 ? pMsg->params[4] : "",
                              pMsg->params.size() > 5 ? pMsg->params[5] : "",
                              pMsg->params.size() > 6 ? pMsg->params[6] : "",
                              pMsg->params.size() > 7 ? pMsg->params[7] : "",
                              pMsg->params.size() > 8 ? pMsg->params[8] : "");
    }
#endif
  }
  break;

  case TMSG_NETWORKMESSAGE:
    m_ServiceManager->GetNetwork().NetworkMessage(static_cast<CNetworkBase::EMESSAGE>(pMsg->param1),
                                                  pMsg->param2);
    break;

  case TMSG_SETLANGUAGE:
    SetLanguage(pMsg->strParam);
    break;


  case TMSG_SWITCHTOFULLSCREEN:
  {
    CGUIComponent* gui = CServiceBroker::GetGUI();
    if (gui)
      gui->GetWindowManager().SwitchToFullScreen(true);
    break;
  }
  case TMSG_VIDEORESIZE:
  {
    XBMC_Event newEvent = {};
    newEvent.type = XBMC_VIDEORESIZE;
    newEvent.resize.w = pMsg->param1;
    newEvent.resize.h = pMsg->param2;
    OnEvent(newEvent);
    CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
  }
    break;

  case TMSG_SETVIDEORESOLUTION:
    CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(static_cast<RESOLUTION>(pMsg->param1), pMsg->param2 == 1);
    break;

  case TMSG_TOGGLEFULLSCREEN:
    CServiceBroker::GetWinSystem()->GetGfxContext().ToggleFullScreen();
    appPlayer->TriggerUpdateResolution();
    break;

  case TMSG_MINIMIZE:
    CServiceBroker::GetWinSystem()->Minimize();
    break;

  case TMSG_EXECUTE_OS:
    // Suspend AE temporarily so exclusive or hog-mode sinks
    // don't block external player's access to audio device
    IAE *audioengine;
    audioengine = CServiceBroker::GetActiveAE();
    if (audioengine)
    {
      if (!audioengine->Suspend())
      {
        CLog::Log(LOGINFO, "{}: Failed to suspend AudioEngine before launching external program",
                  __FUNCTION__);
      }
    }
#if defined(TARGET_DARWIN)
    CLog::Log(LOGINFO, "ExecWait is not implemented on this platform");
#elif defined(TARGET_POSIX)
    CUtil::RunCommandLine(pMsg->strParam, (pMsg->param1 == 1));
#elif defined(TARGET_WINDOWS)
    CWIN32Util::XBMCShellExecute(pMsg->strParam.c_str(), (pMsg->param1 == 1));
#endif
    // Resume AE processing of XBMC native audio
    if (audioengine)
    {
      if (!audioengine->Resume())
      {
        CLog::Log(LOGFATAL, "{}: Failed to restart AudioEngine after return from external player",
                  __FUNCTION__);
      }
    }
    break;

  case TMSG_EXECUTE_SCRIPT:
    CScriptInvocationManager::GetInstance().ExecuteAsync(pMsg->strParam);
    break;

  case TMSG_EXECUTE_BUILT_IN:
    CBuiltins::GetInstance().Execute(pMsg->strParam);
    break;

  case TMSG_PICTURE_SHOW:
  {
    CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
    if (!pSlideShow) return;

    // stop playing file
    if (appPlayer->IsPlayingVideo())
      StopPlaying();

    if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
      CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();

    const auto appPower = GetComponent<CApplicationPowerHandling>();
    appPower->ResetScreenSaver();
    appPower->WakeUpScreenSaverAndDPMS();

    if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW)
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
    if (URIUtils::IsZIP(pMsg->strParam) || URIUtils::IsRAR(pMsg->strParam)) // actually a cbz/cbr
    {
      CFileItemList items;
      CURL pathToUrl;
      if (URIUtils::IsZIP(pMsg->strParam))
        pathToUrl = URIUtils::CreateArchivePath("zip", CURL(pMsg->strParam), "");
      else
        pathToUrl = URIUtils::CreateArchivePath("rar", CURL(pMsg->strParam), "");

      CUtil::GetRecursiveListing(pathToUrl.Get(), items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), XFILE::DIR_FLAG_NO_FILE_DIRS);
      if (items.Size() > 0)
      {
        pSlideShow->Reset();
        for (int i = 0; i<items.Size(); ++i)
        {
          pSlideShow->Add(items[i].get());
        }
        pSlideShow->Select(items[0]->GetPath());
      }
    }
    else
    {
      CFileItem item(pMsg->strParam, false);
      pSlideShow->Reset();
      pSlideShow->Add(&item);
      pSlideShow->Select(pMsg->strParam);
    }
  }
  break;

  case TMSG_PICTURE_SLIDESHOW:
  {
    CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
    if (!pSlideShow) return;

    if (appPlayer->IsPlayingVideo())
      StopPlaying();

    pSlideShow->Reset();

    CFileItemList items;
    std::string strPath = pMsg->strParam;
    std::string extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
    if (pMsg->param1)
      extensions += "|.tbn";
    CUtil::GetRecursiveListing(strPath, items, extensions);

    if (items.Size() > 0)
    {
      for (int i = 0; i<items.Size(); ++i)
        pSlideShow->Add(items[i].get());
      pSlideShow->StartSlideShow(); //Start the slideshow!
    }

    if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() != WINDOW_SLIDESHOW)
    {
      if (items.Size() == 0)
      {
        CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_SCREENSAVER_MODE, "screensaver.xbmc.builtin.dim");
        GetComponent<CApplicationPowerHandling>()->ActivateScreenSaver();
      }
      else
        CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
    }

  }
  break;

  case TMSG_LOADPROFILE:
    {
      const int profile = pMsg->param1;
      if (profile >= 0)
        CServiceBroker::GetSettingsComponent()->GetProfileManager()->LoadProfile(static_cast<unsigned int>(profile));
    }

    break;

  case TMSG_EVENT:
  {
    if (pMsg->lpVoid)
    {
      XBMC_Event* event = static_cast<XBMC_Event*>(pMsg->lpVoid);
      OnEvent(*event);
      delete event;
    }
  }
  break;

  default:
    CLog::Log(LOGERROR, "{}: Unhandled threadmessage sent, {}", __FUNCTION__, msg);
    break;
  }
}

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<CCriticalSection> 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();
        }
      }
    }

    HandlePortEvents();
    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;

    /*! @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)
    {
      if (!m_skipGuiRender)
        CServiceBroker::GetGUI()->GetWindowManager().Process(CTimeUtils::GetFrameTime());
    }
    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->Reset();
  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::TYPE_MUSIC, playlist);
    CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::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();

    CServiceBroker::UnregisterSpeechRecognition();

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

    CServiceBroker::UnregisterTextureCache();

    // 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.

    g_localizeStrings.Clear();
    g_LangCodeExpander.Clear();
    g_charsetConverter.clear();
    g_directoryCache.Clear();
    //CServiceBroker::GetInputManager().ClearKeymaps(); //! @todo
    CEventServer::RemoveInstance();
    DllLoaderContainer::Clear();
    CServiceBroker::GetPlaylistPlayer().Clear();

    if (m_ServiceManager)
      m_ServiceManager->DeinitStageTwo();

#ifdef TARGET_POSIX
    CXHandle::DumpObjectTracker();

#ifdef HAS_DVD_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::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_pAppPort.use_count() > 1)
    {
      KODI::TIME::Sleep(100ms);
      if (timer.IsTimePast())
      {
        CLog::Log(LOGERROR, "CApplication::Stop - CAppPort still in use, app may crash");
        break;
      }
    }
    m_pAppPort.reset();
  }

  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 (g_SkinInfo != nullptr)
      g_SkinInfo->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");

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

    // stop scanning before we kill the network and so on
    if (CMusicLibraryQueue::GetInstance().IsRunning())
      CMusicLibraryQueue::GetInstance().CancelAllJobs();

    if (CVideoLibraryQueue::GetInstance().IsRunning())
      CVideoLibraryQueue::GetInstance().CancelAllJobs();

    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

#if defined(TARGET_DARWIN_OSX)
    if (XBMCHelper::GetInstance().IsAlwaysOn() == false)
      XBMCHelper::GetInstance().Stop();
#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());

    CGUIComponent *gui = CServiceBroker::GetGUI();
    if (gui)
      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(CFileItem& item, std::unique_ptr<PLAYLIST::CPlayList>& playlist)
    : m_item(item), m_playlist(playlist)
  {
  }

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

private:
  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 (item.IsSmartPlayList())
  {
    CFileItemList items;
    CUtil::GetRecursiveListing(item.GetPath(), items, "", DIR_FLAG_NO_FILE_DIRS);
    if (items.Size())
    {
      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::TYPE_VIDEO;

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

      return ProcessAndStartPlaylist(smartpl.GetName(), playlist, smartplPlaylistId);
    }
  }
  else if (item.IsPlayList() || item.IsInternetStream())
  {
    // Not owner. Dialog auto-deletes itself.
    CGUIDialogCache* dlgCache =
        new CGUIDialogCache(5s, g_localizeStrings.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::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);
}

// PlayStack()
// For playing a multi-file video.  Particularly inefficient
// on startup, as we are required to calculate the length
// of each video, so we open + close each one in turn.
// A faster calculation of video time would improve this
// substantially.
// return value: same with PlayFile()
bool CApplication::PlayStack(CFileItem& item, bool bRestart)
{
  const auto stackHelper = GetComponent<CApplicationStackHelper>();
  if (!stackHelper->InitializeStack(item))
    return false;

  int startoffset = stackHelper->InitializeStackStartPartAndOffset(item);

  CFileItem selectedStackPart = stackHelper->GetCurrentStackPartFileItem();
  selectedStackPart.SetStartOffset(startoffset);

  if (item.HasProperty("savedplayerstate"))
  {
    selectedStackPart.SetProperty("savedplayerstate", item.GetProperty("savedplayerstate")); // pass on to part
    item.ClearProperty("savedplayerstate");
  }

  return PlayFile(selectedStackPart, "", true);
}

bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRestart)
{
  // Ensure the MIME type has been retrieved for http:// and shout:// streams
  if (item.GetMimeType().empty())
    item.FillInMimeType();

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

  if (!bRestart)
  {
    // bRestart will be true when called from PlayStack(), skipping this block
    appPlayer->SetPlaySpeed(1);

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

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

  if (item.IsDiscStub())
  {
    return CServiceBroker::GetMediaManager().playStubFile(item);
  }

  if (item.IsPlayList())
    return false;

  // if the item is a plugin we need to resolve the plugin paths
  if (URIUtils::HasPluginPath(item) && !XFILE::CPluginDirectory::GetResolvedPluginResult(item))
    return false;

#ifdef HAS_UPNP
  if (URIUtils::IsUPnP(item.GetPath()))
  {
    if (!XFILE::CUPnPDirectory::GetResource(item.GetURL(), item))
      return false;
  }
#endif

  // if we have a stacked set of files, we need to setup our stack routines for
  // "seamless" seeking and total time of the movie etc.
  // will recall with restart set to true
  if (item.IsStack())
    return PlayStack(item, bRestart);

  CPlayerOptions options;

  if (item.HasProperty("StartPercent"))
  {
    options.startpercent = item.GetProperty("StartPercent").asDouble();
    item.SetStartOffset(0);
  }

  options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset());

  if (bRestart)
  {
    // have to be set here due to playstack using this for starting the file
    if (item.HasVideoInfoTag())
      options.state = item.GetVideoInfoTag()->GetResumePoint().playerState;
  }
  if (!bRestart || stackHelper->IsPlayingISOStack())
  {
    // the following code block is only applicable when bRestart is false OR to ISO stacks

    if (item.IsVideo())
    {
      // open the d/b and retrieve the bookmarks for the current movie
      CVideoDatabase dbs;
      dbs.Open();

      std::string path = item.GetPath();
      std::string videoInfoTagPath(item.GetVideoInfoTag()->m_strFileNameAndPath);
      if (videoInfoTagPath.find("removable://") == 0 || item.IsVideoDb())
        path = videoInfoTagPath;
      dbs.LoadVideoInfo(path, *item.GetVideoInfoTag());

      if (item.HasProperty("savedplayerstate"))
      {
        options.starttime = CUtil::ConvertMilliSecsToSecs(item.GetStartOffset());
        options.state = item.GetProperty("savedplayerstate").asString();
        item.ClearProperty("savedplayerstate");
      }
      else if (item.GetStartOffset() == STARTOFFSET_RESUME)
      {
        options.starttime = 0.0;
        if (item.IsResumePointSet())
        {
          options.starttime = item.GetCurrentResumeTime();
          if (item.HasVideoInfoTag())
            options.state = item.GetVideoInfoTag()->GetResumePoint().playerState;
        }
        else
        {
          CBookmark bookmark;
          std::string path = item.GetPath();
          if (item.HasVideoInfoTag() && StringUtils::StartsWith(item.GetVideoInfoTag()->m_strFileNameAndPath, "removable://"))
            path = item.GetVideoInfoTag()->m_strFileNameAndPath;
          else if (item.HasProperty("original_listitem_url") && URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
            path = item.GetProperty("original_listitem_url").asString();
          if (dbs.GetResumeBookMark(path, bookmark))
          {
            options.starttime = bookmark.timeInSeconds;
            options.state = bookmark.playerState;
          }
        }

        if (options.starttime == 0.0 && item.HasVideoInfoTag())
        {
          // No resume point is set, but check if this item is part of a multi-episode file
          const CVideoInfoTag *tag = item.GetVideoInfoTag();

          if (tag->m_iBookmarkId > 0)
          {
            CBookmark bookmark;
            dbs.GetBookMarkForEpisode(*tag, bookmark);
            options.starttime = bookmark.timeInSeconds;
            options.state = bookmark.playerState;
          }
        }
      }
      else if (item.HasVideoInfoTag())
      {
        const CVideoInfoTag *tag = item.GetVideoInfoTag();

        if (tag->m_iBookmarkId > 0)
        {
          CBookmark bookmark;
          dbs.GetBookMarkForEpisode(*tag, bookmark);
          options.starttime = bookmark.timeInSeconds;
          options.state = bookmark.playerState;
        }
      }

      dbs.Close();
    }
  }

  // a disc image might be Blu-Ray disc
  if (!(options.startpercent > 0.0 || options.starttime > 0.0) &&
      (item.IsBDFile() || item.IsDiscImage()))
  {
    //check if we must show the simplified bd menu
    if (!CGUIDialogSimpleMenu::ShowPlaySelection(item))
      return true;
  }

  // this really aught to be inside !bRestart, but since PlayStack
  // uses that to init playback, we have to keep it outside
  const PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
  if (item.IsAudio() && playlistId == PLAYLIST::TYPE_MUSIC)
  { // playing from a playlist by the looks
    // don't switch to fullscreen if we are not playing the first item...
    options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
        CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
        CSettings::SETTING_MUSICFILES_SELECTACTION) &&
        !CMediaSettings::GetInstance().DoesMediaStartWindowed();
  }
  else if (item.IsVideo() && playlistId == PLAYLIST::TYPE_VIDEO &&
           CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() > 1)
  { // playing from a playlist by the looks
    // don't switch to fullscreen if we are not playing the first item...
    options.fullscreen = !CServiceBroker::GetPlaylistPlayer().HasPlayedFirstFile() &&
        CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreenOnMovieStart &&
        !CMediaSettings::GetInstance().DoesMediaStartWindowed();
  }
  else if (stackHelper->IsPlayingRegularStack())
  {
    //! @todo - this will fail if user seeks back to first file in stack
    if (stackHelper->GetCurrentPartNumber() == 0 ||
        stackHelper->GetRegisteredStack(item)->GetStartOffset() != 0)
      options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
          m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();
    else
      options.fullscreen = false;
  }
  else
    options.fullscreen = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->
        m_fullScreenOnMovieStart && !CMediaSettings::GetInstance().DoesMediaStartWindowed();

  // stereo streams may have lower quality, i.e. 32bit vs 16 bit
  options.preferStereo = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoPreferStereoStream &&
                         CServiceBroker::GetActiveAE()->HasStereoAudioChannelCount();

  // 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.
    int 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
    };
    int dMsgCount = CServiceBroker::GetGUI()->GetWindowManager().RemoveThreadMessageByMessageIds(&previousMsgsIgnoredByNewPlaying[0]);
    if (dMsgCount > 0)
      CLog::LogF(LOGDEBUG, "Ignored {} playback thread messages", dMsgCount);
  }

  const auto appVolume = GetComponent<CApplicationVolumeHandling>();
  appPlayer->OpenFile(item, options, m_ServiceManager->GetPlayerCoreFactory(), player, *this);
  appPlayer->SetVolume(appVolume->GetVolumeRatio());
  appPlayer->SetMute(appVolume->IsMuted());

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

  if (item.HasPVRChannelInfoTag())
    CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::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::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() &&
      (m_itemCurrentFile->IsCDDA() || 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)
{
  switch (message.GetMessage())
  {
  case GUI_MSG_NOTIFY_ALL:
    {
      if (message.GetParam1()==GUI_MSG_REMOVED_MEDIA)
      {
        // Update general playlist: Remove DVD playlist items
        int nRemoved = CServiceBroker::GetPlaylistPlayer().RemoveDVDItems();
        if ( nRemoved > 0 )
        {
          CGUIMessage msg( GUI_MSG_PLAYLIST_CHANGED, 0, 0 );
          CServiceBroker::GetGUI()->GetWindowManager().SendMessage( msg );
        }
        // stop the file if it's on dvd (will set the resume point etc)
        if (m_itemCurrentFile->IsOnDVD())
          StopPlaying();
      }
      else if (message.GetParam1() == GUI_MSG_UI_READY)
      {
        // remove splash window
        CServiceBroker::GetGUI()->GetWindowManager().Delete(WINDOW_SPLASH);

        // show the volumebar if the volume is muted
        const auto appVolume = GetComponent<CApplicationVolumeHandling>();
        if (appVolume->IsMuted() ||
            appVolume->GetVolumeRatio() <= CApplicationVolumeHandling::VOLUME_MINIMUM)
          appVolume->ShowVolumeBar();

        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(g_localizeStrings.Get(24149), addonList);
          HELPERS::ShowOKDialogText(CVariant{24148}, CVariant{std::move(msg)});
          m_incompatibleAddons.clear();
        }

        // show info dialog about moved configuration files if needed
        ShowAppMigrationMessage();

        // offer enabling addons at kodi startup that are disabled due to
        // e.g. os package manager installation on linux
        ConfigureAndEnableAddons();

        m_bInitializing = false;

        if (message.GetSenderId() == WINDOW_SETTINGS_PROFILES)
          GetComponent<CApplicationSkinHandling>()->ReloadSkin(false);
      }
      else if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
      {
        CFileItemPtr item = std::static_pointer_cast<CFileItem>(message.GetItem());
        if (m_itemCurrentFile->IsSamePath(item.get()))
        {
          m_itemCurrentFile->UpdateInfo(*item);
          CServiceBroker::GetGUI()->GetInfoManager().UpdateCurrentItem(*item);
        }
      }
    }
    break;

  case GUI_MSG_PLAYBACK_STARTED:
    {
#ifdef TARGET_DARWIN_EMBEDDED
      // @TODO move this away to platform code
      CDarwinUtils::SetScheduling(GetComponent<CApplicationPlayer>()->IsPlayingVideo());
#endif
      PLAYLIST::CPlayList playList = CServiceBroker::GetPlaylistPlayer().GetPlaylist(
          CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist());

      // Update our infoManager with the new details etc.
      if (m_nextPlaylistItem >= 0)
      {
        // playing an item which is not in the list - player might be stopped already
        // so do nothing
        if (playList.size() <= m_nextPlaylistItem)
          return true;

        // we've started a previously queued item
        CFileItemPtr item = playList[m_nextPlaylistItem];
        // update the playlist manager
        int currentSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
        int param = ((currentSong & 0xffff) << 16) | (m_nextPlaylistItem & 0xffff);
        CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_CHANGED, 0, 0, CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(), param, item);
        CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
        CServiceBroker::GetPlaylistPlayer().SetCurrentSong(m_nextPlaylistItem);
        m_itemCurrentFile.reset(new CFileItem(*item));
      }
      CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile);
      g_partyModeManager.OnSongChange(true);

#ifdef HAS_PYTHON
      // informs python script currently running playback has started
      // (does nothing if python is not loaded)
      CServiceBroker::GetXBPython().OnPlayBackStarted(*m_itemCurrentFile);
#endif

      CVariant param;
      param["player"]["speed"] = 1;
      param["player"]["playerid"] = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();

      CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay",
                                                         m_itemCurrentFile, param);

      // we don't want a busy dialog when switching channels
      const auto appPlayer = GetComponent<CApplicationPlayer>();
      if (!m_itemCurrentFile->IsLiveTV() ||
          (!appPlayer->IsPlayingVideo() && !appPlayer->IsPlayingAudio()))
      {
        CGUIDialogBusy* dialog =
            CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(
                WINDOW_DIALOG_BUSY);
        if (dialog && !dialog->IsDialogRunning())
          dialog->WaitOnEvent(m_playerEvent);
      }

      return true;
    }
    break;

  case GUI_MSG_QUEUE_NEXT_ITEM:
    {
      // Check to see if our playlist player has a new item for us,
      // and if so, we check whether our current player wants the file
      int iNext = CServiceBroker::GetPlaylistPlayer().GetNextSong();
      PLAYLIST::CPlayList& playlist = CServiceBroker::GetPlaylistPlayer().GetPlaylist(
          CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist());
      if (iNext < 0 || iNext >= playlist.size())
      {
        GetComponent<CApplicationPlayer>()->OnNothingToQueueNotify();
        return true; // nothing to do
      }

      // ok, grab the next song
      CFileItem file(*playlist[iNext]);
      // handle plugin://
      CURL url(file.GetDynPath());
      if (url.IsProtocol("plugin"))
        XFILE::CPluginDirectory::GetPluginResult(url.Get(), file, false);

      // Don't queue if next media type is different from current one
      bool bNothingToQueue = false;

      const auto appPlayer = GetComponent<CApplicationPlayer>();
      if (!file.IsVideo() && appPlayer->IsPlayingVideo())
        bNothingToQueue = true;
      else if ((!file.IsAudio() || file.IsVideo()) && appPlayer->IsPlayingAudio())
        bNothingToQueue = true;

      if (bNothingToQueue)
      {
        appPlayer->OnNothingToQueueNotify();
        return true;
      }

#ifdef HAS_UPNP
      if (URIUtils::IsUPnP(file.GetDynPath()))
      {
        if (!XFILE::CUPnPDirectory::GetResource(file.GetDynURL(), file))
          return true;
      }
#endif

      // ok - send the file to the player, if it accepts it
      if (appPlayer->QueueNextFile(file))
      {
        // player accepted the next file
        m_nextPlaylistItem = iNext;
      }
      else
      {
        /* Player didn't accept next file: *ALWAYS* advance playlist in this case so the player can
            queue the next (if it wants to) and it doesn't keep looping on this song */
        CServiceBroker::GetPlaylistPlayer().SetCurrentSong(iNext);
      }

      return true;
    }
    break;

    case GUI_MSG_PLAY_TRAILER:
    {
      const CFileItem* item = dynamic_cast<CFileItem*>(message.GetItem().get());
      if (item == nullptr)
      {
        CLog::LogF(LOGERROR, "Supplied item is not a CFileItem! Trailer cannot be played.");
        return false;
      }

      std::unique_ptr<CFileItem> trailerItem =
          ContentUtils::GeneratePlayableTrailerItem(*item, g_localizeStrings.Get(20410));

      if (item->IsPlayList())
      {
        std::unique_ptr<CFileItemList> fileitemList = std::make_unique<CFileItemList>();
        fileitemList->Add(std::move(trailerItem));
        CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1,
                                                   static_cast<void*>(fileitemList.release()));
      }
      else
      {
        CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 1, 0,
                                                   static_cast<void*>(trailerItem.release()));
      }
      break;
    }

  case GUI_MSG_PLAYBACK_STOPPED:
    m_playerEvent.Set();
    ResetCurrentItem();
    PlaybackCleanup();
#ifdef HAS_PYTHON
    CServiceBroker::GetXBPython().OnPlayBackStopped();
#endif
     return true;

  case GUI_MSG_PLAYBACK_ENDED:
  {
    m_playerEvent.Set();
    const auto stackHelper = GetComponent<CApplicationStackHelper>();
    if (stackHelper->IsPlayingRegularStack() && stackHelper->HasNextStackPartFileItem())
    { // just play the next item in the stack
      PlayFile(stackHelper->SetNextStackPartCurrentFileItem(), "", true);
      return true;
    }
    ResetCurrentItem();
    if (!CServiceBroker::GetPlaylistPlayer().PlayNext(1, true))
      GetComponent<CApplicationPlayer>()->ClosePlayer();

    PlaybackCleanup();

#ifdef HAS_PYTHON
    CServiceBroker::GetXBPython().OnPlayBackEnded();
#endif
    return true;
  }

  case GUI_MSG_PLAYLISTPLAYER_STOPPED:
    ResetCurrentItem();
    if (GetComponent<CApplicationPlayer>()->IsPlaying())
      StopPlaying();
    PlaybackCleanup();
    return true;

  case GUI_MSG_PLAYBACK_AVSTARTED:
    m_playerEvent.Set();
#ifdef HAS_PYTHON
    // informs python script currently running playback has started
    // (does nothing if python is not loaded)
    CServiceBroker::GetXBPython().OnAVStarted(*m_itemCurrentFile);
#endif
    return true;

  case GUI_MSG_PLAYBACK_AVCHANGE:
#ifdef HAS_PYTHON
    // informs python script currently running playback has started
    // (does nothing if python is not loaded)
    CServiceBroker::GetXBPython().OnAVChange();
#endif
      return true;

  case GUI_MSG_PLAYBACK_ERROR:
    HELPERS::ShowOKDialogText(CVariant{16026}, CVariant{16027});
    return true;

  case GUI_MSG_PLAYLISTPLAYER_STARTED:
  case GUI_MSG_PLAYLISTPLAYER_CHANGED:
    {
      return true;
    }
    break;
  case GUI_MSG_FULLSCREEN:
    { // Switch to fullscreen, if we can
      CGUIComponent* gui = CServiceBroker::GetGUI();
      if (gui)
        gui->GetWindowManager().SwitchToFullScreen();

      return true;
    }
    break;
  case GUI_MSG_EXECUTE:
    if (message.GetNumStringParams())
      return ExecuteXBMCAction(message.GetStringParam(), message.GetItem());
    break;
  }
  return false;
}

bool CApplication::ExecuteXBMCAction(std::string actionStr, const CGUIListItemPtr &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);
  }
  else
  {
    // try translating the action from our ButtonTranslator
    unsigned int actionID;
    if (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 (item.IsAudio() || item.IsVideo() || 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;
}

// inform the user that the configuration data has moved from old XBMC location
// to new Kodi location - if applicable
void CApplication::ShowAppMigrationMessage()
{
  // .kodi_migration_complete will be created from the installer/packaging
  // once an old XBMC configuration was moved to the new Kodi location
  // if this is the case show the migration info to the user once which
  // tells him to have a look into the wiki where the move of configuration
  // is further explained.
  if (CFile::Exists("special://home/.kodi_data_was_migrated") &&
      !CFile::Exists("special://home/.kodi_migration_info_shown"))
  {
    HELPERS::ShowOKDialogText(CVariant{24128}, CVariant{24129});
    CFile tmpFile;
    // create the file which will prevent this dialog from appearing in the future
    tmpFile.OpenForWrite("special://home/.kodi_migration_info_shown");
    tmpFile.Close();
  }
}

void CApplication::ConfigureAndEnableAddons()
{
  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

  // update sound
  GetComponent<CApplicationPlayer>()->DoAudioWork();

  // 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();

#if defined(TARGET_DARWIN_OSX) && defined(SDL_FOUND)
  // There is an issue on OS X that several system services ask the cursor to become visible
  // during their startup routines.  Given that we can't control this, we hack it in by
  // forcing the
  if (CServiceBroker::GetWinSystem()->IsFullScreen())
  { // SDL thinks it's hidden
    Cocoa_HideMouse();
  }
#endif

  // Temporarily pause pausable jobs when viewing video/picture
  int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
  if (CurrentFileItem().IsVideo() ||
      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_DVD_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->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack())
    return stackHelper->GetCurrentStackPartFileItem();
  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())
      rc = stackHelper->GetStackTotalTimeMs() * 0.001;
    else
      rc = appPlayer->GetTotalTime() * 0.001;
  }

  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())
    {
      uint64_t startOfCurrentFile = stackHelper->GetCurrentStackPartStartTimeMs();
      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())
    {
      // 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->GetStackPartNumberAtTimeMs(static_cast<uint64_t>(dTime * 1000.0));
      uint64_t startOfNewFile = stackHelper->GetStackPartStartTimeMs(partNumberToPlay);
      if (partNumberToPlay == stackHelper->GetCurrentPartNumber())
        appPlayer->SeekTime(static_cast<uint64_t>(dTime * 1000.0) - startOfNewFile);
      else
      { // seeking to a new file
        stackHelper->SetStackPartCurrentFileItem(partNumberToPlay);
        CFileItem* item = new CFileItem(stackHelper->GetCurrentStackPartFileItem());
        item->SetStartOffset(static_cast<uint64_t>(dTime * 1000.0) - 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())
    {
      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())
      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, playlistId);

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

  // illegal playlist
  if (playlistId == PLAYLIST::TYPE_NONE || playlistId == PLAYLIST::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();
}
