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

#include "FileItem.h"
#include "GUIDialogFileBrowser.h"
#include "GUIDialogSelect.h"
#include "ServiceBroker.h"
#include "filesystem/Directory.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIEditControl.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
#include "music/MusicDatabase.h"
#include "settings/MediaSourceSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "storage/MediaManager.h"
#include "utils/LabelFormatter.h"
#include "utils/StringUtils.h"
#include "utils/Variant.h"
#include "video/VideoDatabase.h"

#include <utility>

#define CONTROL_FIELD           15
#define CONTROL_OPERATOR        16
#define CONTROL_VALUE           17
#define CONTROL_OK              18
#define CONTROL_CANCEL          19
#define CONTROL_BROWSE          20

CGUIDialogSmartPlaylistRule::CGUIDialogSmartPlaylistRule(void)
    : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_RULE, "SmartPlaylistRule.xml")
{
  m_cancelled = false;
  m_loadType = KEEP_IN_MEMORY;
}

CGUIDialogSmartPlaylistRule::~CGUIDialogSmartPlaylistRule() = default;

bool CGUIDialogSmartPlaylistRule::OnBack(int actionID)
{
  m_cancelled = true;
  return CGUIDialog::OnBack(actionID);
}

bool CGUIDialogSmartPlaylistRule::OnMessage(CGUIMessage& message)
{
  switch ( message.GetMessage() )
  {
  case GUI_MSG_CLICKED:
    {
      int iControl = message.GetSenderId();
      if (iControl == CONTROL_OK)
        OnOK();
      else if (iControl == CONTROL_CANCEL)
        OnCancel();
      else if (iControl == CONTROL_VALUE)
      {
        std::string parameter;
        OnEditChanged(iControl, parameter);
        m_rule.SetParameter(parameter);
      }
      else if (iControl == CONTROL_OPERATOR)
        OnOperator();
      else if (iControl == CONTROL_FIELD)
        OnField();
      else if (iControl == CONTROL_BROWSE)
        OnBrowse();
      return true;
    }
    break;

  case GUI_MSG_VALIDITY_CHANGED:
    CONTROL_ENABLE_ON_CONDITION(CONTROL_OK, message.GetParam1());
    break;
  }
  return CGUIDialog::OnMessage(message);
}

void CGUIDialogSmartPlaylistRule::OnOK()
{
  m_cancelled = false;
  Close();
}

void CGUIDialogSmartPlaylistRule::OnBrowse()
{
  CFileItemList items;
  CMusicDatabase database;
  database.Open();
  CVideoDatabase videodatabase;
  videodatabase.Open();

  std::string basePath;
  if (CSmartPlaylist::IsMusicType(m_type))
    basePath = "musicdb://";
  else
    basePath = "videodb://";

  VIDEODB_CONTENT_TYPE type = VIDEODB_CONTENT_MOVIES;
  if (m_type == "movies")
    basePath += "movies/";
  else if (m_type == "tvshows")
  {
    type = VIDEODB_CONTENT_TVSHOWS;
    basePath += "tvshows/";
  }
  else if (m_type == "musicvideos")
  {
    type = VIDEODB_CONTENT_MUSICVIDEOS;
    basePath += "musicvideos/";
  }
  else if (m_type == "episodes")
  {
    if (m_rule.m_field == FieldGenre || m_rule.m_field == FieldYear ||
        m_rule.m_field == FieldStudio)
      type = VIDEODB_CONTENT_TVSHOWS;
    else
      type = VIDEODB_CONTENT_EPISODES;
    basePath += "tvshows/";
  }

  int iLabel = 0;
  if (m_rule.m_field == FieldGenre)
  {
    if (m_type == "tvshows" ||
        m_type == "episodes" ||
        m_type == "movies")
      videodatabase.GetGenresNav(basePath + "genres/", items, type);
    else if (m_type == "songs" ||
             m_type == "albums" ||
             m_type == "artists" ||
             m_type == "mixed")
      database.GetGenresNav("musicdb://genres/",items);
    if (m_type == "musicvideos" ||
        m_type == "mixed")
    {
      CFileItemList items2;
      videodatabase.GetGenresNav("videodb://musicvideos/genres/",items2,VIDEODB_CONTENT_MUSICVIDEOS);
      items.Append(items2);
    }
    iLabel = 515;
  }
  else if (m_rule.m_field == FieldSource)
  {
    if (m_type == "songs" ||
      m_type == "albums" ||
      m_type == "artists" ||
      m_type == "mixed")
    {
      database.GetSourcesNav("musicdb://sources/", items);
      iLabel = 39030;
    }
  }
  else if (m_rule.m_field == FieldRole)
  {
    if (m_type == "artists" || m_type == "mixed")
    {
      database.GetRolesNav("musicdb://songs/", items);
      iLabel = 38033;
    }
  }
  else if (m_rule.m_field == FieldCountry)
  {
    videodatabase.GetCountriesNav(basePath, items, type);
    iLabel = 574;
  }
  else if (m_rule.m_field == FieldArtist || m_rule.m_field == FieldAlbumArtist)
  {
    if (CSmartPlaylist::IsMusicType(m_type))
      database.GetArtistsNav("musicdb://artists/", items, m_rule.m_field == FieldAlbumArtist, -1);
    if (m_type == "musicvideos" ||
        m_type == "mixed")
    {
      CFileItemList items2;
      videodatabase.GetMusicVideoArtistsByName("", items2);
      items.Append(items2);
    }
    iLabel = 557;
  }
  else if (m_rule.m_field == FieldAlbum)
  {
    if (CSmartPlaylist::IsMusicType(m_type))
      database.GetAlbumsNav("musicdb://albums/", items);
    if (m_type == "musicvideos" ||
        m_type == "mixed")
    {
      CFileItemList items2;
      videodatabase.GetMusicVideoAlbumsByName("", items2);
      items.Append(items2);
    }
    iLabel = 558;
  }
  else if (m_rule.m_field == FieldActor)
  {
    videodatabase.GetActorsNav(basePath + "actors/",items,type);
    iLabel = 20337;
  }
  else if (m_rule.m_field == FieldYear)
  {
    if (CSmartPlaylist::IsMusicType(m_type))
      database.GetYearsNav("musicdb://years/", items);
    if (CSmartPlaylist::IsVideoType(m_type))
    {
      CFileItemList items2;
      videodatabase.GetYearsNav(basePath + "years/", items2, type);
      items.Append(items2);
    }
    iLabel = 562;
  }
  else if (m_rule.m_field == FieldOrigYear)
  {
    database.GetYearsNav("musicdb://originalyears/", items);
    iLabel = 38078;
  }
  else if (m_rule.m_field == FieldDirector)
  {
    videodatabase.GetDirectorsNav(basePath + "directors/", items, type);
    iLabel = 20339;
  }
  else if (m_rule.m_field == FieldStudio)
  {
    videodatabase.GetStudiosNav(basePath + "studios/", items, type);
    iLabel = 572;
  }
  else if (m_rule.m_field == FieldWriter)
  {
    videodatabase.GetWritersNav(basePath, items, type);
    iLabel = 20417;
  }
  else if (m_rule.m_field == FieldTvShowTitle ||
          (m_type == "tvshows" && m_rule.m_field == FieldTitle))
  {
    videodatabase.GetTvShowsNav(basePath + "titles/", items);
    iLabel = 20343;
  }
  else if (m_rule.m_field == FieldTitle)
  {
    if (m_type == "songs" || m_type == "mixed")
    {
      database.GetSongsNav("musicdb://songs/", items, -1, -1, -1);
      iLabel = 134;
    }
    if (m_type == "movies")
    {
      videodatabase.GetMoviesNav(basePath + "titles/", items);
      iLabel = 20342;
    }
    if (m_type == "episodes")
    {
      videodatabase.GetEpisodesNav(basePath + "titles/-1/-1/", items);
      // we need to replace the db label (<season>x<episode> <title>) with the title only
      CLabelFormatter format("%T", "");
      for (int i = 0; i < items.Size(); i++)
        format.FormatLabel(items[i].get());
      iLabel = 20360;
    }
    if (m_type == "musicvideos" || m_type == "mixed")
    {
      videodatabase.GetMusicVideosNav(basePath + "titles/", items);
      iLabel = 20389;
    }
  }
  else if (m_rule.m_field == FieldPlaylist || m_rule.m_field == FieldVirtualFolder)
  {
    // use filebrowser to grab another smart playlist

    // Note: This can cause infinite loops (playlist that refers to the same playlist) but I don't
    //       think there's any decent way to deal with this, as the infinite loop may be an arbitrary
    //       number of playlists deep, eg playlist1 -> playlist2 -> playlist3 ... -> playlistn -> playlist1
    if (CSmartPlaylist::IsVideoType(m_type))
      XFILE::CDirectory::GetDirectory("special://videoplaylists/", items, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS);
    if (CSmartPlaylist::IsMusicType(m_type))
    {
      CFileItemList items2;
      XFILE::CDirectory::GetDirectory("special://musicplaylists/", items2, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS);
      items.Append(items2);
    }

    for (int i = 0; i < items.Size(); i++)
    {
      CFileItemPtr item = items[i];
      CSmartPlaylist playlist;
      // don't list unloadable smartplaylists or any referenceable smartplaylists
      // which do not match the type of the current smartplaylist
      if (!playlist.Load(item->GetPath()) ||
         (m_rule.m_field == FieldPlaylist &&
         (!CSmartPlaylist::CheckTypeCompatibility(m_type, playlist.GetType()) ||
         (!playlist.GetGroup().empty() || playlist.IsGroupMixed()))))
      {
        items.Remove(i);
        i -= 1;
        continue;
      }

      if (!playlist.GetName().empty())
        item->SetLabel(playlist.GetName());
    }
    iLabel = 559;
  }
  else if (m_rule.m_field == FieldPath)
  {
    VECSOURCES sources;
    if (m_type == "songs" || m_type == "mixed")
      sources = *CMediaSourceSettings::GetInstance().GetSources("music");
    if (CSmartPlaylist::IsVideoType(m_type))
    {
      VECSOURCES sources2 = *CMediaSourceSettings::GetInstance().GetSources("video");
      sources.insert(sources.end(),sources2.begin(),sources2.end());
    }
    CServiceBroker::GetMediaManager().GetLocalDrives(sources);

    std::string path = m_rule.GetParameter();
    CGUIDialogFileBrowser::ShowAndGetDirectory(sources, g_localizeStrings.Get(657), path, false);
    if (!m_rule.m_parameter.empty())
      m_rule.m_parameter.clear();
    if (!path.empty())
      m_rule.m_parameter.emplace_back(std::move(path));

    UpdateButtons();
    return;
  }
  else if (m_rule.m_field == FieldSet)
  {
    videodatabase.GetSetsNav("videodb://movies/sets/", items, VIDEODB_CONTENT_MOVIES);
    iLabel = 20434;
  }
  else if (m_rule.m_field == FieldTag)
  {
    VIDEODB_CONTENT_TYPE type = VIDEODB_CONTENT_MOVIES;
    if (m_type == "tvshows" ||
        m_type == "episodes")
      type = VIDEODB_CONTENT_TVSHOWS;
    else if (m_type == "musicvideos")
      type = VIDEODB_CONTENT_MUSICVIDEOS;
    else if (m_type != "movies")
      return;

    videodatabase.GetTagsNav(basePath + "tags/", items, type);
    iLabel = 20459;
  }
  else
  { //! @todo Add browseability in here.
    assert(false);
  }

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

  CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  pDialog->Reset();
  pDialog->SetItems(items);
  std::string strHeading = StringUtils::Format(g_localizeStrings.Get(13401).c_str(), g_localizeStrings.Get(iLabel).c_str());
  pDialog->SetHeading(CVariant{std::move(strHeading)});
  pDialog->SetMultiSelection(m_rule.m_field != FieldPlaylist && m_rule.m_field != FieldVirtualFolder);

  if (!m_rule.m_parameter.empty())
    pDialog->SetSelected(m_rule.m_parameter);

  pDialog->Open();
  if (pDialog->IsConfirmed())
  {
    m_rule.m_parameter.clear();
    for (int i : pDialog->GetSelectedItems())
      m_rule.m_parameter.push_back(items.Get(i)->GetLabel());

    UpdateButtons();
  }
  pDialog->Reset();
}

std::pair<std::string, int> OperatorLabel(CDatabaseQueryRule::SEARCH_OPERATOR op)
{
  return std::make_pair(CSmartPlaylistRule::GetLocalizedOperator(op), op);
}

std::vector<std::pair<std::string, int>> CGUIDialogSmartPlaylistRule::GetValidOperators(const CSmartPlaylistRule& rule)
{
  std::vector< std::pair<std::string, int> > labels;
  switch (rule.GetFieldType(rule.m_field))
  {
  case CDatabaseQueryRule::TEXT_FIELD:
    // text fields - add the usual comparisons
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_CONTAINS));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_STARTS_WITH));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_ENDS_WITH));
    break;

  case CDatabaseQueryRule::REAL_FIELD:
  case CDatabaseQueryRule::NUMERIC_FIELD:
  case CDatabaseQueryRule::SECONDS_FIELD:
    // numerical fields - less than greater than
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_GREATER_THAN));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_LESS_THAN));
    break;

  case CDatabaseQueryRule::DATE_FIELD:
    // date field
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_AFTER));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_BEFORE));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_IN_THE_LAST));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST));
    break;

  case CDatabaseQueryRule::PLAYLIST_FIELD:
    CONTROL_ENABLE(CONTROL_BROWSE);
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL));
    break;

  case CDatabaseQueryRule::BOOLEAN_FIELD:
    CONTROL_DISABLE(CONTROL_VALUE);
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_TRUE));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_FALSE));
    break;

  case CDatabaseQueryRule::TEXTIN_FIELD:
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS));
    labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL));
    break;
  }
  return labels;
}

void CGUIDialogSmartPlaylistRule::OnCancel()
{
  m_cancelled = true;
  Close();
}

void CGUIDialogSmartPlaylistRule::OnField()
{
  const auto fields = CSmartPlaylistRule::GetFields(m_type);
  CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  dialog->Reset();
  dialog->SetHeading(CVariant{20427});
  int selected = -1;
  for (auto field = fields.begin(); field != fields.end(); field++)
  {
    dialog->Add(CSmartPlaylistRule::GetLocalizedField(*field));
    if (*field == m_rule.m_field)
      selected = std::distance(fields.begin(), field);
  }
  if (selected > -1)
    dialog->SetSelected(selected);
  dialog->Open();
  int newSelected = dialog->GetSelectedItem();
  // check if selection has changed
  if (!dialog->IsConfirmed() || newSelected < 0 || newSelected == selected)
    return;

  m_rule.m_field = fields[newSelected];
  // check if operator is still valid. if not, reset to first valid one
  std::vector< std::pair<std::string, int> > validOperators = GetValidOperators(m_rule);
  bool isValid = false;
  for (auto op : validOperators)
    if (std::get<0>(op) == std::get<0>(OperatorLabel(m_rule.m_operator)))
      isValid = true;
  if (!isValid)
    m_rule.m_operator = (CDatabaseQueryRule::SEARCH_OPERATOR)std::get<1>(validOperators[0]);

  m_rule.SetParameter("");
  UpdateButtons();
}

void CGUIDialogSmartPlaylistRule::OnOperator()
{
  const auto labels = GetValidOperators(m_rule);
  CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  dialog->Reset();
  dialog->SetHeading(CVariant{ 16023 });
  for (auto label : labels)
    dialog->Add(std::get<0>(label));
  dialog->SetSelected(CSmartPlaylistRule::GetLocalizedOperator(m_rule.m_operator));
  dialog->Open();
  int newSelected = dialog->GetSelectedItem();
  // check if selection has changed
  if (!dialog->IsConfirmed() || newSelected < 0)
    return;

  m_rule.m_operator = (CDatabaseQueryRule::SEARCH_OPERATOR)std::get<1>(labels[newSelected]);
  UpdateButtons();
}

void CGUIDialogSmartPlaylistRule::UpdateButtons()
{
  if (m_rule.m_field == 0)
    m_rule.m_field = CSmartPlaylistRule::GetFields(m_type)[0];
  SET_CONTROL_LABEL(CONTROL_FIELD, CSmartPlaylistRule::GetLocalizedField(m_rule.m_field));

  CONTROL_ENABLE(CONTROL_VALUE);
  if (CSmartPlaylistRule::IsFieldBrowseable(m_rule.m_field))
    CONTROL_ENABLE(CONTROL_BROWSE);
  else
    CONTROL_DISABLE(CONTROL_BROWSE);
  SET_CONTROL_LABEL(CONTROL_OPERATOR, std::get<0>(OperatorLabel(m_rule.m_operator)));

  // update label2 appropriately
  SET_CONTROL_LABEL2(CONTROL_VALUE, m_rule.GetParameter());
  CGUIEditControl::INPUT_TYPE type = CGUIEditControl::INPUT_TYPE_TEXT;
  CDatabaseQueryRule::FIELD_TYPE fieldType = m_rule.GetFieldType(m_rule.m_field);
  switch (fieldType)
  {
  case CDatabaseQueryRule::TEXT_FIELD:
  case CDatabaseQueryRule::PLAYLIST_FIELD:
  case CDatabaseQueryRule::TEXTIN_FIELD:
  case CDatabaseQueryRule::REAL_FIELD:
  case CDatabaseQueryRule::NUMERIC_FIELD:
    type = CGUIEditControl::INPUT_TYPE_TEXT;
    break;
  case CDatabaseQueryRule::DATE_FIELD:
    if (m_rule.m_operator == CDatabaseQueryRule::OPERATOR_IN_THE_LAST ||
        m_rule.m_operator == CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST)
      type = CGUIEditControl::INPUT_TYPE_TEXT;
    else
      type = CGUIEditControl::INPUT_TYPE_DATE;
    break;
  case CDatabaseQueryRule::SECONDS_FIELD:
    type = CGUIEditControl::INPUT_TYPE_SECONDS;
    break;
  case CDatabaseQueryRule::BOOLEAN_FIELD:
    type = CGUIEditControl::INPUT_TYPE_NUMBER;
    break;
  }
  SendMessage(GUI_MSG_SET_TYPE, CONTROL_VALUE, type, 21420);
}

void CGUIDialogSmartPlaylistRule::OnInitWindow()
{
  CGUIDialog::OnInitWindow();

  UpdateButtons();

  CGUIEditControl *editControl = dynamic_cast<CGUIEditControl*>(GetControl(CONTROL_VALUE));
  if (editControl != NULL)
    editControl->SetInputValidation(CSmartPlaylistRule::Validate, &m_rule);
}

void CGUIDialogSmartPlaylistRule::OnDeinitWindow(int nextWindowID)
{
  CGUIDialog::OnDeinitWindow(nextWindowID);

  // reset field spincontrolex
  SendMessage(GUI_MSG_LABEL_RESET, CONTROL_FIELD);
  // reset operator spincontrolex
  SendMessage(GUI_MSG_LABEL_RESET, CONTROL_OPERATOR);
}

bool CGUIDialogSmartPlaylistRule::EditRule(CSmartPlaylistRule &rule, const std::string& type)
{
  CGUIDialogSmartPlaylistRule *editor = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSmartPlaylistRule>(WINDOW_DIALOG_SMART_PLAYLIST_RULE);
  if (!editor) return false;

  editor->m_rule = rule;
  editor->m_type = type;
  editor->Open();
  rule = editor->m_rule;
  return !editor->m_cancelled;
}

