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

#include "GUIComponent.h"
#include "GUIUserMessages.h"
#include "GUIWindowManager.h"
#include "ServiceBroker.h"
#include "dialogs/GUIDialogKeyboardGeneric.h"
#include "messaging/ApplicationMessenger.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "utils/Digest.h"
#include "utils/StringUtils.h"
#include "utils/Variant.h"
#if defined(TARGET_DARWIN_EMBEDDED)
#include "dialogs/GUIDialogKeyboardTouch.h"

#include "platform/darwin/ios-common/DarwinEmbedKeyboard.h"
#endif

using namespace KODI::MESSAGING;
using KODI::UTILITY::CDigest;

CGUIKeyboard *CGUIKeyboardFactory::g_activeKeyboard = NULL;
FILTERING CGUIKeyboardFactory::m_filtering = FILTERING_NONE;

CGUIKeyboardFactory::CGUIKeyboardFactory(void) = default;

CGUIKeyboardFactory::~CGUIKeyboardFactory(void) = default;

void CGUIKeyboardFactory::keyTypedCB(CGUIKeyboard *ref, const std::string &typedString)
{
  if(ref)
  {
    // send our search message in safe way (only the active window needs it)
    CGUIMessage message(GUI_MSG_NOTIFY_ALL, ref->GetWindowId(), 0);
    switch(m_filtering)
    {
      case FILTERING_SEARCH:
        message.SetParam1(GUI_MSG_SEARCH_UPDATE);
        message.SetStringParam(typedString);
        CServiceBroker::GetAppMessenger()->SendGUIMessage(
            message, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
        break;
      case FILTERING_CURRENT:
        message.SetParam1(GUI_MSG_FILTER_ITEMS);
        message.SetStringParam(typedString);
        CServiceBroker::GetAppMessenger()->SendGUIMessage(message);
        break;
      case FILTERING_NONE:
        break;
    }
    ref->resetAutoCloseTimer();
  }
}

bool CGUIKeyboardFactory::SendTextToActiveKeyboard(const std::string &aTextString, bool closeKeyboard /* = false */)
{
  if (!g_activeKeyboard)
    return false;
  return g_activeKeyboard->SetTextToKeyboard(aTextString, closeKeyboard);
}


// Show keyboard with initial value (aTextString) and replace with result string.
// Returns: true  - successful display and input (empty result may return true or false depending on parameter)
//          false - unsuccessful display of the keyboard or cancelled editing
bool CGUIKeyboardFactory::ShowAndGetInput(std::string& aTextString,
                                          const CVariant& heading,
                                          bool allowEmptyResult,
                                          bool hiddenInput /* = false */,
                                          unsigned int autoCloseMs /* = 0 */)
{
  bool confirmed = false;
  //heading can be a string or a localization id
  std::string headingStr;
  if (heading.isString())
    headingStr = heading.asString();
  else if (heading.isInteger() && heading.asInteger())
    headingStr = CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(
        (uint32_t)heading.asInteger());

  bool useKodiKeyboard = true;
#if defined(TARGET_DARWIN_EMBEDDED)
#if defined(TARGET_DARWIN_TVOS)
  useKodiKeyboard = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
      CSettings::SETTING_INPUT_TVOSUSEKODIKEYBOARD);
#else
  useKodiKeyboard = CDarwinEmbedKeyboard::hasExternalKeyboard();
#endif // defined(TARGET_DARWIN_TVOS)
#endif

  auto& winManager = CServiceBroker::GetGUI()->GetWindowManager();
  CGUIKeyboard* kb = nullptr;
  if (useKodiKeyboard)
    kb = winManager.GetWindow<CGUIDialogKeyboardGeneric>(WINDOW_DIALOG_KEYBOARD);
#if defined(TARGET_DARWIN_EMBEDDED)
  else
    kb = winManager.GetWindow<CGUIDialogKeyboardTouch>(WINDOW_DIALOG_KEYBOARD_TOUCH);
#endif // defined(TARGET_DARWIN_EMBEDDED)

  if (kb)
  {
    g_activeKeyboard = kb;
    kb->startAutoCloseTimer(autoCloseMs);
    confirmed = kb->ShowAndGetInput(keyTypedCB, aTextString, aTextString, headingStr, hiddenInput);
    g_activeKeyboard = NULL;
  }

  if (confirmed)
  {
    if (!allowEmptyResult && aTextString.empty())
      confirmed = false;
  }

  return confirmed;
}

bool CGUIKeyboardFactory::ShowAndGetInput(std::string& aTextString, bool allowEmptyResult, unsigned int autoCloseMs /* = 0 */)
{
  return ShowAndGetInput(aTextString, CVariant{""}, allowEmptyResult, false, autoCloseMs);
}

// Shows keyboard and prompts for a password.
// Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
bool CGUIKeyboardFactory::ShowAndGetNewPassword(std::string& newPassword,
                                                const CVariant& heading,
                                                bool allowEmpty,
                                                unsigned int autoCloseMs /* = 0 */)
{
  return ShowAndGetInput(newPassword, heading, allowEmpty, true, autoCloseMs);
}

// Shows keyboard and prompts for a password.
// Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
bool CGUIKeyboardFactory::ShowAndGetNewPassword(std::string& newPassword, unsigned int autoCloseMs /* = 0 */)
{
  return ShowAndGetNewPassword(newPassword, 12340, false, autoCloseMs);
}

bool CGUIKeyboardFactory::ShowAndGetFilter(std::string &filter, bool searching, unsigned int autoCloseMs /* = 0 */)
{
  m_filtering = searching ? FILTERING_SEARCH : FILTERING_CURRENT;
  bool ret = ShowAndGetInput(filter, searching ? 16017 : 16028, true, false, autoCloseMs);
  m_filtering = FILTERING_NONE;
  return ret;
}


// \brief Show keyboard twice to get and confirm a user-entered password string.
// \param newPassword Overwritten with user input if return=true.
// \param heading Heading to display
// \param allowEmpty Whether a blank password is valid or not.
// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing.
bool CGUIKeyboardFactory::ShowAndVerifyNewPassword(std::string& newPassword,
                                                   const CVariant& heading,
                                                   bool allowEmpty,
                                                   unsigned int autoCloseMs /* = 0 */)
{
  // Prompt user for password input
  std::string userInput;
  if (!ShowAndGetInput(userInput, heading, allowEmpty, true, autoCloseMs))
  { // user cancelled, or invalid input
    return false;
  }
  // success - verify the password
  std::string checkInput;
  if (!ShowAndGetInput(checkInput, 12341, allowEmpty, true, autoCloseMs))
  { // user cancelled, or invalid input
    return false;
  }
  // check the password
  if (checkInput == userInput)
  {
    newPassword = CDigest::Calculate(CDigest::Type::MD5, userInput);
    return true;
  }
  HELPERS::ShowOKDialogText(CVariant{12341}, CVariant{12344});
  return false;
}

// \brief Show keyboard twice to get and confirm a user-entered password string.
// \param strNewPassword Overwritten with user input if return=true.
// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing.
bool CGUIKeyboardFactory::ShowAndVerifyNewPassword(std::string& newPassword, unsigned int autoCloseMs /* = 0 */)
{
  const std::string& heading =
      CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12340);
  return ShowAndVerifyNewPassword(newPassword, heading, false, autoCloseMs);
}

// \brief Show keyboard and verify user input against strPassword.
// \param strPassword Value to compare against user input.
// \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer.
// \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank.
// \return 0 if successful display and user input. 1 if unsuccessful input. -1 if no user input or canceled editing.
int CGUIKeyboardFactory::ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries, unsigned int autoCloseMs /* = 0 */)
{
  std::string strHeadingTemp;
  if (1 > iRetries && !strHeading.empty())
    strHeadingTemp = strHeading;
  else
    strHeadingTemp = StringUtils::Format(
        "{} - {} {}", CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12326),
        CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
            CSettings::SETTING_MASTERLOCK_MAXRETRIES) -
            iRetries,
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12343));

  std::string strUserInput;
  //! @todo GUI Setting to enable disable this feature y/n?
  if (!ShowAndGetInput(strUserInput, strHeadingTemp, false, true, autoCloseMs))  //bool hiddenInput = false/true ?
    return -1; // user canceled out

  if (!strPassword.empty())
  {
    std::string md5pword2 = CDigest::Calculate(CDigest::Type::MD5, strUserInput);
    if (StringUtils::EqualsNoCase(strPassword, md5pword2))
      return 0;     // user entered correct password
    else return 1;  // user must have entered an incorrect password
  }
  else
  {
    if (!strUserInput.empty())
    {
      strPassword = CDigest::Calculate(CDigest::Type::MD5, strUserInput);
      return 0; // user entered correct password
    }
    else return 1;
  }
}

