/*
 *  Copyright (C) 2017-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 "WindowTranslator.h"
#include "Application.h"
#include "guilib/WindowIDs.h"
#include "pvr/PVRGUIActions.h"
#include "pvr/PVRManager.h"
#include "ServiceBroker.h"
#include "utils/log.h"
#include "utils/StringUtils.h"

#include <algorithm>
#include <cstring>
#include <stdlib.h>

const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName =
{
    { "home"                     , WINDOW_HOME },
    { "programs"                 , WINDOW_PROGRAMS },
    { "pictures"                 , WINDOW_PICTURES },
    { "filemanager"              , WINDOW_FILES },
    { "settings"                 , WINDOW_SETTINGS_MENU },
    { "music"                    , WINDOW_MUSIC_NAV },
    { "videos"                   , WINDOW_VIDEO_NAV },
    { "tvchannels"               , WINDOW_TV_CHANNELS },
    { "tvrecordings"             , WINDOW_TV_RECORDINGS },
    { "tvguide"                  , WINDOW_TV_GUIDE },
    { "tvtimers"                 , WINDOW_TV_TIMERS },
    { "tvsearch"                 , WINDOW_TV_SEARCH },
    { "radiochannels"            , WINDOW_RADIO_CHANNELS },
    { "radiorecordings"          , WINDOW_RADIO_RECORDINGS },
    { "radioguide"               , WINDOW_RADIO_GUIDE },
    { "radiotimers"              , WINDOW_RADIO_TIMERS },
    { "radiosearch"              , WINDOW_RADIO_SEARCH },
    { "gamecontrollers"          , WINDOW_DIALOG_GAME_CONTROLLERS },
    { "games"                    , WINDOW_GAMES },
    { "pvrguideinfo"             , WINDOW_DIALOG_PVR_GUIDE_INFO },
    { "pvrrecordinginfo"         , WINDOW_DIALOG_PVR_RECORDING_INFO },
    { "pvrradiordsinfo"          , WINDOW_DIALOG_PVR_RADIO_RDS_INFO },
    { "pvrtimersetting"          , WINDOW_DIALOG_PVR_TIMER_SETTING },
    { "pvrgroupmanager"          , WINDOW_DIALOG_PVR_GROUP_MANAGER },
    { "pvrchannelmanager"        , WINDOW_DIALOG_PVR_CHANNEL_MANAGER },
    { "pvrguidesearch"           , WINDOW_DIALOG_PVR_GUIDE_SEARCH },
    { "pvrchannelscan"           , WINDOW_DIALOG_PVR_CHANNEL_SCAN },
    { "pvrupdateprogress"        , WINDOW_DIALOG_PVR_UPDATE_PROGRESS },
    { "pvrosdchannels"           , WINDOW_DIALOG_PVR_OSD_CHANNELS },
    { "pvrchannelguide"          , WINDOW_DIALOG_PVR_CHANNEL_GUIDE },
    { "pvrosdguide"              , WINDOW_DIALOG_PVR_CHANNEL_GUIDE }, // backward compatibility to v17
    { "pvrosdteletext"           , WINDOW_DIALOG_OSD_TELETEXT },
    { "systeminfo"               , WINDOW_SYSTEM_INFORMATION },
    { "screencalibration"        , WINDOW_SCREEN_CALIBRATION },
    { "systemsettings"           , WINDOW_SETTINGS_SYSTEM },
    { "servicesettings"          , WINDOW_SETTINGS_SERVICE },
    { "pvrsettings"              , WINDOW_SETTINGS_MYPVR },
    { "playersettings"           , WINDOW_SETTINGS_PLAYER },
    { "mediasettings"            , WINDOW_SETTINGS_MEDIA },
    { "interfacesettings"        , WINDOW_SETTINGS_INTERFACE },
    { "appearancesettings"       , WINDOW_SETTINGS_INTERFACE },	// backward compatibility to v16
    { "gamesettings"             , WINDOW_SETTINGS_MYGAMES },
    { "videoplaylist"            , WINDOW_VIDEO_PLAYLIST },
    { "loginscreen"              , WINDOW_LOGIN_SCREEN },
    { "profiles"                 , WINDOW_SETTINGS_PROFILES },
    { "skinsettings"             , WINDOW_SKIN_SETTINGS },
    { "addonbrowser"             , WINDOW_ADDON_BROWSER },
    { "yesnodialog"              , WINDOW_DIALOG_YES_NO },
    { "progressdialog"           , WINDOW_DIALOG_PROGRESS },
    { "virtualkeyboard"          , WINDOW_DIALOG_KEYBOARD },
    { "volumebar"                , WINDOW_DIALOG_VOLUME_BAR },
    { "submenu"                  , WINDOW_DIALOG_SUB_MENU },
    { "favourites"               , WINDOW_DIALOG_FAVOURITES },
    { "contextmenu"              , WINDOW_DIALOG_CONTEXT_MENU },
    { "notification"             , WINDOW_DIALOG_KAI_TOAST },
    { "numericinput"             , WINDOW_DIALOG_NUMERIC },
    { "gamepadinput"             , WINDOW_DIALOG_GAMEPAD },
    { "shutdownmenu"             , WINDOW_DIALOG_BUTTON_MENU },
    { "playercontrols"           , WINDOW_DIALOG_PLAYER_CONTROLS },
    { "playerprocessinfo"        , WINDOW_DIALOG_PLAYER_PROCESS_INFO },
    { "seekbar"                  , WINDOW_DIALOG_SEEK_BAR },
    { "musicosd"                 , WINDOW_DIALOG_MUSIC_OSD },
    { "addonsettings"            , WINDOW_DIALOG_ADDON_SETTINGS },
    { "visualisationpresetlist"  , WINDOW_DIALOG_VIS_PRESET_LIST },
    { "osdcmssettings"           , WINDOW_DIALOG_CMS_OSD_SETTINGS },
    { "osdvideosettings"         , WINDOW_DIALOG_VIDEO_OSD_SETTINGS },
    { "osdaudiosettings"         , WINDOW_DIALOG_AUDIO_OSD_SETTINGS },
    { "osdsubtitlesettings"      , WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS },
    { "videobookmarks"           , WINDOW_DIALOG_VIDEO_BOOKMARKS },
    { "filebrowser"              , WINDOW_DIALOG_FILE_BROWSER },
    { "networksetup"             , WINDOW_DIALOG_NETWORK_SETUP },
    { "mediasource"              , WINDOW_DIALOG_MEDIA_SOURCE },
    { "profilesettings"          , WINDOW_DIALOG_PROFILE_SETTINGS },
    { "locksettings"             , WINDOW_DIALOG_LOCK_SETTINGS },
    { "contentsettings"          , WINDOW_DIALOG_CONTENT_SETTINGS },
    { "libexportsettings"        , WINDOW_DIALOG_LIBEXPORT_SETTINGS },
    { "songinformation"          , WINDOW_DIALOG_SONG_INFO },
    { "smartplaylisteditor"      , WINDOW_DIALOG_SMART_PLAYLIST_EDITOR },
    { "smartplaylistrule"        , WINDOW_DIALOG_SMART_PLAYLIST_RULE },
    { "busydialog"               , WINDOW_DIALOG_BUSY },
    { "busydialognocancel"       , WINDOW_DIALOG_BUSY_NOCANCEL },
    { "pictureinfo"              , WINDOW_DIALOG_PICTURE_INFO },
    { "accesspoints"             , WINDOW_DIALOG_ACCESS_POINTS },
    { "fullscreeninfo"           , WINDOW_DIALOG_FULLSCREEN_INFO },
    { "sliderdialog"             , WINDOW_DIALOG_SLIDER },
    { "addoninformation"         , WINDOW_DIALOG_ADDON_INFO },
    { "subtitlesearch"           , WINDOW_DIALOG_SUBTITLES },
    { "musicplaylist"            , WINDOW_MUSIC_PLAYLIST },
    { "musicplaylisteditor"      , WINDOW_MUSIC_PLAYLIST_EDITOR },
    { "infoprovidersettings"     , WINDOW_DIALOG_INFOPROVIDER_SETTINGS },
    { "teletext"                 , WINDOW_DIALOG_OSD_TELETEXT },
    { "selectdialog"             , WINDOW_DIALOG_SELECT },
    { "musicinformation"         , WINDOW_DIALOG_MUSIC_INFO },
    { "okdialog"                 , WINDOW_DIALOG_OK },
    { "movieinformation"         , WINDOW_DIALOG_VIDEO_INFO },
    { "textviewer"               , WINDOW_DIALOG_TEXT_VIEWER },
    { "fullscreenvideo"          , WINDOW_FULLSCREEN_VIDEO },
    { "fullscreenlivetv"         , WINDOW_FULLSCREEN_LIVETV }, // virtual window for fullscreen radio, uses WINDOW_FULLSCREEN_VIDEO as fallback
    { "fullscreenlivetvpreview"  , WINDOW_FULLSCREEN_LIVETV_PREVIEW }, // Live TV channel preview
    { "fullscreenlivetvinput"    , WINDOW_FULLSCREEN_LIVETV_INPUT }, // Livr TV direct channel number input
    { "fullscreenradio"          , WINDOW_FULLSCREEN_RADIO }, // virtual window for fullscreen radio, uses WINDOW_VISUALISATION as fallback
    { "fullscreenradiopreview"   , WINDOW_FULLSCREEN_RADIO_PREVIEW }, // PVR Radio channel preview
    { "fullscreenradioinput"     , WINDOW_FULLSCREEN_RADIO_INPUT }, // PVR radio direct channel number input
    { "fullscreengame"           , WINDOW_FULLSCREEN_GAME },
    { "visualisation"            , WINDOW_VISUALISATION },
    { "slideshow"                , WINDOW_SLIDESHOW },
    { "weather"                  , WINDOW_WEATHER },
    { "screensaver"              , WINDOW_SCREENSAVER },
    { "videoosd"                 , WINDOW_DIALOG_VIDEO_OSD },
    { "videomenu"                , WINDOW_VIDEO_MENU },
    { "videotimeseek"            , WINDOW_VIDEO_TIME_SEEK },
    { "splash"                   , WINDOW_SPLASH },
    { "startwindow"              , WINDOW_START },
    { "startup"                  , WINDOW_STARTUP_ANIM },
    { "peripheralsettings"       , WINDOW_DIALOG_PERIPHERAL_SETTINGS },
    { "extendedprogressdialog"   , WINDOW_DIALOG_EXT_PROGRESS },
    { "mediafilter"              , WINDOW_DIALOG_MEDIA_FILTER },
    { "addon"                    , WINDOW_ADDON_START },
    { "eventlog"                 , WINDOW_EVENT_LOG},
    { "tvtimerrules"             , WINDOW_TV_TIMER_RULES},
    { "radiotimerrules"          , WINDOW_RADIO_TIMER_RULES},
    { "gameosd"                  , WINDOW_DIALOG_GAME_OSD },
    { "gamevideofilter"          , WINDOW_DIALOG_GAME_VIDEO_FILTER },
    { "gamestretchmode"          , WINDOW_DIALOG_GAME_STRETCH_MODE },
    { "gamevolume"               , WINDOW_DIALOG_GAME_VOLUME },
    { "gameadvancedsettings"     , WINDOW_DIALOG_GAME_ADVANCED_SETTINGS },
    { "gamevideorotation"        , WINDOW_DIALOG_GAME_VIDEO_ROTATION },
};

namespace
{
struct FallbackWindowMapping
{
  int origin;
  int target;
};

static const std::vector<FallbackWindowMapping> FallbackWindows =
{
    { WINDOW_FULLSCREEN_LIVETV,         WINDOW_FULLSCREEN_VIDEO },
    { WINDOW_FULLSCREEN_LIVETV_INPUT,   WINDOW_FULLSCREEN_LIVETV },
    { WINDOW_FULLSCREEN_LIVETV_PREVIEW, WINDOW_FULLSCREEN_LIVETV },
    { WINDOW_FULLSCREEN_RADIO,          WINDOW_VISUALISATION },
    { WINDOW_FULLSCREEN_RADIO_INPUT,    WINDOW_FULLSCREEN_RADIO },
    { WINDOW_FULLSCREEN_RADIO_PREVIEW,  WINDOW_FULLSCREEN_RADIO },
};
} // anonymous namespace


bool CWindowTranslator::WindowNameCompare::operator()(const WindowMapItem &lhs, const WindowMapItem &rhs) const
{
  return std::strcmp(lhs.windowName, rhs.windowName) < 0;
}

bool CWindowTranslator::WindowIDCompare::operator()(const WindowMapItem &lhs, const WindowMapItem &rhs) const
{
  return lhs.windowId < rhs.windowId;
}

void CWindowTranslator::GetWindows(std::vector<std::string> &windowList)
{
  windowList.clear();
  windowList.reserve(WindowMappingByName.size());
  for (auto itMapping : WindowMappingByName)
    windowList.push_back(itMapping.windowName);
}

int CWindowTranslator::TranslateWindow(const std::string &window)
{
  std::string strWindow(window);
  if (strWindow.empty())
    return WINDOW_INVALID;

  StringUtils::ToLower(strWindow);

  // Eliminate .xml
  if (StringUtils::EndsWith(strWindow, ".xml"))
    strWindow = strWindow.substr(0, strWindow.size() - 4);

  // window12345, for custom window to be keymapped
  if (strWindow.length() > 6 && StringUtils::StartsWith(strWindow, "window"))
    strWindow = strWindow.substr(6);

  // Drop "my" prefix
  if (StringUtils::StartsWith(strWindow, "my"))
    strWindow = strWindow.substr(2);

  if (StringUtils::IsNaturalNumber(strWindow))
  {
    // Allow a full window ID or a delta ID
    int iWindow = atoi(strWindow.c_str());
    if (iWindow > WINDOW_INVALID)
      return iWindow;

    return WINDOW_HOME + iWindow;
  }

  // Run through the window structure
  auto it = WindowMappingByName.find(WindowMapItem{ strWindow.c_str() });
  if (it != WindowMappingByName.end())
    return it->windowId;

  CLog::Log(LOGERROR, "Window Translator: Can't find window %s", window.c_str());

  return WINDOW_INVALID;
}

std::string CWindowTranslator::TranslateWindow(int windowId)
{
  static auto reverseWindowMapping = CreateWindowMappingByID();

  windowId = GetVirtualWindow(windowId);

  auto it = reverseWindowMapping.find(WindowMapItem{ "", windowId });
  if (it != reverseWindowMapping.end())
    return it->windowName;

  return "";
}

int CWindowTranslator::GetFallbackWindow(int windowId)
{
  auto it = std::find_if(FallbackWindows.begin(), FallbackWindows.end(),
    [windowId](const FallbackWindowMapping& mapping)
    {
      return mapping.origin == windowId;
    });

  if (it != FallbackWindows.end())
    return it->target;

  // For add-on windows use WINDOW_ADDON_START because ID is dynamic
  if (WINDOW_ADDON_START < windowId && windowId <= WINDOW_ADDON_END)
    return WINDOW_ADDON_START;

  return -1;
}

CWindowTranslator::WindowMapByID CWindowTranslator::CreateWindowMappingByID()
{
  WindowMapByID reverseWindowMapping;

  reverseWindowMapping.insert(WindowMappingByName.begin(), WindowMappingByName.end());

  return reverseWindowMapping;
}

int CWindowTranslator::GetVirtualWindow(int windowId)
{
  if (windowId == WINDOW_FULLSCREEN_VIDEO)
  {
    // check if we're in a DVD menu
    if (g_application.GetAppPlayer().IsInMenu())
      return WINDOW_VIDEO_MENU;
    // special casing for Live TV
    else if (g_application.CurrentFileItem().HasPVRChannelInfoTag())
    {
      if (CServiceBroker::GetPVRManager().GUIActions()->GetChannelNumberInputHandler().HasChannelNumber())
        return WINDOW_FULLSCREEN_LIVETV_INPUT;
      else if (CServiceBroker::GetPVRManager().GUIActions()->GetChannelNavigator().IsPreview())
        return WINDOW_FULLSCREEN_LIVETV_PREVIEW;
      else
        return WINDOW_FULLSCREEN_LIVETV;
    }
    // special casing for numeric seek
    else if (g_application.GetAppPlayer().GetSeekHandler().HasTimeCode())
      return WINDOW_VIDEO_TIME_SEEK;
  }
  else if (windowId == WINDOW_VISUALISATION)
  {
    // special casing for PVR radio
    if (g_application.CurrentFileItem().HasPVRChannelInfoTag())
    {
      if (CServiceBroker::GetPVRManager().GUIActions()->GetChannelNumberInputHandler().HasChannelNumber())
        return WINDOW_FULLSCREEN_RADIO_INPUT;
      else if (CServiceBroker::GetPVRManager().GUIActions()->GetChannelNavigator().IsPreview())
        return WINDOW_FULLSCREEN_RADIO_PREVIEW;
      else
        return WINDOW_FULLSCREEN_RADIO;
    }
    // special casing for numeric seek
    else if (g_application.GetAppPlayer().GetSeekHandler().HasTimeCode())
      return WINDOW_VIDEO_TIME_SEEK;
  }

  return windowId;
}
