/*
 *  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 "GUIDialogVideoInfo.h"

#include "Application.h"
#include "ContextMenuManager.h"
#include "FileItem.h"
#include "GUIPassword.h"
#include "GUIUserMessages.h"
#include "ServiceBroker.h"
#include "TextureCache.h"
#include "Util.h"
#include "dialogs/GUIDialogFileBrowser.h"
#include "dialogs/GUIDialogProgress.h"
#include "dialogs/GUIDialogSelect.h"
#include "dialogs/GUIDialogYesNo.h"
#include "filesystem/Directory.h"
#include "filesystem/File.h"
#include "filesystem/VideoDatabaseDirectory.h"
#include "filesystem/VideoDatabaseDirectory/QueryParams.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIImage.h"
#include "guilib/GUIKeyboardFactory.h"
#include "guilib/GUIWindow.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "input/Key.h"
#include "messaging/ApplicationMessenger.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "music/MusicDatabase.h"
#include "profiles/ProfileManager.h"
#include "settings/AdvancedSettings.h"
#include "settings/MediaSourceSettings.h"
#include "settings/SettingUtils.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "settings/lib/Setting.h"
#include "storage/MediaManager.h"
#include "utils/FileExtensionProvider.h"
#include "utils/FileUtils.h"
#include "utils/SortUtils.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/Variant.h"
#include "video/VideoInfoScanner.h"
#include "video/VideoInfoTag.h"
#include "video/VideoThumbLoader.h"
#include "video/tags/VideoTagLoaderFFmpeg.h"
#include "video/windows/GUIWindowVideoNav.h"

#include <iterator>
#include <string>

using namespace XFILE::VIDEODATABASEDIRECTORY;
using namespace XFILE;
using namespace KODI::MESSAGING;

#define CONTROL_IMAGE                3
#define CONTROL_TEXTAREA             4
#define CONTROL_BTN_TRACKS           5
#define CONTROL_BTN_REFRESH          6
#define CONTROL_BTN_USERRATING       7
#define CONTROL_BTN_PLAY             8
#define CONTROL_BTN_RESUME           9
#define CONTROL_BTN_GET_THUMB       10
#define CONTROL_BTN_PLAY_TRAILER    11
#define CONTROL_BTN_GET_FANART      12
#define CONTROL_BTN_DIRECTOR        13

#define CONTROL_LIST                50

// predicate used by sorting and set_difference
bool compFileItemsByDbId(const CFileItemPtr& lhs, const CFileItemPtr& rhs)
{
  return lhs->HasVideoInfoTag() && rhs->HasVideoInfoTag() && lhs->GetVideoInfoTag()->m_iDbId < rhs->GetVideoInfoTag()->m_iDbId;
}

CGUIDialogVideoInfo::CGUIDialogVideoInfo(void)
  : CGUIDialog(WINDOW_DIALOG_VIDEO_INFO, "DialogVideoInfo.xml"),
  m_movieItem(new CFileItem),
  m_castList(new CFileItemList)
{
  m_loadType = KEEP_IN_MEMORY;
}

CGUIDialogVideoInfo::~CGUIDialogVideoInfo(void)
{
  delete m_castList;
}

bool CGUIDialogVideoInfo::OnMessage(CGUIMessage& message)
{
  switch ( message.GetMessage() )
  {
  case GUI_MSG_WINDOW_DEINIT:
    {
      ClearCastList();

      if (m_startUserrating != m_movieItem->GetVideoInfoTag()->m_iUserRating)
      {
        CVideoDatabase db;
        if (db.Open())
        {
          m_hasUpdatedUserrating = true;
          db.SetVideoUserRating(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_iUserRating, m_movieItem->GetVideoInfoTag()->m_type);
          db.Close();
        }
      }
    }
    break;

  case GUI_MSG_CLICKED:
    {
      int iControl = message.GetSenderId();
      if (iControl == CONTROL_BTN_REFRESH)
      {
        if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeTvShow)
        {
          bool bCanceled=false;
          if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20377}, CVariant{20378}, bCanceled, CVariant{ "" }, CVariant{ "" }, CGUIDialogYesNo::NO_TIMEOUT))
          {
            m_bRefreshAll = true;
            CVideoDatabase db;
            if (db.Open())
            {
              db.SetPathHash(m_movieItem->GetVideoInfoTag()->m_strPath,"");
              db.Close();
            }
          }
          else
            m_bRefreshAll = false;

          if (bCanceled)
            return false;
        }
        m_bRefresh = true;
        Close();
        return true;
      }
      else if (iControl == CONTROL_BTN_TRACKS)
      {
        m_bViewReview = !m_bViewReview;
        Update();
      }
      else if (iControl == CONTROL_BTN_PLAY)
      {
        Play();
      }
      else if (iControl == CONTROL_BTN_USERRATING)
      {
        OnSetUserrating();
      }
      else if (iControl == CONTROL_BTN_RESUME)
      {
        Play(true);
      }
      else if (iControl == CONTROL_BTN_GET_THUMB)
      {
        OnGetArt();
      }
      else if (iControl == CONTROL_BTN_PLAY_TRAILER)
      {
        PlayTrailer();
      }
      else if (iControl == CONTROL_BTN_GET_FANART)
      {
        OnGetFanart();
      }
      else if (iControl == CONTROL_BTN_DIRECTOR)
      {
        auto directors = m_movieItem->GetVideoInfoTag()->m_director;
        if (directors.size() == 0)
          return true;
        if (directors.size() == 1)
          OnSearch(directors[0]);
        else
        {
          auto pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
          if (pDlgSelect)
          {
            pDlgSelect->Reset();
            pDlgSelect->SetHeading(CVariant{22080});
            for (const auto &director: directors)
              pDlgSelect->Add(director);
            pDlgSelect->Open();

            int iItem = pDlgSelect->GetSelectedItem();
            if (iItem < 0)
              return true;
            OnSearch(directors[iItem]);
          }
        }
      }
      else if (iControl == CONTROL_LIST)
      {
        int iAction = message.GetParam1();
        if (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction)
        {
          CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl);
          OnMessage(msg);
          int iItem = msg.GetParam1();
          if (iItem < 0 || iItem >= m_castList->Size())
            break;
          std::string strItem = m_castList->Get(iItem)->GetLabel();
          if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection)
          {
            SetMovie(m_castList->Get(iItem).get());
            Close();
            Open();
          }
          else
            OnSearch(strItem);
        }
      }
    }
    break;
  case GUI_MSG_NOTIFY_ALL:
    {
      if (IsActive() && message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
      {
        CFileItemPtr item = std::static_pointer_cast<CFileItem>(message.GetItem());
        if (item && m_movieItem->IsPath(item->GetPath()))
        { // Just copy over the stream details and the thumb if we don't already have one
          if (!m_movieItem->HasArt("thumb"))
            m_movieItem->SetArt("thumb", item->GetArt("thumb"));
          m_movieItem->GetVideoInfoTag()->m_streamDetails = item->GetVideoInfoTag()->m_streamDetails;
        }
        return true;
      }
    }
  }

  return CGUIDialog::OnMessage(message);
}

void CGUIDialogVideoInfo::OnInitWindow()
{
  m_bRefresh = false;
  m_bRefreshAll = true;
  m_hasUpdatedThumb = false;
  m_hasUpdatedUserrating = false;
  m_bViewReview = true;

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

  CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_REFRESH,
      (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) &&
      !StringUtils::StartsWithNoCase(m_movieItem->GetProperty("xxuniqueid").c_str(), "xx"));
  CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB,
      (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) &&
      !StringUtils::StartsWithNoCase(m_movieItem->GetVideoInfoTag()->
      GetUniqueID().c_str(), "plugin"));
  // Disable video user rating button for plugins and sets as they don't have tables to save this
  CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_USERRATING, !m_movieItem->IsPlugin() && m_movieItem->GetVideoInfoTag()->m_type != MediaTypeVideoCollection);

  VIDEODB_CONTENT_TYPE type = static_cast<VIDEODB_CONTENT_TYPE>(m_movieItem->GetVideoContentType());
  if (type == VIDEODB_CONTENT_TVSHOWS || type == VIDEODB_CONTENT_MOVIES)
    CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_FANART, (profileManager->
        GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) &&
        !StringUtils::StartsWithNoCase(m_movieItem->GetVideoInfoTag()->
        GetUniqueID().c_str(), "plugin"));
  else
    CONTROL_DISABLE(CONTROL_BTN_GET_FANART);

  Update();

  CGUIDialog::OnInitWindow();
}

bool CGUIDialogVideoInfo::OnAction(const CAction &action)
{
  int userrating = m_movieItem->GetVideoInfoTag()->m_iUserRating;
  if (action.GetID() == ACTION_INCREASE_RATING)
  {
    SetUserrating(userrating + 1);
    return true;
  }
  else if (action.GetID() == ACTION_DECREASE_RATING)
  {
    SetUserrating(userrating - 1);
    return true;
  }
  else if (action.GetID() == ACTION_SHOW_INFO)
  {
    Close();
    return true;
  }
  return CGUIDialog::OnAction(action);
}

void CGUIDialogVideoInfo::SetUserrating(int userrating) const
{
  userrating = std::max(userrating, 0);
  userrating = std::min(userrating, 10);
  if (userrating != m_movieItem->GetVideoInfoTag()->m_iUserRating)
  {
    m_movieItem->GetVideoInfoTag()->SetUserrating(userrating);

    // 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_movieItem);
    CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
  }
}

void CGUIDialogVideoInfo::SetMovie(const CFileItem *item)
{
  *m_movieItem = *item;

  // setup cast list
  ClearCastList();

  // When the scraper throws an error, the video tag can be null here
  if (!item->HasVideoInfoTag())
    return;

  MediaType type = item->GetVideoInfoTag()->m_type;

  m_startUserrating = m_movieItem->GetVideoInfoTag()->m_iUserRating;

  if (type == MediaTypeMusicVideo)
  { // music video
    CMusicDatabase database;
    database.Open();
    const std::vector<std::string> &artists = m_movieItem->GetVideoInfoTag()->m_artist;
    for (std::vector<std::string>::const_iterator it = artists.begin(); it != artists.end(); ++it)
    {
      int idArtist = database.GetArtistByName(*it);
      std::string thumb = database.GetArtForItem(idArtist, MediaTypeArtist, "thumb");
      CFileItemPtr item(new CFileItem(*it));
      if (!thumb.empty())
        item->SetArt("thumb", thumb);
      item->SetArt("icon", "DefaultArtist.png");
      m_castList->Add(item);
    }
  }
  else if (type == MediaTypeVideoCollection)
  {
    CVideoDatabase database;
    database.Open();
    database.GetMoviesNav(m_movieItem->GetPath(), *m_castList, -1, -1, -1, -1, -1, -1,
                          m_movieItem->GetVideoInfoTag()->m_set.id, -1,
                          SortDescription(), VideoDbDetailsAll);
    m_castList->Sort(SortBySortTitle, SortOrderDescending);
    CVideoThumbLoader loader;
    for (auto& item : *m_castList)
      loader.LoadItem(item.get());
  }
  else
  { // movie/show/episode
    for (CVideoInfoTag::iCast it = m_movieItem->GetVideoInfoTag()->m_cast.begin(); it != m_movieItem->GetVideoInfoTag()->m_cast.end(); ++it)
    {
      CFileItemPtr item(new CFileItem(it->strName));
      if (!it->thumb.empty())
        item->SetArt("thumb", it->thumb);
      else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS))
      { // backward compatibility
        std::string thumb = CScraperUrl::GetThumbUrl(it->thumbUrl.GetFirstUrlByType());
        if (!thumb.empty())
        {
          item->SetArt("thumb", thumb);
          CTextureCache::GetInstance().BackgroundCacheImage(thumb);
        }
      }
      item->SetArt("icon", "DefaultActor.png");
      item->SetLabel(it->strName);
      item->SetLabel2(it->strRole);
      m_castList->Add(item);
    }
  }

  if (type == MediaTypeMovie)
  {
    // local trailers should always override non-local, so check
    // for a local one if the registered trailer is online
    if (m_movieItem->GetVideoInfoTag()->m_strTrailer.empty() ||
        URIUtils::IsInternetStream(m_movieItem->GetVideoInfoTag()->m_strTrailer))
    {
      std::string localTrailer = m_movieItem->FindTrailer();
      if (!localTrailer.empty())
      {
        m_movieItem->GetVideoInfoTag()->m_strTrailer = localTrailer;
        CVideoDatabase database;
        if (database.Open())
        {
          database.SetSingleValue(VIDEODB_CONTENT_MOVIES, VIDEODB_ID_TRAILER,
                                  m_movieItem->GetVideoInfoTag()->m_iDbId,
                                  m_movieItem->GetVideoInfoTag()->m_strTrailer);
          database.Close();
          CUtil::DeleteVideoDatabaseDirectoryCache();
        }
      }
    }
  }

  m_castList->SetContent(CMediaTypes::ToPlural(type));

  CVideoThumbLoader loader;
  loader.LoadItem(m_movieItem.get());
}

void CGUIDialogVideoInfo::Update()
{
  // setup plot text area
  std::shared_ptr<CSettingList> setting(std::dynamic_pointer_cast<CSettingList>( 
    CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS)));
  std::string strTmp = m_movieItem->GetVideoInfoTag()->m_strPlot;
  if (m_movieItem->GetVideoInfoTag()->m_type != MediaTypeTvShow)
    if (m_movieItem->GetVideoInfoTag()->GetPlayCount() == 0 && setting &&
        ((m_movieItem->GetVideoInfoTag()->m_type == MediaTypeMovie &&
          !CSettingUtils::FindIntInList(setting,
                                        CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_MOVIES)) ||
         (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeEpisode &&
          !CSettingUtils::FindIntInList(
              setting, CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_TVSHOWEPISODES))))
      strTmp = g_localizeStrings.Get(20370);

  StringUtils::Trim(strTmp);
  SetLabel(CONTROL_TEXTAREA, strTmp);

  CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_castList);
  OnMessage(msg);

  if (GetControl(CONTROL_BTN_TRACKS)) // if no CONTROL_BTN_TRACKS found - allow skinner full visibility control over CONTROL_TEXTAREA and CONTROL_LIST
  {
    if (m_bViewReview)
    {
      if (!m_movieItem->GetVideoInfoTag()->m_artist.empty())
      {
        SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 133);
      }
      else if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection)
      {
        SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 20342);
      }
      else
      {
        SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 206);
      }

      SET_CONTROL_HIDDEN(CONTROL_LIST);
      SET_CONTROL_VISIBLE(CONTROL_TEXTAREA);
    }
    else
    {
      SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 207);

      SET_CONTROL_HIDDEN(CONTROL_TEXTAREA);
      SET_CONTROL_VISIBLE(CONTROL_LIST);
    }
  }

  // Check for resumability
  if (m_movieItem->GetVideoInfoTag()->GetResumePoint().timeInSeconds > 0.0)
    CONTROL_ENABLE(CONTROL_BTN_RESUME);
  else
    CONTROL_DISABLE(CONTROL_BTN_RESUME);

  CONTROL_ENABLE(CONTROL_BTN_PLAY);

  // update the thumbnail
  CGUIControl* pControl = GetControl(CONTROL_IMAGE);
  if (pControl)
  {
    CGUIImage* pImageControl = static_cast<CGUIImage*>(pControl);
    pImageControl->FreeResources();
    pImageControl->SetFileName(m_movieItem->GetArt("thumb"));
  }
  // tell our GUI to completely reload all controls (as some of them
  // are likely to have had this image in use so will need refreshing)
  if (m_hasUpdatedThumb)
  {
    CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS);
    CServiceBroker::GetGUI()->GetWindowManager().SendMessage(reload);
  }
}

bool CGUIDialogVideoInfo::NeedRefresh() const
{
  return m_bRefresh;
}

bool CGUIDialogVideoInfo::RefreshAll() const
{
  return m_bRefreshAll;
}

void CGUIDialogVideoInfo::OnSearch(std::string& strSearch)
{
  CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
  if (progress)
  {
    progress->SetHeading(CVariant{194});
    progress->SetLine(0, CVariant{strSearch});
    progress->SetLine(1, CVariant{""});
    progress->SetLine(2, CVariant{""});
    progress->Open();
    progress->Progress();
  }
  CFileItemList items;
  DoSearch(strSearch, items);

  if (progress)
    progress->Close();

  if (items.Size())
  {
    CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
    if (pDlgSelect)
    {
      pDlgSelect->Reset();
      pDlgSelect->SetHeading(CVariant{283});

      CVideoThumbLoader loader;
      for (int i = 0; i < items.Size(); i++)
      {
        if (items[i]->HasVideoInfoTag() &&
          items[i]->GetVideoInfoTag()->GetPlayCount() > 0)
          items[i]->SetLabel2(g_localizeStrings.Get(16102));

        loader.LoadItem(items[i].get());
        pDlgSelect->Add(*items[i]);
      }

      pDlgSelect->SetUseDetails(true);
      pDlgSelect->Open();

      int iItem = pDlgSelect->GetSelectedItem();
      if (iItem < 0)
        return;

      OnSearchItemFound(items[iItem].get());
    }
  }
  else
  {
    HELPERS::ShowOKDialogText(CVariant{194}, CVariant{284});
  }
}

void CGUIDialogVideoInfo::DoSearch(std::string& strSearch, CFileItemList& items) const
{
  CVideoDatabase db;
  if (!db.Open())
    return;

  CFileItemList movies;
  db.GetMoviesByActor(strSearch, movies);
  for (int i = 0; i < movies.Size(); ++i)
  {
    std::string label = movies[i]->GetVideoInfoTag()->m_strTitle;
    if (movies[i]->GetVideoInfoTag()->HasYear())
      label += StringUtils::Format(" (%i)", movies[i]->GetVideoInfoTag()->GetYear());
    movies[i]->SetLabel(label);
  }
  CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20338) + "] ", items);

  db.GetTvShowsByActor(strSearch, movies);
  for (int i = 0; i < movies.Size(); ++i)
  {
    std::string label = movies[i]->GetVideoInfoTag()->m_strShowTitle;
    if (movies[i]->GetVideoInfoTag()->HasYear())
      label += StringUtils::Format(" (%i)", movies[i]->GetVideoInfoTag()->GetYear());
    movies[i]->SetLabel(label);
  }
  CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20364) + "] ", items);

  db.GetEpisodesByActor(strSearch, movies);
  for (int i = 0; i < movies.Size(); ++i)
  {
    std::string label = movies[i]->GetVideoInfoTag()->m_strTitle + " (" +  movies[i]->GetVideoInfoTag()->m_strShowTitle + ")";
    movies[i]->SetLabel(label);
  }
  CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20359) + "] ", items);

  db.GetMusicVideosByArtist(strSearch, movies);
  for (int i = 0; i < movies.Size(); ++i)
  {
    std::string label = StringUtils::Join(movies[i]->GetVideoInfoTag()->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + " - " + movies[i]->GetVideoInfoTag()->m_strTitle;
    if (movies[i]->GetVideoInfoTag()->HasYear())
      label += StringUtils::Format(" (%i)", movies[i]->GetVideoInfoTag()->GetYear());
    movies[i]->SetLabel(label);
  }
  CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20391) + "] ", items);
  db.Close();
}

void CGUIDialogVideoInfo::OnSearchItemFound(const CFileItem* pItem)
{
  VIDEODB_CONTENT_TYPE type = static_cast<VIDEODB_CONTENT_TYPE>(pItem->GetVideoContentType());

  CVideoDatabase db;
  if (!db.Open())
    return;

  CVideoInfoTag movieDetails;
  if (type == VIDEODB_CONTENT_MOVIES)
    db.GetMovieInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId);
  if (type == VIDEODB_CONTENT_EPISODES)
    db.GetEpisodeInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId);
  if (type == VIDEODB_CONTENT_TVSHOWS)
    db.GetTvShowInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId);
  if (type == VIDEODB_CONTENT_MUSICVIDEOS)
    db.GetMusicVideoInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId);
  db.Close();

  CFileItem item(*pItem);
  *item.GetVideoInfoTag() = movieDetails;
  SetMovie(&item);
  // refresh our window entirely
  Close();
  Open();
}

void CGUIDialogVideoInfo::ClearCastList()
{
  CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST);
  OnMessage(msg);
  m_castList->Clear();
}

void CGUIDialogVideoInfo::Play(bool resume)
{
  if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeTvShow)
  {
    std::string strPath;
    if (m_movieItem->IsPlugin())
    {
      strPath = m_movieItem->GetPath();
      Close();
      if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV)
      {
        CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()->
                            GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE, 0);
        message.SetStringParam(strPath);
        CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
      }
      else
        CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath);
    }
    else
    {
      strPath = StringUtils::Format("videodb://tvshows/titles/%i/",
                                    m_movieItem->GetVideoInfoTag()->m_iDbId);
      Close();
      CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath);
    }
    return;
  }

  if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection)
  {
    std::string strPath = StringUtils::Format("videodb://movies/sets/%i/?setid=%i",m_movieItem->GetVideoInfoTag()->m_iDbId,m_movieItem->GetVideoInfoTag()->m_iDbId);
    Close();
    CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV, strPath);
    return;
  }

  CGUIWindowVideoNav* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowVideoNav>(WINDOW_VIDEO_NAV);
  if (pWindow)
  {
    // close our dialog
    Close(true);
    if (resume)
      m_movieItem->m_lStartOffset = STARTOFFSET_RESUME;
    else if (!CGUIWindowVideoBase::ShowResumeMenu(*m_movieItem))
    {
      // The Resume dialog was closed without any choice
      Open();
      return;
    }
    pWindow->PlayMovie(m_movieItem.get());
  }
}

namespace
{
// Add art types required in Kodi and configured by the user
void AddHardCodedAndExtendedArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag)
{
  for (const auto& artType : CVideoThumbLoader::GetArtTypes(tag.m_type))
  {
    if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend())
      artTypes.emplace_back(artType);
  }
}

// Add art types currently assigned to the media item
void AddCurrentArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag,
  CVideoDatabase& db)
{
  std::map<std::string, std::string> currentArt;
  db.GetArtForItem(tag.m_iDbId, tag.m_type, currentArt);
  for (const auto& art : currentArt)
  {
    if (!art.second.empty() && find(artTypes.cbegin(), artTypes.cend(), art.first) == artTypes.cend())
      artTypes.push_back(art.first);
  }
}

// Add art types that exist for other media items of the same type
void AddMediaTypeArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag,
  CVideoDatabase& db)
{
  std::vector<std::string> dbArtTypes;
  db.GetArtTypes(tag.m_type, dbArtTypes);
  for (const auto& artType : dbArtTypes)
  {
    if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend())
      artTypes.push_back(artType);
  }
}

// Add art types from available but unassigned artwork for this media item
void AddAvailableArtTypes(std::vector<std::string>& artTypes, const CVideoInfoTag& tag,
  CVideoDatabase& db)
{
  for (const auto& artType : db.GetAvailableArtTypesForItem(tag.m_iDbId, tag.m_type))
  {
    if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend())
      artTypes.push_back(artType);
  }
}

std::vector<std::string> GetArtTypesList(const CVideoInfoTag& tag)
{
  CVideoDatabase db;
  db.Open();

  std::vector<std::string> artTypes;

  AddHardCodedAndExtendedArtTypes(artTypes, tag);
  AddCurrentArtTypes(artTypes, tag, db);
  AddMediaTypeArtTypes(artTypes, tag, db);
  AddAvailableArtTypes(artTypes, tag, db);

  db.Close();
  return artTypes;
}
}

std::string CGUIDialogVideoInfo::ChooseArtType(const CFileItem &videoItem)
{
  // prompt for choice
  CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  if (!dialog || !videoItem.HasVideoInfoTag())
    return "";

  CFileItemList items;
  dialog->SetHeading(CVariant{13511});
  dialog->Reset();
  dialog->SetUseDetails(true);
  dialog->EnableButton(true, 13516);

  std::vector<std::string> artTypes = GetArtTypesList(*videoItem.GetVideoInfoTag());

  for (std::vector<std::string>::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i)
  {
    std::string type = *i;
    CFileItemPtr item(new CFileItem(type, false));
    if (type == "banner")
      item->SetLabel(g_localizeStrings.Get(20020));
    else if (type == "fanart")
      item->SetLabel(g_localizeStrings.Get(20445));
    else if (type == "poster")
      item->SetLabel(g_localizeStrings.Get(20021));
    else if (type == "thumb")
      item->SetLabel(g_localizeStrings.Get(21371));
    else
      item->SetLabel(type);
    item->SetProperty("type", type);
    if (videoItem.HasArt(type))
      item->SetArt("thumb", videoItem.GetArt(type));
    items.Add(item);
  }

  dialog->SetItems(items);
  dialog->Open();

  if (dialog->IsButtonPressed())
  {
    // Get the new artwork name
    std::string strArtworkName;
    if (!CGUIKeyboardFactory::ShowAndGetInput(strArtworkName, CVariant{g_localizeStrings.Get(13516)}, false))
      return "";

    return strArtworkName;
  }

  return dialog->GetSelectedFileItem()->GetProperty("type").asString();
}

void CGUIDialogVideoInfo::OnGetArt()
{
  if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection)
  {
    ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type);
    return;
  }
  std::string type = ChooseArtType(*m_movieItem);
  if (type.empty())
    return; // cancelled

  //! @todo this can be removed once these are unified.
  if (type == "fanart")
    OnGetFanart();
  else
  {
    CFileItemList items;

    // Current thumb
    if (m_movieItem->HasArt(type))
    {
      CFileItemPtr item(new CFileItem("thumb://Current", false));
      item->SetArt("thumb", m_movieItem->GetArt(type));
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13512));
      items.Add(item);
    }
    else if ((type == "poster" || type == "banner") && m_movieItem->HasArt("thumb"))
    { // add the 'thumb' type in
      CFileItemPtr item(new CFileItem("thumb://Thumb", false));
      item->SetArt("thumb", m_movieItem->GetArt("thumb"));
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13512));
      items.Add(item);
    }

    std::string embeddedArt;
    if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv"))
    {
      CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false);
      CVideoTagLoaderFFmpeg loader(item, nullptr, false);
      CVideoInfoTag tag;
      loader.Load(tag, false, nullptr);
      for (const auto& it : tag.m_coverArt)
      {
        if (it.m_type == type)
        {
          CFileItemPtr itemF(new CFileItem("thumb://Embedded", false));
          embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_" + type);
          itemF->SetArt("thumb", embeddedArt);
          itemF->SetLabel(g_localizeStrings.Get(13519));
          items.Add(itemF);
        }
      }
    }

    // Grab the thumbnails from the web
    m_movieItem->GetVideoInfoTag()->m_strPictureURL.Parse();
    std::vector<std::string> thumbs;
    int season = (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeSeason) ? m_movieItem->GetVideoInfoTag()->m_iSeason : -1;
    m_movieItem->GetVideoInfoTag()->m_strPictureURL.GetThumbUrls(thumbs, type, season);

    for (unsigned int i = 0; i < thumbs.size(); ++i)
    {
      std::string strItemPath = StringUtils::Format("thumb://Remote%i", i);
      CFileItemPtr item(new CFileItem(strItemPath, false));
      item->SetArt("thumb", thumbs[i]);
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13513));

      //! @todo Do we need to clear the cached image?
      //    CTextureCache::GetInstance().ClearCachedImage(thumb);
      items.Add(item);
    }

    std::string localThumb = CVideoThumbLoader::GetLocalArt(*m_movieItem, type);
    if (!localThumb.empty())
    {
      CFileItemPtr item(new CFileItem("thumb://Local", false));
      item->SetArt("thumb", localThumb);
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13514));
      items.Add(item);
    }
    else
    { // no local thumb exists, so we are just using the IMDb thumb or cached thumb
      // which is probably the IMDb thumb.  These could be wrong, so allow the user
      // to delete the incorrect thumb
      CFileItemPtr item(new CFileItem("thumb://None", false));
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13515));
      items.Add(item);
    }

    std::string result;
    VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video"));
    AddItemPathToFileBrowserSources(sources, *m_movieItem);
    CServiceBroker::GetMediaManager().GetLocalDrives(sources);
    if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) &&
        result != "thumb://Current") // user didn't choose the one they have
    {
      std::string newThumb;
      if (StringUtils::StartsWith(result, "thumb://Remote"))
      {
        int number = atoi(result.substr(14).c_str());
        newThumb = thumbs[number];
      }
      else if (result == "thumb://Thumb")
        newThumb = m_movieItem->GetArt("thumb");
      else if (result == "thumb://Local")
        newThumb = localThumb;
      else if (result == "thumb://Embedded")
        newThumb = embeddedArt;
      else if (CFile::Exists(result))
        newThumb = result;
      else // none
        newThumb.clear();

      // update thumb in the database
      CVideoDatabase db;
      if (db.Open())
      {
        db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, type, newThumb);
        db.Close();
      }
      CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show
      m_movieItem->SetArt(type, newThumb);
      if (m_movieItem->HasProperty("set_folder_thumb"))
      { // have a folder thumb to set as well
        VIDEO::CVideoInfoScanner::ApplyThumbToFolder(m_movieItem->GetProperty("set_folder_thumb").asString(), newThumb);
      }
      m_hasUpdatedThumb = true;
    }
  }

  // Update our screen
  Update();

  // re-open the art selection dialog as we come back from
  // the image selection dialog
  OnGetArt();
}

// Allow user to select a Fanart
void CGUIDialogVideoInfo::OnGetFanart()
{
  CFileItemList items;

  // Ensure the fanart is unpacked
  m_movieItem->GetVideoInfoTag()->m_fanart.Unpack();

  if (m_movieItem->HasArt("fanart"))
  {
    CFileItemPtr itemCurrent(new CFileItem("fanart://Current",false));
    itemCurrent->SetArt("thumb", m_movieItem->GetArt("fanart"));
    itemCurrent->SetArt("icon", "DefaultPicture.png");
    itemCurrent->SetLabel(g_localizeStrings.Get(20440));
    items.Add(itemCurrent);
  }

  std::string embeddedArt;
  if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv"))
  {
    CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false);
    CVideoTagLoaderFFmpeg loader(item, nullptr, false);
    CVideoInfoTag tag;
    loader.Load(tag, false, nullptr);
    for (const auto& it : tag.m_coverArt)
    {
      if (it.m_type == "fanart")
      {
        CFileItemPtr itemF(new CFileItem("fanart://Embedded", false));
        embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_fanart");
        itemF->SetArt("thumb", embeddedArt);
        itemF->SetLabel(g_localizeStrings.Get(13520));
        items.Add(itemF);
      }
    }
  }

  // Grab the thumbnails from the web
  for (unsigned int i = 0; i < m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts(); i++)
  {
    if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i), "image"))
      continue;
    std::string strItemPath = StringUtils::Format("fanart://Remote%i",i);
    CFileItemPtr item(new CFileItem(strItemPath, false));
    std::string thumb = m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i);
    item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(thumb));
    item->SetArt("icon", "DefaultPicture.png");
    item->SetLabel(g_localizeStrings.Get(20441));

    //! @todo Do we need to clear the cached image?
//    CTextureCache::GetInstance().ClearCachedImage(thumb);
    items.Add(item);
  }

  CFileItem item(*m_movieItem->GetVideoInfoTag());
  std::string strLocal = item.GetLocalFanart();
  if (!strLocal.empty())
  {
    CFileItemPtr itemLocal(new CFileItem("fanart://Local",false));
    itemLocal->SetArt("thumb", strLocal);
    itemLocal->SetArt("icon", "DefaultPicture.png");
    itemLocal->SetLabel(g_localizeStrings.Get(20438));

    //! @todo Do we need to clear the cached image?
    CTextureCache::GetInstance().ClearCachedImage(strLocal);
    items.Add(itemLocal);
  }
  else
  {
    CFileItemPtr itemNone(new CFileItem("fanart://None", false));
    itemNone->SetArt("icon", "DefaultPicture.png");
    itemNone->SetLabel(g_localizeStrings.Get(20439));
    items.Add(itemNone);
  }

  std::string result;
  VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video"));
  AddItemPathToFileBrowserSources(sources, item);
  CServiceBroker::GetMediaManager().GetLocalDrives(sources);
  bool flip=false;
  if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) ||
    StringUtils::EqualsNoCase(result, "fanart://Current"))
    return;   // user cancelled

  if (StringUtils::EqualsNoCase(result, "fanart://Local"))
    result = strLocal;

  if (StringUtils::EqualsNoCase(result, "fanart://Embedded"))
  {
    unsigned int current = m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts();
    int found = -1;
    for (size_t i = 0; i < current; ++i)
      if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL(), "image"))
        found = i;
    if (found != -1)
    {
      m_movieItem->GetVideoInfoTag()->m_fanart.AddFanart(embeddedArt, "", "");
      found = current;
    }

    m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(found);

    CVideoDatabase db;
    if (db.Open())
    {
      db.UpdateFanart(*m_movieItem, static_cast<VIDEODB_CONTENT_TYPE>(m_movieItem->GetVideoContentType()));
      db.Close();
    }
    result = embeddedArt;
  }

  if (StringUtils::StartsWith(result, "fanart://Remote"))
  {
    int iFanart = atoi(result.substr(15).c_str());
    // set new primary fanart, and update our database accordingly
    m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(iFanart);
    CVideoDatabase db;
    if (db.Open())
    {
      db.UpdateFanart(*m_movieItem, static_cast<VIDEODB_CONTENT_TYPE>(m_movieItem->GetVideoContentType()));
      db.Close();
    }
    result = m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL();
  }
  else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFile::Exists(result))
    result.clear();

  // set the fanart image
  if (flip && !result.empty())
    result = CTextureUtils::GetWrappedImageURL(result, "", "flipped");
  CVideoDatabase db;
  if (db.Open())
  {
    db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, "fanart", result);
    db.Close();
  }

  CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show
  m_movieItem->SetArt("fanart", result);
  m_hasUpdatedThumb = true;

  // Update our screen
  Update();
}

void CGUIDialogVideoInfo::OnSetUserrating() const
{
  CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  if (dialog)
  {
    dialog->SetHeading(CVariant{ 38023 });
    dialog->Add(g_localizeStrings.Get(38022));
    for (int i = 1; i <= 10; i++)
      dialog->Add(StringUtils::Format("%s: %i", g_localizeStrings.Get(563).c_str(), i));

    dialog->SetSelected(m_movieItem->GetVideoInfoTag()->m_iUserRating);

    dialog->Open();

    int iItem = dialog->GetSelectedItem();
    if (iItem < 0)
      return;

    SetUserrating(iItem);
  }
}

void CGUIDialogVideoInfo::PlayTrailer()
{
  CFileItem item;
  item.SetPath(m_movieItem->GetVideoInfoTag()->m_strTrailer);
  *item.GetVideoInfoTag() = *m_movieItem->GetVideoInfoTag();
  item.GetVideoInfoTag()->m_streamDetails.Reset();
  item.GetVideoInfoTag()->m_strTitle = StringUtils::Format("%s (%s)",
                                                           m_movieItem->GetVideoInfoTag()->m_strTitle.c_str(),
                                                           g_localizeStrings.Get(20410).c_str());
  item.SetArt(m_movieItem->GetArt());
  item.GetVideoInfoTag()->m_iDbId = -1;
  item.GetVideoInfoTag()->m_iFileId = -1;

  // Close the dialog.
  Close(true);

  if (item.IsPlayList())
  {
    CFileItemList *l = new CFileItemList; //don't delete,
    l->Add(std::make_shared<CFileItem>(item));
    CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_PLAY, -1, -1, static_cast<void*>(l));
  }
  else
    CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(new CFileItem(item)));
}

void CGUIDialogVideoInfo::SetLabel(int iControl, const std::string &strLabel)
{
  if (strLabel.empty())
  {
    SET_CONTROL_LABEL(iControl, 416);  // "Not available"
  }
  else
  {
    SET_CONTROL_LABEL(iControl, strLabel);
  }
}

std::string CGUIDialogVideoInfo::GetThumbnail() const
{
  return m_movieItem->GetArt("thumb");
}

namespace
{
std::string GetItemPathForBrowserSource(const CFileItem& item)
{
  if (!item.HasVideoInfoTag())
    return "";

  std::string itemDir = item.GetVideoInfoTag()->m_basePath;
  //season
  if (itemDir.empty())
    itemDir = item.GetVideoInfoTag()->GetPath();

  CFileItem itemTmp(itemDir, false);
  if (itemTmp.IsVideo())
    itemDir = URIUtils::GetParentPath(itemDir);

  return itemDir;
}

void AddItemPathStringToFileBrowserSources(VECSOURCES& sources,
    const std::string& itemDir, const std::string& label)
{
  if (!itemDir.empty() && CDirectory::Exists(itemDir))
  {
    CMediaSource itemSource;
    itemSource.strName = label;
    itemSource.strPath = itemDir;
    sources.push_back(itemSource);
  }
}
} // namespace

void CGUIDialogVideoInfo::AddItemPathToFileBrowserSources(VECSOURCES& sources,
    const CFileItem& item)
{
  std::string itemDir = GetItemPathForBrowserSource(item);
  AddItemPathStringToFileBrowserSources(sources, itemDir, g_localizeStrings.Get(36041));
}

int CGUIDialogVideoInfo::ManageVideoItem(const CFileItemPtr &item)
{
  if (item == nullptr || !item->IsVideoDb() || !item->HasVideoInfoTag() || item->GetVideoInfoTag()->m_iDbId < 0)
    return -1;

  CVideoDatabase database;
  if (!database.Open())
    return -1;

  const std::string &type = item->GetVideoInfoTag()->m_type;
  int dbId = item->GetVideoInfoTag()->m_iDbId;

  CContextButtons buttons;
  if (type == MediaTypeMovie || type == MediaTypeVideoCollection ||
      type == MediaTypeTvShow || type == MediaTypeEpisode ||
     (type == MediaTypeSeason && item->GetVideoInfoTag()->m_iSeason > 0) ||  // seasons without "all seasons" and "specials"
      type == MediaTypeMusicVideo)
    buttons.Add(CONTEXT_BUTTON_EDIT, 16105);

  if (type == MediaTypeMovie || type == MediaTypeTvShow)
    buttons.Add(CONTEXT_BUTTON_EDIT_SORTTITLE, 16107);

  if (type == MediaTypeMovie)
  {
    // only show link/unlink if there are tvshows available
    if (database.HasContent(VIDEODB_CONTENT_TVSHOWS))
    {
      buttons.Add(CONTEXT_BUTTON_LINK_MOVIE, 20384);
      if (database.IsLinkedToTvshow(dbId))
        buttons.Add(CONTEXT_BUTTON_UNLINK_MOVIE, 20385);
    }

    // set or change movie set the movie belongs to
    buttons.Add(CONTEXT_BUTTON_SET_MOVIESET, 20465);
  }

  if (type == MediaTypeEpisode &&
      item->GetVideoInfoTag()->m_iBookmarkId > 0)
    buttons.Add(CONTEXT_BUTTON_UNLINK_BOOKMARK, 20405);

  // movie sets
  if (item->m_bIsFolder && type == MediaTypeVideoCollection)
  {
    buttons.Add(CONTEXT_BUTTON_SET_MOVIESET_ART, 13511);
    buttons.Add(CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, 20465);
  }

  // seasons
  if (item->m_bIsFolder && type == MediaTypeSeason)
    buttons.Add(CONTEXT_BUTTON_SET_SEASON_ART, 13511);

  // tags
  if (item->m_bIsFolder && type == "tag")
  {
    CVideoDbUrl videoUrl;
    if (videoUrl.FromString(item->GetPath()))
    {
      const std::string &mediaType = videoUrl.GetItemType();

      buttons.Add(CONTEXT_BUTTON_TAGS_ADD_ITEMS, StringUtils::Format(g_localizeStrings.Get(20460).c_str(), GetLocalizedVideoType(mediaType).c_str()));
      buttons.Add(CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, StringUtils::Format(g_localizeStrings.Get(20461).c_str(), GetLocalizedVideoType(mediaType).c_str()));
    }
  }

  if (type != MediaTypeSeason)
    buttons.Add(CONTEXT_BUTTON_DELETE, 646);

  //temporary workaround until the context menu ids are removed
  const int addonItemOffset = 10000;

  auto addonItems = CServiceBroker::GetContextMenuManager().GetAddonItems(*item, CContextMenuManager::MANAGE);
  for (size_t i = 0; i < addonItems.size(); ++i)
    buttons.Add(addonItemOffset + i, addonItems[i]->GetLabel(*item));

  bool result = false;
  int button = CGUIDialogContextMenu::ShowAndGetChoice(buttons);
  if (button >= 0)
  {
    switch (static_cast<CONTEXT_BUTTON>(button))
    {
      case CONTEXT_BUTTON_EDIT:
        result = UpdateVideoItemTitle(item);
        break;

      case CONTEXT_BUTTON_EDIT_SORTTITLE:
        result = UpdateVideoItemSortTitle(item);
        break;

      case CONTEXT_BUTTON_LINK_MOVIE:
        result = LinkMovieToTvShow(item, false, database);
        break;

      case CONTEXT_BUTTON_UNLINK_MOVIE:
        result = LinkMovieToTvShow(item, true, database);
        break;

      case CONTEXT_BUTTON_SET_MOVIESET:
      {
        CFileItemPtr selectedSet;
        if (GetSetForMovie(item.get(), selectedSet))
          result = SetMovieSet(item.get(), selectedSet.get());
        break;
      }

      case CONTEXT_BUTTON_UNLINK_BOOKMARK:
        database.DeleteBookMarkForEpisode(*item->GetVideoInfoTag());
        result = true;
        break;

      case CONTEXT_BUTTON_DELETE:
        result = DeleteVideoItem(item);
        break;

      case CONTEXT_BUTTON_SET_MOVIESET_ART:
      case CONTEXT_BUTTON_SET_SEASON_ART:
        result = ManageVideoItemArtwork(item, type);
        break;

      case CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS:
        result = ManageMovieSets(item);
        break;

      case CONTEXT_BUTTON_TAGS_ADD_ITEMS:
        result = AddItemsToTag(item);
        break;

      case CONTEXT_BUTTON_TAGS_REMOVE_ITEMS:
        result = RemoveItemsFromTag(item);
        break;

      default:
        if (button >= addonItemOffset)
          result = CONTEXTMENU::LoopFrom(*addonItems[button - addonItemOffset], item);
        break;
    }
  }

  database.Close();

  if (result)
    return button;

  return -1;
}

//Add change a title's name
bool CGUIDialogVideoInfo::UpdateVideoItemTitle(const CFileItemPtr &pItem)
{
  if (pItem == nullptr || !pItem->HasVideoInfoTag())
    return false;

  // dont allow update while scanning
  if (g_application.IsVideoScanning())
  {
    HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057});
    return false;
  }

  CVideoDatabase database;
  if (!database.Open())
    return false;

  int iDbId = pItem->GetVideoInfoTag()->m_iDbId;
  MediaType mediaType = pItem->GetVideoInfoTag()->m_type;

  CVideoInfoTag detail;
  std::string title;
  if (mediaType == MediaTypeMovie)
  {
    database.GetMovieInfo("", detail, iDbId, VideoDbDetailsNone);
    title = detail.m_strTitle;
  }
  else if (mediaType == MediaTypeVideoCollection)
  {
    database.GetSetInfo(iDbId, detail);
    title = detail.m_strTitle;
  }
  else if (mediaType == MediaTypeEpisode)
  {
    database.GetEpisodeInfo(pItem->GetPath(), detail, iDbId, VideoDbDetailsNone);
    title = detail.m_strTitle;
  }
  else if (mediaType == MediaTypeSeason)
  {
    database.GetSeasonInfo(iDbId, detail);
    title = detail.m_strSortTitle;
  }
  else if (mediaType == MediaTypeTvShow)
  {
    database.GetTvShowInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, 0, VideoDbDetailsNone);
    title = detail.m_strTitle;
  }
  else if (mediaType == MediaTypeMusicVideo)
  {
    database.GetMusicVideoInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, VideoDbDetailsNone);
    title = detail.m_strTitle;
  }

  // get the new title
  if (!CGUIKeyboardFactory::ShowAndGetInput(title, CVariant{ g_localizeStrings.Get(16105) }, false))
    return false;

  if (mediaType == MediaTypeSeason)
  {
    detail.m_strSortTitle = title;
    std::map<std::string, std::string> artwork;
    database.SetDetailsForSeason(detail, artwork, detail.m_iIdShow, detail.m_iDbId);
  }
  else
  {
    detail.m_strTitle = title;
    VIDEODB_CONTENT_TYPE iType = static_cast<VIDEODB_CONTENT_TYPE>(pItem->GetVideoContentType());
    database.UpdateMovieTitle(iDbId, detail.m_strTitle, iType);
  }

  return true;
}

bool CGUIDialogVideoInfo::CanDeleteVideoItem(const CFileItemPtr &item)
{
  if (item == nullptr || !item->HasVideoInfoTag())
    return false;

  CQueryParams params;
  CVideoDatabaseDirectory::GetQueryParams(item->GetPath(), params);

  return params.GetMovieId()   != -1 ||
         params.GetEpisodeId() != -1 ||
         params.GetMVideoId()  != -1 ||
         params.GetSetId()     != -1 ||
         (params.GetTvShowId() != -1 && params.GetSeason() <= -1 &&
          !CVideoDatabaseDirectory::IsAllItem(item->GetPath()));
}

bool CGUIDialogVideoInfo::DeleteVideoItemFromDatabase(const CFileItemPtr &item, bool unavailable /* = false */)
{
  if (item == nullptr || !item->HasVideoInfoTag() ||
      !CanDeleteVideoItem(item))
    return false;

  // dont allow update while scanning
  if (g_application.IsVideoScanning())
  {
    HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057});
    return false;
  }

  CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
  if (pDialog == nullptr)
    return false;

  int heading = -1;
  VIDEODB_CONTENT_TYPE type = static_cast<VIDEODB_CONTENT_TYPE>(item->GetVideoContentType());
  switch (type)
  {
    case VIDEODB_CONTENT_MOVIES:
      heading = 432;
      break;
    case VIDEODB_CONTENT_EPISODES:
      heading = 20362;
      break;
    case VIDEODB_CONTENT_TVSHOWS:
      heading = 20363;
      break;
    case VIDEODB_CONTENT_MUSICVIDEOS:
      heading = 20392;
      break;
    case VIDEODB_CONTENT_MOVIE_SETS:
      heading = 646;
      break;

    default:
      return false;
  }

  pDialog->SetHeading(CVariant{heading});

  if (unavailable)
  {
    pDialog->SetLine(0, CVariant{g_localizeStrings.Get(662)});
    pDialog->SetLine(1, CVariant{g_localizeStrings.Get(663)});
  }
  else
  {
    pDialog->SetLine(0, CVariant{StringUtils::Format(g_localizeStrings.Get(433).c_str(), item->GetLabel().c_str())});
    pDialog->SetLine(1, CVariant{""});
  }
  pDialog->SetLine(2, CVariant{""});
  pDialog->Open();

  if (!pDialog->IsConfirmed())
    return false;

  CVideoDatabase database;
  database.Open();

  if (item->GetVideoInfoTag()->m_iDbId < 0)
    return false;

  switch (type)
  {
    case VIDEODB_CONTENT_MOVIES:
      database.DeleteMovie(item->GetVideoInfoTag()->m_iDbId);
      break;
    case VIDEODB_CONTENT_EPISODES:
      database.DeleteEpisode(item->GetVideoInfoTag()->m_iDbId);
      break;
    case VIDEODB_CONTENT_TVSHOWS:
      database.DeleteTvShow(item->GetVideoInfoTag()->m_iDbId);
      break;
    case VIDEODB_CONTENT_MUSICVIDEOS:
      database.DeleteMusicVideo(item->GetVideoInfoTag()->m_iDbId);
      break;
    case VIDEODB_CONTENT_MOVIE_SETS:
      database.DeleteSet(item->GetVideoInfoTag()->m_iDbId);
      break;
    default:
      return false;
  }
  return true;
}

bool CGUIDialogVideoInfo::DeleteVideoItem(const CFileItemPtr &item, bool unavailable /* = false */)
{
  if (item == nullptr)
    return false;

  // delete the video item from the database
  if (!DeleteVideoItemFromDatabase(item, unavailable))
    return false;

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

  // check if the user is allowed to delete the actual file as well
  if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) &&
      (profileManager->GetCurrentProfile().getLockMode() == LOCK_MODE_EVERYONE ||
       !profileManager->GetCurrentProfile().filesLocked() ||
       g_passwordManager.IsMasterLockUnlocked(true)))
  {
    std::string strDeletePath = item->GetVideoInfoTag()->GetPath();

    if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strDeletePath), "VIDEO_TS.IFO"))
    {
      strDeletePath = URIUtils::GetDirectory(strDeletePath);
      if (StringUtils::EndsWithNoCase(strDeletePath, "video_ts/"))
      {
        URIUtils::RemoveSlashAtEnd(strDeletePath);
        strDeletePath = URIUtils::GetDirectory(strDeletePath);
      }
    }
    if (URIUtils::HasSlashAtEnd(strDeletePath))
      item->m_bIsFolder = true;

    // check if the file/directory can be deleted
    if (CUtil::SupportsWriteFileOperations(strDeletePath))
    {
      item->SetPath(strDeletePath);

      // HACK: stacked files need to be treated as folders in order to be deleted
      if (item->IsStack())
        item->m_bIsFolder = true;

      CGUIComponent *gui = CServiceBroker::GetGUI();
      if (gui && gui->ConfirmDelete(item->GetPath()))
        CFileUtils::DeleteItem(item);
    }
  }

  CUtil::DeleteVideoDatabaseDirectoryCache();

  return true;
}

bool CGUIDialogVideoInfo::ManageMovieSets(const CFileItemPtr &item)
{
  if (item == nullptr)
    return false;

  CFileItemList originalItems;
  CFileItemList selectedItems;

  if (!GetMoviesForSet(item.get(), originalItems, selectedItems) ||
      selectedItems.Size() == 0) // need at least one item selected
    return false;

  VECFILEITEMS original = originalItems.GetList();
  std::sort(original.begin(), original.end(), compFileItemsByDbId);
  VECFILEITEMS selected = selectedItems.GetList();
  std::sort(selected.begin(), selected.end(), compFileItemsByDbId);

  bool refreshNeeded = false;
  // update the "added" items
  VECFILEITEMS addedItems;
  set_difference(selected.begin(),selected.end(), original.begin(),original.end(), std::back_inserter(addedItems), compFileItemsByDbId);
  for (VECFILEITEMS::const_iterator it = addedItems.begin();  it != addedItems.end(); ++it)
  {
    if (SetMovieSet(it->get(), item.get()))
      refreshNeeded = true;
  }

  // update the "deleted" items
  CFileItemPtr clearItem(new CFileItem());
  clearItem->GetVideoInfoTag()->m_iDbId = -1; // -1 will be used to clear set
  VECFILEITEMS deletedItems;
  set_difference(original.begin(),original.end(), selected.begin(),selected.end(), std::back_inserter(deletedItems), compFileItemsByDbId);
  for (VECFILEITEMS::iterator it = deletedItems.begin();  it != deletedItems.end(); ++it)
  {
    if (SetMovieSet(it->get(), clearItem.get()))
      refreshNeeded = true;
  }

  return refreshNeeded;
}

bool CGUIDialogVideoInfo::GetMoviesForSet(const CFileItem *setItem, CFileItemList &originalMovies, CFileItemList &selectedMovies)
{
  if (setItem == nullptr || !setItem->HasVideoInfoTag())
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return false;

  std::string baseDir = StringUtils::Format("videodb://movies/sets/%d", setItem->GetVideoInfoTag()->m_iDbId);

  if (!CDirectory::GetDirectory(baseDir, originalMovies, "", DIR_FLAG_DEFAULTS) ||
      originalMovies.Size() <= 0) // keep a copy of the original members of the set
    return false;

  CFileItemList listItems;
  if (!videodb.GetSortedVideos(MediaTypeMovie, "videodb://movies", SortDescription(), listItems) || listItems.Size() <= 0)
    return false;

  CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  if (dialog == nullptr)
    return false;

  listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);

  dialog->Reset();
  dialog->SetMultiSelection(true);
  dialog->SetHeading(CVariant{g_localizeStrings.Get(20457)});
  dialog->SetItems(listItems);
  std::vector<int> selectedIndices;
  for (int i = 0; i < originalMovies.Size(); i++)
  {
    for (int listIndex = 0; listIndex < listItems.Size(); listIndex++)
    {
      if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == originalMovies[i]->GetVideoInfoTag()->m_iDbId)
      {
        selectedIndices.push_back(listIndex);
        break;
      }
    }
  }
  dialog->SetSelected(selectedIndices);
  dialog->EnableButton(true, 186);
  dialog->Open();

  if (dialog->IsConfirmed())
  {
    for (int i : dialog->GetSelectedItems())
      selectedMovies.Add(listItems.Get(i));
    return (selectedMovies.Size() > 0);
  }
  else
    return false;
}

bool CGUIDialogVideoInfo::GetSetForMovie(const CFileItem *movieItem, CFileItemPtr &selectedSet)
{
  if (movieItem == nullptr || !movieItem->HasVideoInfoTag())
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return false;

  CFileItemList listItems;

  // " ignoreSingleMovieSets=false " as an option in the url is needed here
  // to override the gui-setting "Include sets containing a single movie"
  // and retrieve all moviesets

  std::string baseDir = "videodb://movies/sets/?ignoreSingleMovieSets=false";

  if (!CDirectory::GetDirectory(baseDir, listItems, "", DIR_FLAG_DEFAULTS))
    return false;
  listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);

  int currentSetId = 0;
  std::string currentSetLabel;

  if (movieItem->GetVideoInfoTag()->m_set.id > currentSetId)
  {
    currentSetId = movieItem->GetVideoInfoTag()->m_set.id;
    currentSetLabel = videodb.GetSetById(currentSetId);
  }

  if (currentSetId > 0)
  {
    // remove duplicate entry
    for (int listIndex = 0; listIndex < listItems.Size(); listIndex++)
    {
      if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == currentSetId)
      {
        listItems.Remove(listIndex);
        break;
      }
    }
    // add clear item
    std::string strClear = StringUtils::Format(g_localizeStrings.Get(20467).c_str(), currentSetLabel.c_str());
    CFileItemPtr clearItem(new CFileItem(strClear));
    clearItem->GetVideoInfoTag()->m_iDbId = -1; // -1 will be used to clear set
    listItems.AddFront(clearItem, 0);
    // add keep current set item
    std::string strKeep = StringUtils::Format(g_localizeStrings.Get(20469).c_str(), currentSetLabel.c_str());
    CFileItemPtr keepItem(new CFileItem(strKeep));
    keepItem->GetVideoInfoTag()->m_iDbId = currentSetId;
    listItems.AddFront(keepItem, 1);
  }

  CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  if (dialog == nullptr)
    return false;

  dialog->Reset();
  dialog->SetHeading(CVariant{g_localizeStrings.Get(20466)});
  dialog->SetItems(listItems);
  if (currentSetId >= 0)
  {
    for (int listIndex = 0; listIndex < listItems.Size(); listIndex++)
    {
      if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == currentSetId)
      {
        dialog->SetSelected(listIndex);
        break;
      }
    }
  }
  dialog->EnableButton(true, 20468); // new set via button
  dialog->Open();

  if (dialog->IsButtonPressed())
  { // creating new set
    std::string newSetTitle;
    if (!CGUIKeyboardFactory::ShowAndGetInput(newSetTitle, CVariant{g_localizeStrings.Get(20468)}, false))
      return false;
    int idSet = videodb.AddSet(newSetTitle);
    std::map<std::string, std::string> movieArt, setArt;
    if (!videodb.GetArtForItem(idSet, MediaTypeVideoCollection, setArt))
    {
      videodb.GetArtForItem(movieItem->GetVideoInfoTag()->m_iDbId, MediaTypeMovie, movieArt);
      videodb.SetArtForItem(idSet, MediaTypeVideoCollection, movieArt);
    }
    CFileItemPtr newSet(new CFileItem(newSetTitle));
    newSet->GetVideoInfoTag()->m_iDbId = idSet;
    selectedSet = newSet;
    return true;
  }
  else if (dialog->IsConfirmed())
  {
    selectedSet = dialog->GetSelectedFileItem();
    return (selectedSet != nullptr);
  }
  else
    return false;
}

bool CGUIDialogVideoInfo::SetMovieSet(const CFileItem *movieItem, const CFileItem *selectedSet)
{
  if (movieItem == nullptr || !movieItem->HasVideoInfoTag() ||
      selectedSet == nullptr || !selectedSet->HasVideoInfoTag())
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return false;

  videodb.SetMovieSet(movieItem->GetVideoInfoTag()->m_iDbId, selectedSet->GetVideoInfoTag()->m_iDbId);
  return true;
}

bool CGUIDialogVideoInfo::GetItemsForTag(const std::string &strHeading, const std::string &type, CFileItemList &items, int idTag /* = -1 */, bool showAll /* = true */)
{
  CVideoDatabase videodb;
  if (!videodb.Open())
    return false;

  MediaType mediaType = MediaTypeNone;
  std::string baseDir = "videodb://";
  std::string idColumn;
  if (type.compare(MediaTypeMovie) == 0)
  {
    mediaType = MediaTypeMovie;
    baseDir += "movies";
    idColumn = "idMovie";
  }
  else if (type.compare(MediaTypeTvShow) == 0)
  {
    mediaType = MediaTypeTvShow;
    baseDir += "tvshows";
    idColumn = "idShow";
  }
  else if (type.compare(MediaTypeMusicVideo) == 0)
  {
    mediaType = MediaTypeMusicVideo;
    baseDir += "musicvideos";
    idColumn = "idMVideo";
  }

  baseDir += "/titles/";
  CVideoDbUrl videoUrl;
  if (!videoUrl.FromString(baseDir))
    return false;

  CVideoDatabase::Filter filter;
  if (idTag > 0)
  {
    if (!showAll)
      videoUrl.AddOption("tagid", idTag);
    else
      filter.where = videodb.PrepareSQL("%s_view.%s NOT IN (SELECT tag_link.media_id FROM tag_link WHERE tag_link.tag_id = %d AND tag_link.media_type = '%s')", type.c_str(), idColumn.c_str(), idTag, type.c_str());
  }

  CFileItemList listItems;
  if (!videodb.GetSortedVideos(mediaType, videoUrl.ToString(), SortDescription(), listItems, filter) || listItems.Size() <= 0)
    return false;

  CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  if (dialog == nullptr)
    return false;

  listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);

  dialog->Reset();
  dialog->SetMultiSelection(true);
  dialog->SetHeading(CVariant{strHeading});
  dialog->SetItems(listItems);
  dialog->EnableButton(true, 186);
  dialog->Open();

  for (int i : dialog->GetSelectedItems())
    items.Add(listItems.Get(i));
  return items.Size() > 0;
}

bool CGUIDialogVideoInfo::AddItemsToTag(const CFileItemPtr &tagItem)
{
  if (tagItem == nullptr || !tagItem->HasVideoInfoTag())
    return false;

  CVideoDbUrl videoUrl;
  if (!videoUrl.FromString(tagItem->GetPath()))
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return true;

  std::string mediaType = videoUrl.GetItemType();
  mediaType = mediaType.substr(0, mediaType.length() - 1);

  CFileItemList items;
  std::string localizedType = GetLocalizedVideoType(mediaType);
  std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20464).c_str(), localizedType.c_str());
  if (!GetItemsForTag(strLabel, mediaType, items, tagItem->GetVideoInfoTag()->m_iDbId))
    return true;

  for (int index = 0; index < items.Size(); index++)
  {
    if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
      continue;

    videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, tagItem->GetVideoInfoTag()->m_iDbId, mediaType);
  }

  return true;
}

bool CGUIDialogVideoInfo::RemoveItemsFromTag(const CFileItemPtr &tagItem)
{
  if (tagItem == nullptr || !tagItem->HasVideoInfoTag())
    return false;

  CVideoDbUrl videoUrl;
  if (!videoUrl.FromString(tagItem->GetPath()))
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return true;

  std::string mediaType = videoUrl.GetItemType();
  mediaType = mediaType.substr(0, mediaType.length() - 1);

  CFileItemList items;
  std::string localizedType = GetLocalizedVideoType(mediaType);
  std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20464).c_str(), localizedType.c_str());
  if (!GetItemsForTag(strLabel, mediaType, items, tagItem->GetVideoInfoTag()->m_iDbId, false))
    return true;

  for (int index = 0; index < items.Size(); index++)
  {
    if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
      continue;

    videodb.RemoveTagFromItem(items[index]->GetVideoInfoTag()->m_iDbId, tagItem->GetVideoInfoTag()->m_iDbId, mediaType);
  }

  return true;
}

namespace
{
std::string FindLocalMovieSetArtworkFile(const CFileItemPtr& item, const std::string& artType)
{
  std::string infoFolder = VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel());
  if (infoFolder.empty())
    return "";

  CFileItemList availableArtFiles;
  CDirectory::GetDirectory(infoFolder, availableArtFiles,
      CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(),
      DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
  for (const auto& artFile : availableArtFiles)
  {
    std::string candidate = URIUtils::GetFileName(artFile->GetPath());
    URIUtils::RemoveExtension(candidate);
    if (StringUtils::EqualsNoCase(candidate, artType))
      return artFile->GetPath();
  }
  return "";
}
} // namespace

bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const CFileItemPtr &item, const MediaType &type)
{
  if (item == nullptr || !item->HasVideoInfoTag() || type.empty())
    return false;

  CVideoDatabase videodb;
  if (!videodb.Open())
    return true;

  // Grab the thumbnails from the web
  CFileItemList items;
  CFileItemPtr noneitem(new CFileItem("thumb://None", false));
  std::string currentThumb;
  int idArtist = -1;
  std::string artistPath;
  std::string artistOldPath;
  std::string artType = "thumb";
  if (type == MediaTypeArtist)
  {
    CMusicDatabase musicdb;
    if (musicdb.Open())
    {
      idArtist = musicdb.GetArtistByName(item->GetLabel()); // Fails when name not unique
      if (idArtist >= 0 )
      {
        // Get artist paths - possible locations for thumb - while music db open
        musicdb.GetOldArtistPath(idArtist, artistOldPath);  // Old artist path, local to music files
        CArtist artist;
        musicdb.GetArtist(idArtist, artist); // Need name and mbid for artist folder name
        musicdb.GetArtistPath(artist, artistPath);  // Artist path in artist info folder

        currentThumb = musicdb.GetArtForItem(idArtist, MediaTypeArtist, "thumb");
        if (currentThumb.empty())
          currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType);
      }
    }
  }
  else if (type == "actor")
    currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType);
  else
  { // SEASON, SET
    artType = ChooseArtType(*item);
    if (artType.empty())
      return false;

    if (artType == "fanart" && type != MediaTypeVideoCollection)
      return OnGetFanart(item);

    if (item->HasArt(artType))
      currentThumb = item->GetArt(artType);
    else if ((artType == "poster" || artType == "banner") && item->HasArt("thumb"))
      currentThumb = item->GetArt("thumb");
  }

  if (!currentThumb.empty())
  {
    CFileItemPtr item(new CFileItem("thumb://Current", false));
    item->SetArt("thumb", currentThumb);
    item->SetLabel(g_localizeStrings.Get(13512));
    items.Add(item);
  }
  noneitem->SetArt("icon", "DefaultFolder.png");
  noneitem->SetLabel(g_localizeStrings.Get(13515));

  bool local = false;
  std::vector<std::string> thumbs;
  if (type != MediaTypeArtist)
  {
    CVideoInfoTag tag;
    if (type == MediaTypeSeason)
    {
      videodb.GetTvShowInfo("", tag, item->GetVideoInfoTag()->m_iIdShow);
      tag.m_strPictureURL.Parse();
      tag.m_strPictureURL.GetThumbUrls(thumbs, artType, item->GetVideoInfoTag()->m_iSeason);
    }
    else if (type == MediaTypeVideoCollection)
    {
      CFileItemList items;
      std::string baseDir = StringUtils::Format("videodb://movies/sets/%d", item->GetVideoInfoTag()->m_iDbId);
      if (videodb.GetMoviesNav(baseDir, items))
      {
        for (int i=0; i < items.Size(); i++)
        {
          CVideoInfoTag* pTag = items[i]->GetVideoInfoTag();
          pTag->m_strPictureURL.Parse();
          pTag->m_strPictureURL.GetThumbUrls(thumbs, "set." + artType, -1, true);
        }
      }
    }
    else
    {
      tag = *item->GetVideoInfoTag();
      tag.m_strPictureURL.Parse();
      tag.m_strPictureURL.GetThumbUrls(thumbs, artType);
    }

    for (size_t i = 0; i < thumbs.size(); i++)
    {
      CFileItemPtr item(new CFileItem(StringUtils::Format("thumb://Remote{0}", i), false));
      item->SetArt("thumb", thumbs[i]);
      item->SetArt("icon", "DefaultPicture.png");
      item->SetLabel(g_localizeStrings.Get(13513));
      items.Add(item);

      //! @todo Do we need to clear the cached image?
      //    CTextureCache::GetInstance().ClearCachedImage(thumbs[i]);
    }

    if (type == "actor")
    {
      std::string picturePath;
      std::string strThumb = URIUtils::AddFileToFolder(picturePath, "folder.jpg");
      if (XFILE::CFile::Exists(strThumb))
      {
        CFileItemPtr pItem(new CFileItem(strThumb,false));
        pItem->SetLabel(g_localizeStrings.Get(13514));
        pItem->SetArt("thumb", strThumb);
        items.Add(pItem);
        local = true;
      }
      else
        noneitem->SetArt("icon", "DefaultActor.png");
    }

    if (type == MediaTypeVideoCollection)
    {
      std::string localFile = FindLocalMovieSetArtworkFile(item, artType);
      if (!localFile.empty())
      {
        CFileItemPtr pItem(new CFileItem(localFile, false));
        pItem->SetLabel(g_localizeStrings.Get(13514));
        pItem->SetArt("thumb", localFile);
        items.Add(pItem);
        local = true;
      }
      else
        noneitem->SetArt("icon", "DefaultVideo.png");
    }
  }
  else
  {
    std::string strThumb;
    bool existsThumb = false;
    // First look for artist thumb in the primary location
    if (!artistPath.empty())
    {
      strThumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg");
      existsThumb = CFile::Exists(strThumb);
    }
    // If not there fall back local to music files (historic location for those album artists with a unique folder)
    if (!existsThumb && !artistOldPath.empty())
    {
      strThumb = URIUtils::AddFileToFolder(artistOldPath, "folder.jpg");
      existsThumb = CFile::Exists(strThumb);
    }

    if (existsThumb)
    {
      CFileItemPtr pItem(new CFileItem(strThumb, false));
      pItem->SetLabel(g_localizeStrings.Get(13514));
      pItem->SetArt("thumb", strThumb);
      items.Add(pItem);
      local = true;
    }
    else
      noneitem->SetArt("icon", "DefaultArtist.png");
  }

  if (!local)
    items.Add(noneitem);

  std::string result;
  VECSOURCES sources=*CMediaSourceSettings::GetInstance().GetSources("video");
  CServiceBroker::GetMediaManager().GetLocalDrives(sources);
  if (type == MediaTypeVideoCollection)
  {
    AddItemPathStringToFileBrowserSources(sources,
        VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel()),
        g_localizeStrings.Get(36041));
    AddItemPathStringToFileBrowserSources(sources,
        CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
            CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER),
        "* " + g_localizeStrings.Get(20226));
  }
  else
    AddItemPathToFileBrowserSources(sources, *item);
  if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result))
    return false;   // user cancelled

  if (result == "thumb://Current")
    result = currentThumb;   // user chose the one they have

  // delete the thumbnail if that's what the user wants, else overwrite with the
  // new thumbnail
  if (result == "thumb://None")
    result.clear();
  else if (StringUtils::StartsWith(result, "thumb://Remote"))
  {
    int number = atoi(StringUtils::Mid(result, 14).c_str());
    result = thumbs[number];
  }

  // write the selected artwork to the database
  if (type == MediaTypeVideoCollection ||
      type == "actor" ||
      type == MediaTypeSeason ||
      (type == MediaTypeArtist && idArtist < 0))
    videodb.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType, result);
  else
  {
    CMusicDatabase musicdb;
    if (musicdb.Open())
      musicdb.SetArtForItem(idArtist, MediaTypeArtist, artType, result);
  }

  item->SetArt(artType, result);

  CUtil::DeleteVideoDatabaseDirectoryCache();
  CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS);
  CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);

  return true;
}

std::string CGUIDialogVideoInfo::GetLocalizedVideoType(const std::string &strType)
{
  if (CMediaTypes::IsMediaType(strType, MediaTypeMovie))
    return g_localizeStrings.Get(20342);
  else if (CMediaTypes::IsMediaType(strType, MediaTypeTvShow))
    return g_localizeStrings.Get(20343);
  else if (CMediaTypes::IsMediaType(strType, MediaTypeEpisode))
    return g_localizeStrings.Get(20359);
  else if (CMediaTypes::IsMediaType(strType, MediaTypeMusicVideo))
    return g_localizeStrings.Get(20391);

  return "";
}

bool CGUIDialogVideoInfo::UpdateVideoItemSortTitle(const CFileItemPtr &pItem)
{
  // dont allow update while scanning
  if (g_application.IsVideoScanning())
  {
    HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057});
    return false;
  }

  CVideoDatabase database;
  if (!database.Open())
    return false;

  int iDbId = pItem->GetVideoInfoTag()->m_iDbId;
  CVideoInfoTag detail;
  VIDEODB_CONTENT_TYPE iType = static_cast<VIDEODB_CONTENT_TYPE>(pItem->GetVideoContentType());
  if (iType == VIDEODB_CONTENT_MOVIES)
    database.GetMovieInfo("", detail, iDbId, VideoDbDetailsNone);
  else if (iType == VIDEODB_CONTENT_TVSHOWS)
    database.GetTvShowInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, 0, VideoDbDetailsNone);

  std::string currentTitle;
  if (detail.m_strSortTitle.empty())
    currentTitle = detail.m_strTitle;
  else
    currentTitle = detail.m_strSortTitle;

  // get the new sort title
  if (!CGUIKeyboardFactory::ShowAndGetInput(currentTitle, CVariant{g_localizeStrings.Get(16107)}, false))
    return false;

  return database.UpdateVideoSortTitle(iDbId, currentTitle, iType);
}

bool CGUIDialogVideoInfo::LinkMovieToTvShow(const CFileItemPtr &item, bool bRemove, CVideoDatabase &database)
{
  int dbId = item->GetVideoInfoTag()->m_iDbId;

  CFileItemList list;
  if (bRemove)
  {
    std::vector<int> ids;
    if (!database.GetLinksToTvShow(dbId, ids))
      return false;

    for (unsigned int i = 0; i < ids.size(); ++i)
    {
      CVideoInfoTag tag;
      database.GetTvShowInfo("", tag, ids[i], 0 , VideoDbDetailsNone);
      CFileItemPtr show(new CFileItem(tag));
      list.Add(show);
    }
  }
  else
  {
    database.GetTvShowsNav("videodb://tvshows/titles", list);

    // remove already linked shows
    std::vector<int> ids;
    if (!database.GetLinksToTvShow(dbId, ids))
      return false;

    for (int i = 0; i < list.Size(); )
    {
      size_t j;
      for (j = 0; j < ids.size(); ++j)
      {
        if (list[i]->GetVideoInfoTag()->m_iDbId == ids[j])
          break;
      }
      if (j == ids.size())
        i++;
      else
        list.Remove(i);
    }
  }

  int iSelectedLabel = 0;
  if (list.Size() > 1 || (!bRemove && !list.IsEmpty()))
  {
    list.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
    CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
    if (pDialog)
    {
      pDialog->Reset();
      pDialog->SetItems(list);
      pDialog->SetHeading(CVariant{20356});
      pDialog->Open();
      iSelectedLabel = pDialog->GetSelectedItem();
    }
  }

  if (iSelectedLabel > -1 && iSelectedLabel < list.Size())
    return database.LinkMovieToTvshow(dbId, list[iSelectedLabel]->GetVideoInfoTag()->m_iDbId, bRemove);

  return false;
}

bool CGUIDialogVideoInfo::OnGetFanart(const CFileItemPtr &videoItem)
{
  if (videoItem == nullptr || !videoItem->HasVideoInfoTag())
    return false;

  // update the db
  CVideoDatabase videodb;
  if (!videodb.Open())
    return false;

  CVideoThumbLoader loader;
  CFileItem item(*videoItem);
  loader.LoadItem(&item);

  CFileItemList items;
  if (item.HasArt("fanart"))
  {
    CFileItemPtr itemCurrent(new CFileItem("fanart://Current", false));
    itemCurrent->SetArt("thumb", item.GetArt("fanart"));
    itemCurrent->SetLabel(g_localizeStrings.Get(20440));
    items.Add(itemCurrent);
  }

  // add the none option
  {
    CFileItemPtr itemNone(new CFileItem("fanart://None", false));
    itemNone->SetArt("icon", "DefaultVideo.png");
    itemNone->SetLabel(g_localizeStrings.Get(20439));
    items.Add(itemNone);
  }

  std::string result;
  VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video"));
  CServiceBroker::GetMediaManager().GetLocalDrives(sources);
  AddItemPathToFileBrowserSources(sources, item);
  bool flip = false;
  if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) ||
      StringUtils::EqualsNoCase(result, "fanart://Current"))
    return false;

  else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFile::Exists(result))
    result.clear();
  if (!result.empty() && flip)
    result = CTextureUtils::GetWrappedImageURL(result, "", "flipped");

  videodb.SetArtForItem(item.GetVideoInfoTag()->m_iDbId, item.GetVideoInfoTag()->m_type, "fanart", result);

  // clear view cache and reload images
  CUtil::DeleteVideoDatabaseDirectoryCache();

  return true;
}

void CGUIDialogVideoInfo::ShowFor(const CFileItem& item)
{
  auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowVideoNav>(WINDOW_VIDEO_NAV);
  if (window)
  {
    ADDON::ScraperPtr info;
    window->OnItemInfo(item, info);
  }
}
