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

#include "FileItem.h"
#include "GUIPassword.h"
#include "GUIUserMessages.h"
#include "ServiceBroker.h"
#include "TextureCache.h"
#include "dialogs/GUIDialogBusy.h"
#include "dialogs/GUIDialogFileBrowser.h"
#include "dialogs/GUIDialogProgress.h"
#include "filesystem/Directory.h"
#include "filesystem/File.h"
#include "filesystem/MusicDatabaseDirectory.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "input/Key.h"
#include "messaging/helpers/DialogHelper.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "music/MusicDatabase.h"
#include "music/MusicLibraryQueue.h"
#include "music/MusicThumbLoader.h"
#include "music/MusicUtils.h"
#include "music/dialogs/GUIDialogSongInfo.h"
#include "music/infoscanner/MusicInfoScanner.h"
#include "music/tags/MusicInfoTag.h"
#include "music/windows/GUIWindowMusicNav.h"
#include "profiles/ProfileManager.h"
#include "settings/MediaSourceSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "storage/MediaManager.h"
#include "utils/FileExtensionProvider.h"
#include "utils/ProgressJob.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"

using namespace XFILE;
using namespace MUSIC_INFO;
using namespace MUSICDATABASEDIRECTORY;
using namespace KODI::MESSAGING;

#define CONTROL_BTN_REFRESH      6
#define CONTROL_USERRATING       7
#define CONTROL_BTN_GET_THUMB   10
#define CONTROL_ARTISTINFO      12

#define CONTROL_LIST            50

#define TIME_TO_BUSY_DIALOG 500

class CGetInfoJob : public CJob
{
public:
  ~CGetInfoJob(void) override = default;

  // Fetch full album/artist information including art types list
  bool DoWork() override
  {
    CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().
	  GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
    if (!dialog)
      return false;
    if (dialog->IsCancelled())
      return false;
    CFileItemPtr m_item = dialog->GetCurrentListItem();
    CMusicInfoTag& tag = *m_item->GetMusicInfoTag();

    CMusicDatabase database;
    database.Open();
    // May only have partially populated music item, so fetch all artist or album data from db
    if (tag.GetType() == MediaTypeArtist)
    {
      int artistId = tag.GetDatabaseId();
      CArtist artist;
      if (!database.GetArtist(artistId, artist))
        return false;
      tag.SetArtist(artist);
      CMusicDatabase::SetPropertiesFromArtist(*m_item, artist);
      m_item->SetLabel(artist.strArtist);

      // Get artist folder where local art could be found
      // Get the *name* of the folder for this artist within the Artist Info folder (may not exist).
      // If there is no Artist Info folder specified in settings this will be blank
      database.GetArtistPath(artist, artist.strPath);
      // Get the old location for those album artists with a unique folder (local to music files)
      // If there is no folder for the artist and *only* the artist this will be blank
      std::string oldartistpath;
      bool oldpathfound = database.GetOldArtistPath(artist.idArtist, oldartistpath);

      // Set up path for *item folder when browsing for art, by default this is
      // in the Artist Info Folder (when it exists), but could end up blank
      std::string artistItemPath = artist.strPath;
      if (!CDirectory::Exists(artistItemPath))
      {
        // Fall back local to music files (historic location for those album artists with a unique folder)
        // although there may not be such a unique folder for the arist
        if (oldpathfound)
          artistItemPath = oldartistpath;
        else
          // Fall back further to browse the Artist Info Folder itself
          artistItemPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER);
      }
      m_item->SetPath(artistItemPath);

      // Store info as CArtist as well as item properties
      dialog->SetArtist(artist, oldartistpath);

      // Fetch artist discography as scraped from online sources, but always
      // include all the albums in the music library
      dialog->SetDiscography(database);
    }
    else
    {
      // tag.GetType == MediaTypeAlbum
      int albumId = tag.GetDatabaseId();
      CAlbum album;
      if (!database.GetAlbum(albumId, album))
        return false;
      tag.SetAlbum(album);
      CMusicDatabase::SetPropertiesFromAlbum(*m_item, album);

      // Get album folder where local art could be found
      database.GetAlbumPath(albumId, album.strPath);
      // Set up path for *item folder when browsing for art
      m_item->SetPath(album.strPath);
      // Store info as CAlbum as well as item properties
      dialog->SetAlbum(album, album.strPath);

      // Set the list of songs and related art
      dialog->SetSongs(album.songs);
    }
    database.Close();

    /*
      Load current art (to CGUIListItem.m_art)
      For albums this includes related artist(s) art and artist fanart set as
      fallback album fanart.
      Clear item art first to ensure fresh not cached/partial art
    */
    m_item->ClearArt();
    CMusicThumbLoader loader;
    loader.LoadItem(m_item.get());

    // Fill vector of possible art types with current art, when it exists,
    // for display on the art type selection dialog
    CFileItemList artlist;
    MUSIC_UTILS::FillArtTypesList(*m_item, artlist);
    dialog->SetArtTypeList(artlist);
    if (dialog->IsCancelled())
      return false;

    // Tell waiting MusicDialog that job is complete
    dialog->FetchComplete();

    return true;
  }
};

class CSetUserratingJob : public CJob
{
  int idAlbum;
  int iUserrating;
public:
  CSetUserratingJob(int albumId, int userrating) :
    idAlbum(albumId),
    iUserrating(userrating)
  { }

  ~CSetUserratingJob(void) override = default;

  bool DoWork(void) override
  {
    // Asynchronously update userrating in library
    CMusicDatabase db;
    if (db.Open())
    {
      db.SetAlbumUserrating(idAlbum, iUserrating);
      db.Close();
    }

    return true;
  }
};

class CRefreshInfoJob : public CProgressJob
{
public:
  CRefreshInfoJob(CGUIDialogProgress* progressDialog)
    : CProgressJob(nullptr)
  {
    if (progressDialog)
      SetProgressIndicators(nullptr, progressDialog);
    SetAutoClose(true);
  }

  ~CRefreshInfoJob(void) override = default;

  // Refresh album/artist information including art types list
  bool DoWork() override
  {
    CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().
	  GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
    if (!dialog)
      return false;
    if (dialog->IsCancelled())
      return false;
    CFileItemPtr m_item = dialog->GetCurrentListItem();
    CMusicInfoTag& tag = *m_item->GetMusicInfoTag();
    CArtist& m_artist = dialog->GetArtist();
    CAlbum& m_album = dialog->GetAlbum();

    CGUIDialogProgress* dlgProgress = GetProgressDialog();
    CMusicDatabase database;
    database.Open();
    if (tag.GetType() == MediaTypeArtist)
    {
      ADDON::ScraperPtr scraper;
      if (!database.GetScraper(m_artist.idArtist, CONTENT_ARTISTS, scraper))
        return false;

      if (dlgProgress->IsCanceled())
        return false;
      database.ClearArtistLastScrapedTime(m_artist.idArtist);

      if (dlgProgress->IsCanceled())
        return false;
      CMusicInfoScanner scanner;
      if (scanner.UpdateArtistInfo(m_artist, scraper, true, dlgProgress) != CInfoScanner::INFO_ADDED)
        return false;
      else
        // Tell info dialog, so can show message
        dialog->SetScrapedInfo(true);

      if (dlgProgress->IsCanceled())
        return false;
      //That changed DB and m_artist, now update dialog item with new info and art
      tag.SetArtist(m_artist);
      CMusicDatabase::SetPropertiesFromArtist(*m_item, m_artist);

      // Fetch artist discography as scraped from online sources, but always
      // include all the albums in the music library
      dialog->SetDiscography(database);
    }
    else
    {
      // tag.GetType == MediaTypeAlbum
      ADDON::ScraperPtr scraper;
      if (!database.GetScraper(m_album.idAlbum, CONTENT_ALBUMS, scraper))
        return false;

      if (dlgProgress->IsCanceled())
        return false;
      database.ClearAlbumLastScrapedTime(m_album.idAlbum);

      if (dlgProgress->IsCanceled())
        return false;
      CMusicInfoScanner scanner;
      if (scanner.UpdateAlbumInfo(m_album, scraper, true, GetProgressDialog()) != CInfoScanner::INFO_ADDED)
        return false;
      else
        // Tell info dialog, so can show message
        dialog->SetScrapedInfo(true);

      if (dlgProgress->IsCanceled())
        return false;
      //That changed DB and m_album, now update dialog item with new info and art
      // Album songs are unchanged by refresh (even with Musicbrainz sync?)
      tag.SetAlbum(m_album);
      CMusicDatabase::SetPropertiesFromAlbum(*m_item, m_album);

      // Set the list of songs and related art
      dialog->SetSongs(m_album.songs);
    }
    database.Close();

    if (dlgProgress->IsCanceled())
      return false;
    /*
    Load current art (to CGUIListItem.m_art)
    For albums this includes related artist(s) art and artist fanart set as
    fallback album fanart.
    Clear item art first to ensure fresh not cached/partial art
    */
    m_item->ClearArt();
    CMusicThumbLoader loader;
    loader.LoadItem(m_item.get());

    if (dlgProgress->IsCanceled())
      return false;
    // Fill vector of possible art types with current art, when it exists,
    // for display on the art type selection dialog
    CFileItemList artlist;
    MUSIC_UTILS::FillArtTypesList(*m_item, artlist);
    dialog->SetArtTypeList(artlist);
    if (dialog->IsCancelled())
      return false;

    // Tell waiting MusicDialog that job is complete
    MarkFinished();
    return true;
  }
};

CGUIDialogMusicInfo::CGUIDialogMusicInfo(void)
    : CGUIDialog(WINDOW_DIALOG_MUSIC_INFO, "DialogMusicInfo.xml")
    , m_item(new CFileItem)
{
  m_albumSongs = new CFileItemList;
  m_loadType = KEEP_IN_MEMORY;
  m_artTypeList.Clear();
}

CGUIDialogMusicInfo::~CGUIDialogMusicInfo(void)
{
  delete m_albumSongs;
}

bool CGUIDialogMusicInfo::OnMessage(CGUIMessage& message)
{
  switch ( message.GetMessage() )
  {
  case GUI_MSG_WINDOW_DEINIT:
    {
      m_artTypeList.Clear();
      // For albums update user rating if it has changed
      if(!m_bArtistInfo && m_startUserrating != m_item->GetMusicInfoTag()->GetUserrating())
      {
        m_hasUpdatedUserrating = true;

        // Asynchronously update song userrating in library
        CSetUserratingJob *job = new CSetUserratingJob(m_item->GetMusicInfoTag()->GetAlbumId(),
                                                       m_item->GetMusicInfoTag()->GetUserrating());
        CServiceBroker::GetJobManager()->AddJob(job, nullptr);
      }
      if (m_hasRefreshed || m_hasUpdatedUserrating)
      {
        // Send a message to all windows to tell them to update the item.
        // This communicates changes to the music lib window.
        // The music lib window item is updated to but changes to the rating when it is the sort
        // do not show on screen until refresh() that fetches the list from scratch, sorts etc.
        CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item);
        CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
      }

      CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST);
      OnMessage(msg);
      m_albumSongs->Clear();
    }
    break;

  case GUI_MSG_WINDOW_INIT:
    {
      CGUIDialog::OnMessage(message);
      Update();
      m_cancelled = false;
      return true;
    }
    break;


  case GUI_MSG_CLICKED:
    {
      int iControl = message.GetSenderId();
      if (iControl == CONTROL_USERRATING)
      {
        OnSetUserrating();
      }
      else if (iControl == CONTROL_BTN_REFRESH)
      {
        RefreshInfo();
        return true;
      }
      else if (iControl == CONTROL_BTN_GET_THUMB)
      {
        OnGetArt();
        return true;
      }
      else if (iControl == CONTROL_ARTISTINFO)
      {
        if (!m_bArtistInfo)
          OnArtistInfo(m_album.artistCredits[0].GetArtistId());
        return true;
      }
      else if (iControl == CONTROL_LIST)
      {
        int iAction = message.GetParam1();
        if (m_bArtistInfo && (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction))
        {
          CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl);
          CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
          int iItem = msg.GetParam1();
          int id = -1;
          if (iItem >= 0 && iItem < m_albumSongs->Size())
              id = m_albumSongs->Get(iItem)->GetMusicInfoTag()->GetDatabaseId();
          if (id > 0)
          {
            OnAlbumInfo(id);
            return true;
          }
        }
      }
    }
    break;
  }

  return CGUIDialog::OnMessage(message);
}

bool CGUIDialogMusicInfo::OnAction(const CAction &action)
{
  int userrating = m_item->GetMusicInfoTag()->GetUserrating();
  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);
}

bool CGUIDialogMusicInfo::SetItem(CFileItem* item)
{
  *m_item = *item;
  m_event.Reset();
  m_cancelled = false;  // Happens before win_init

  // In a separate job fetch info and fill list of art types.
  int jobid =
      CServiceBroker::GetJobManager()->AddJob(new CGetInfoJob(), nullptr, CJob::PRIORITY_LOW);

  // Wait to get all data before show, allowing user to cancel if fetch is slow
  if (!CGUIDialogBusy::WaitOnEvent(m_event, TIME_TO_BUSY_DIALOG))
  {
    // Cancel job still waiting in queue (unlikely)
    CServiceBroker::GetJobManager()->CancelJob(jobid);
    // Flag to stop job already in progress
    m_cancelled = true;
    return false;
  }

  return true;
}

void CGUIDialogMusicInfo::SetAlbum(const CAlbum& album, const std::string &path)
{
  m_album = album;
  m_item->SetPath(album.strPath);

  m_startUserrating = m_album.iUserrating;
  m_fallbackartpath.clear();
  m_bArtistInfo = false;
  m_hasUpdatedUserrating = false;
  m_hasRefreshed = false;
}

void CGUIDialogMusicInfo::SetArtist(const CArtist& artist, const std::string &path)
{
  m_artist = artist;
  m_fallbackartpath = path;
  m_bArtistInfo = true;
  m_hasRefreshed = false;
}

void CGUIDialogMusicInfo::SetSongs(const VECSONGS &songs) const
{
  m_albumSongs->Clear();
  CMusicThumbLoader loader;
  for (unsigned int i = 0; i < songs.size(); i++)
  {
    const CSong& song = songs[i];
    CFileItemPtr item(new CFileItem(song));
    // Load the song art and related artist(s) (that may be different from album artist) art
    loader.LoadItem(item.get());
    m_albumSongs->Add(item);
  }
}

void CGUIDialogMusicInfo::SetDiscography(CMusicDatabase& database) const
{
  m_albumSongs->Clear();
  database.GetArtistDiscography(m_artist.idArtist, *m_albumSongs);
  CMusicThumbLoader loader;
  for (const auto& item : *m_albumSongs)
  {
    // Load all the album art and related artist(s) art (could be other collaborating artists)
    loader.LoadItem(item.get());
    if (item->GetMusicInfoTag()->GetDatabaseId() == -1)
      item->SetArt("thumb", "DefaultAlbumCover.png");
  }
}

void CGUIDialogMusicInfo::Update()
{
  if (m_bArtistInfo)
  {
    SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO);
    SET_CONTROL_HIDDEN(CONTROL_USERRATING);

    CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs);
    OnMessage(message);

  }
  else
  {
    SET_CONTROL_VISIBLE(CONTROL_ARTISTINFO);
    SET_CONTROL_VISIBLE(CONTROL_USERRATING);

    CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs);
    OnMessage(message);

  }

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

  // Disable the Choose Art button if the user isn't allowed it
  CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB,
    profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser);
}

void CGUIDialogMusicInfo::SetLabel(int iControl, const std::string& strLabel)
{
  if (strLabel.empty())
  {
    SET_CONTROL_LABEL(iControl, 416);
  }
  else
  {
    SET_CONTROL_LABEL(iControl, strLabel);
  }
}

void CGUIDialogMusicInfo::OnInitWindow()
{
  SET_CONTROL_LABEL(CONTROL_BTN_REFRESH, 184);
  SET_CONTROL_LABEL(CONTROL_USERRATING, 38023);
  SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511);
  SET_CONTROL_LABEL(CONTROL_ARTISTINFO, 21891);

  if (m_bArtistInfo)
  {
    SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO);
    SET_CONTROL_HIDDEN(CONTROL_USERRATING);
  }
  CGUIDialog::OnInitWindow();
}

void CGUIDialogMusicInfo::FetchComplete()
{
  //Trigger the event to indicate data has been fetched
  m_event.Set();
}


void CGUIDialogMusicInfo::RefreshInfo()
{
  // Double check we have permission (button should be hidden when not)
  const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
  if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
    return;

  // Check if scanning
  if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
  {
    HELPERS::ShowOKDialogText(CVariant{ 189 }, CVariant{ 14057 });
    return;
  }

  CGUIDialogProgress* dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().
    GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
  if (!dlgProgress)
    return;

  if (m_bArtistInfo)
  { // Show dialog box indicating we're searching for the artist
    dlgProgress->SetHeading(CVariant{ 21889 });
    dlgProgress->SetLine(0, CVariant{ m_artist.strArtist });
    dlgProgress->SetLine(1, CVariant{ "" });
    dlgProgress->SetLine(2, CVariant{ "" });
  }
  else
  { // Show dialog box indicating we're searching for the album
    dlgProgress->SetHeading(CVariant{ 185 });
    dlgProgress->SetLine(0, CVariant{ m_album.strAlbum });
    dlgProgress->SetLine(1, CVariant{ m_album.strArtistDesc });
    dlgProgress->SetLine(2, CVariant{ "" });
  }
  dlgProgress->Open();

  SetScrapedInfo(false);
  // Start separate job to scrape info and fill list of art types.
  CServiceBroker::GetJobManager()->AddJob(new CRefreshInfoJob(dlgProgress), nullptr,
                                          CJob::PRIORITY_HIGH);

  // Wait for refresh to complete or be canceled, but render every 10ms so that the
  // pointer movements works on dialog even when job is reporting progress infrequently
  if (dlgProgress)
    dlgProgress->Wait(10);

  if (dlgProgress->IsCanceled())
  {
    return;
  }

  // Show message when scraper was unsuccessful
  if (!HasScrapedInfo())
  {
    if (m_bArtistInfo)
      HELPERS::ShowOKDialogText(CVariant{ 21889 }, CVariant{ 20199 });
    else
      HELPERS::ShowOKDialogText(CVariant{ 185 }, CVariant{ 500 });
    return;
  }

  //  Show new values on screen
  Update();
  m_hasRefreshed = true;

  if (dlgProgress)
    dlgProgress->Close();
}

void CGUIDialogMusicInfo::SetUserrating(int userrating) const
{
  userrating = std::max(userrating, 0);
  userrating = std::min(userrating, 10);
  if (userrating != m_item->GetMusicInfoTag()->GetUserrating())
  {
    m_item->GetMusicInfoTag()->SetUserrating(userrating);
  }
}

void CGUIDialogMusicInfo::OnAlbumInfo(int id)
{
  // Switch to show album info for given album ID
  // Close current (artist) dialog to save art changes
  Close(true);
  ShowForAlbum(id);
}

void CGUIDialogMusicInfo::OnArtistInfo(int id)
{
  // Switch to show artist info for given artist ID
  // Close current (album) dialog to save art and rating changes
  Close(true);
  ShowForArtist(id);
}

CFileItemPtr CGUIDialogMusicInfo::GetCurrentListItem(int offset)
{
  return m_item;
}

std::string CGUIDialogMusicInfo::GetContent()
{
  if (m_item->GetMusicInfoTag()->GetType() == MediaTypeArtist)
    return "artists";
  else
    return "albums";
}

void CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item)
{
  std::string itemDir;
  std::string artistFolder;

  itemDir = item.GetPath();
  if (item.HasMusicInfoTag())
  {
    if (item.GetMusicInfoTag()->GetType() == MediaTypeSong)
      itemDir = URIUtils::GetParentPath(item.GetMusicInfoTag()->GetURL());

    // For artist add Artist Info Folder path to browser sources
    if (item.GetMusicInfoTag()->GetType() == MediaTypeArtist)
    {
      artistFolder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER);
      if (!artistFolder.empty() && artistFolder.compare(itemDir) == 0)
        itemDir.clear();  // skip *item when artist not have a unique path
    }
  }
  // Add "*Item folder" path to file browser sources
  if (!itemDir.empty() && CDirectory::Exists(itemDir))
  {
    CMediaSource itemSource;
    itemSource.strName = g_localizeStrings.Get(36041);
    itemSource.strPath = itemDir;
    sources.push_back(itemSource);
  }

  // For artist add Artist Info Folder path to browser sources
  if (!artistFolder.empty() && CDirectory::Exists(artistFolder))
  {
    CMediaSource itemSource;
    itemSource.strName = "* " + g_localizeStrings.Get(20223);
    itemSource.strPath = artistFolder;
    sources.push_back(itemSource);
  }
}

void CGUIDialogMusicInfo::SetArtTypeList(CFileItemList& artlist)
{
  m_artTypeList.Clear();
  m_artTypeList.Copy(artlist);
}

/*
Allow user to choose artwork for the artist or album
For each type of art the options are:
1.  Current art
2.  Local art (thumb found by filename)
3.  Remote art (scraped list of urls from online sources e.g. fanart.tv)
5.  Embedded art (@todo)
6.  None
*/
void CGUIDialogMusicInfo::OnGetArt()
{
  std::string type = MUSIC_UTILS::ShowSelectArtTypeDialog(m_artTypeList);
  if (type.empty())
    return; // Cancelled

  CFileItemList items;
  CGUIListItem::ArtMap primeArt = m_item->GetArt(); // art without fallbacks
  bool bHasArt = m_item->HasArt(type);
  bool bFallback(false);
  if (bHasArt)
  {
    // Check if that type of art is actually a fallback, e.g. artist fanart
    CGUIListItem::ArtMap::const_iterator i = primeArt.find(type);
    bFallback = (i == primeArt.end());
  }

  // Build list of possible images of that art type
  if (bHasArt)
  {
    // Add item for current artwork
    // For album it could be a fallback from artist
    CFileItemPtr item(new CFileItem("thumb://Current", false));
    item->SetArt("thumb", m_item->GetArt(type));
    item->SetArt("icon", "DefaultPicture.png");
    item->SetLabel(g_localizeStrings.Get(13512));
    items.Add(item);
  }

 // Grab the thumbnails of this art type scraped from the web
  std::vector<std::string> remotethumbs;
  // Art type is encoded into the scraper XML as optional "aspect=" field
  // Type "thumb" returns URLs for all types of art including those without aspect.
  // Those URL without aspect are also returned for all other type values.
  if (m_bArtistInfo)
    m_artist.thumbURL.GetThumbUrls(remotethumbs, type);
  else
    m_album.thumbURL.GetThumbUrls(remotethumbs, type);

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

    items.Add(item);
  }

  // Local art
  std::string localArt;
  std::vector<std::string> paths;
  if (m_bArtistInfo)
  {
    // Individual artist subfolder within the Artist Information Folder
    paths.emplace_back(m_artist.strPath);
    // Fallback local to music files (when there is a unique folder)
    paths.emplace_back(m_fallbackartpath);
  }
  else
    // Album folder, when a unique one exists, no fallback
    paths.emplace_back(m_album.strPath);
  for (const auto& path : paths)
  {
    if (!localArt.empty() && CFile::Exists(localArt))
      break;
    if (!path.empty())
    {
      CFileItem item(path, true);
      if (type == "thumb")
        // Local music thumbnail images named by <musicthumbs>
        localArt = item.GetUserMusicThumb(true);
      else
      { // Check case and ext insenitively for local images with type as name
        // e.g. <arttype>.jpg
        CFileItemList items;
        CDirectory::GetDirectory(path, items,
            CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(),
            DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);

        for (int j = 0; j < items.Size(); j++)
        {
          std::string strCandidate = URIUtils::GetFileName(items[j]->GetPath());
          URIUtils::RemoveExtension(strCandidate);
          if (StringUtils::EqualsNoCase(strCandidate, type))
          {
            localArt = items[j]->GetPath();
            break;
          }
        }
      }
    }
  }
  if (!localArt.empty() && CFile::Exists(localArt))
  {
    CFileItemPtr item(new CFileItem("Local Art: " + localArt, false));
    item->SetArt("thumb", localArt);
    item->SetLabel(g_localizeStrings.Get(13514)); // "Local art"
    items.Add(item);
  }

  // No art
  if (bHasArt && !bFallback)
  { // Actually has this type of art (not a fallback) so
    // allow the user to delete it by selecting "no art".
    CFileItemPtr item(new CFileItem("thumb://None", false));
    if (m_bArtistInfo)
      item->SetArt("icon", "DefaultArtist.png");
    else
      item->SetArt("icon", "DefaultAlbumCover.png");
    item->SetLabel(g_localizeStrings.Get(13515));
    items.Add(item);
  }

  //! @todo: Add support for extracting embedded art from song files to use for album

  // Clear local images of this type from cache so user will see any recent
  // local file changes immediately
  for (auto& item : items)
  {
    // Skip images from remote sources, recache done by refresh (could be slow)
    if (StringUtils::StartsWith(item->GetPath(), "thumb://Remote"))
      continue;
    std::string thumb(item->GetArt("thumb"));
    if (thumb.empty())
      continue;
    CURL url(CTextureUtils::UnwrapImageURL(thumb));
    // Skip images from remote sources (current thumb could be remote)
    if (url.IsProtocol("http") || url.IsProtocol("https"))
      continue;
    CServiceBroker::GetTextureCache()->ClearCachedImage(thumb);
    // Remove any thumbnail of local image too (created when browsing files)
    std::string thumbthumb(CTextureUtils::GetWrappedThumbURL(thumb));
    CServiceBroker::GetTextureCache()->ClearCachedImage(thumbthumb);
  }

  // Show list of possible art for user selection
  // Note that during selection thumbs of *all* images shown are cached. When
  // browsing folders there could be many irrelevant thumbs cached that are
  // never used by Kodi again, but there is no obvious way to clear these
  // thumbs from the cache automatically.
  std::string result;
  VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music"));
  CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_item);
  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.
    // Overwrite with the new art or clear it
    std::string newArt;
    if (StringUtils::StartsWith(result, "thumb://Remote"))
    {
      int number = atoi(result.substr(14).c_str());
      newArt = remotethumbs[number];
    }
    else if (result == "thumb://Thumb")
      newArt = m_item->GetArt("thumb");
    else if (StringUtils::StartsWith(result, "Local Art: "))
      newArt = localArt;
    else if (CFile::Exists(result))
      newArt = result;
    else // none
      newArt.clear();

    // Asynchronously update that type of art in the database and then
    // refresh artist, album and fallback art of currently playing song
    MUSIC_UTILS::UpdateArtJob(m_item, type, newArt);

    // Update local item and art list with current art
    m_item->SetArt(type, newArt);
    for (const auto& artitem : m_artTypeList)
    {
      if (artitem->GetProperty("artType") == type)
      {
        artitem->SetArt("thumb", newArt);
        break;
      }
    }

    // Get new artwork to show in other places e.g. on music lib window, but this does
    // not update artist, album or fallback art for the currently playing song as it
    // is a different item with different ID and media type
    CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item);
    CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
  }

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

void CGUIDialogMusicInfo::OnSetUserrating() const
{
  int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_item->GetMusicInfoTag()->GetUserrating());
  if (userrating < 0) // Nothing selected, so rating unchanged
    return;

  SetUserrating(userrating);
}


void CGUIDialogMusicInfo::ShowForAlbum(int idAlbum)
{
  std::string path = StringUtils::Format("musicdb://albums/{}", idAlbum);
  CFileItem item(path, true); // An album, but IsAlbum() not set as didn't use SetAlbum()
  ShowFor(&item);
}

void CGUIDialogMusicInfo::ShowForArtist(int idArtist)
{
  std::string path = StringUtils::Format("musicdb://artists/{}", idArtist);
  CFileItem item(path, true);
  ShowFor(&item);
}

void CGUIDialogMusicInfo::ShowFor(CFileItem* pItem)
{
  if (pItem->IsParentFolder() || URIUtils::IsSpecial(pItem->GetPath()) ||
    StringUtils::StartsWithNoCase(pItem->GetPath(), "musicsearch://"))
    return; // nothing to do

  if (!pItem->m_bIsFolder)
  { // Show Song information dialog
    CGUIDialogSongInfo::ShowFor(pItem);
    return;
  }

  CFileItem musicitem("musicdb://", true);

  // We have a folder album/artist info dialog only shown for db items
  // or for music video with artist/album in music library
  if (pItem->IsMusicDb())
  {
    if (!pItem->HasMusicInfoTag() || pItem->GetMusicInfoTag()->GetDatabaseId() < 1)
    {
      // Maybe only path is set, then set MusicInfoTag
      CQueryParams params;
      CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params);
      if (params.GetArtistId() > 0)
        pItem->GetMusicInfoTag()->SetDatabaseId(params.GetArtistId(), MediaTypeArtist);
      else if (params.GetAlbumId() > 0)
        pItem->GetMusicInfoTag()->SetDatabaseId(params.GetAlbumId(), MediaTypeAlbum);
      else
        return; // nothing to do
    }
    musicitem.SetFromMusicInfoTag(*pItem->GetMusicInfoTag());
  }
  else if (pItem->HasProperty("artist_musicid"))
  {
    musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("artist_musicid").asInteger32(),
                                               MediaTypeArtist);
  }
  else if (pItem->HasProperty("album_musicid"))
  {
    musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("album_musicid").asInteger32(),
                                               MediaTypeAlbum);
  }
  else
    return; // nothing to do


  CGUIDialogMusicInfo *pDlgMusicInfo = CServiceBroker::GetGUI()->GetWindowManager().
	  GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
    if (pDlgMusicInfo)
    {
      if (pDlgMusicInfo->SetItem(&musicitem))
      {
        pDlgMusicInfo->Open();
        if (pItem->GetMusicInfoTag()->GetType() == MediaTypeAlbum &&
          pDlgMusicInfo->HasUpdatedUserrating())
        {
          auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowMusicBase>(WINDOW_MUSIC_NAV);
          if (window)
            window->RefreshContent("albums");
        }
      }
    }
}
