/*
 *  Copyright (C) 2017-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "IRTranslator.h"
#include "ServiceBroker.h"
#include "filesystem/File.h"
#include "input/remote/IRRemote.h"
#include "profiles/ProfileManager.h"
#include "settings/SettingsComponent.h"
#include "utils/log.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/XBMCTinyXML.h"

#include <stdlib.h>
#include <vector>

CIRTranslator::CIRTranslator()
{
}

void CIRTranslator::Load(const std::string &irMapName)
{
  if (irMapName.empty())
    return;

  Clear();

  bool success = false;

  std::string irMapPath = URIUtils::AddFileToFolder("special://xbmc/system/", irMapName);
  if (XFILE::CFile::Exists(irMapPath))
    success |= LoadIRMap(irMapPath);
  else
    CLog::Log(LOGDEBUG, "CIRTranslator::Load - no system %s found, skipping", irMapName.c_str());

  irMapPath = CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem(irMapName);
  if (XFILE::CFile::Exists(irMapPath))
    success |= LoadIRMap(irMapPath);
  else
    CLog::Log(LOGDEBUG, "CIRTranslator::Load - no userdata %s found, skipping", irMapName.c_str());

  if (!success)
    CLog::Log(LOGERROR, "CIRTranslator::Load - unable to load remote map %s", irMapName.c_str());
}

bool CIRTranslator::LoadIRMap(const std::string &irMapPath)
{
  std::string remoteMapTag = URIUtils::GetFileName(irMapPath);
  size_t lastindex = remoteMapTag.find_last_of(".");
  if (lastindex != std::string::npos)
    remoteMapTag = remoteMapTag.substr(0, lastindex);
  StringUtils::ToLower(remoteMapTag);

  // Load our xml file, and fill up our mapping tables
  CXBMCTinyXML xmlDoc;

  // Load the config file
  CLog::Log(LOGINFO, "Loading %s", irMapPath.c_str());
  if (!xmlDoc.LoadFile(irMapPath))
  {
    CLog::Log(LOGERROR, "%s, Line %d\n%s", irMapPath.c_str(), xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
    return false;
  }

  TiXmlElement* pRoot = xmlDoc.RootElement();
  std::string strValue = pRoot->Value();
  if (strValue != remoteMapTag)
  {
    CLog::Log(LOGERROR, "%s Doesn't contain <%s>", irMapPath.c_str(), remoteMapTag.c_str());
    return false;
  }

  // Run through our window groups
  TiXmlNode* pRemote = pRoot->FirstChild();
  while (pRemote != nullptr)
  {
    if (pRemote->Type() == TiXmlNode::TINYXML_ELEMENT)
    {
      const char *szRemote = pRemote->Value();
      if (szRemote != nullptr)
      {
        TiXmlAttribute* pAttr = pRemote->ToElement()->FirstAttribute();
        if (pAttr != nullptr)
          MapRemote(pRemote, pAttr->Value());
      }
    }
    pRemote = pRemote->NextSibling();
  }

  return true;
}

void CIRTranslator::MapRemote(TiXmlNode *pRemote, const std::string &szDevice)
{
  CLog::Log(LOGINFO, "* Adding remote mapping for device '%s'", szDevice.c_str());

  std::vector<std::string> remoteNames;

  auto it = m_irRemotesMap.find(szDevice);
  if (it == m_irRemotesMap.end())
    m_irRemotesMap[szDevice].reset(new IRButtonMap);

  std::shared_ptr<IRButtonMap>& buttons = m_irRemotesMap[szDevice];

  TiXmlElement *pButton = pRemote->FirstChildElement();
  while (pButton != nullptr)
  {
    if (!pButton->NoChildren())
    {
      if (pButton->ValueStr() == "altname")
        remoteNames.push_back(pButton->FirstChild()->ValueStr());
      else
        (*buttons)[pButton->FirstChild()->ValueStr()] = pButton->ValueStr();
    }
    pButton = pButton->NextSiblingElement();
  }

  for (const auto& remoteName : remoteNames)
  {
    CLog::Log(LOGINFO, "* Linking remote mapping for '%s' to '%s'", szDevice.c_str(), remoteName.c_str());
    m_irRemotesMap[remoteName] = buttons;
  }
}

void CIRTranslator::Clear()
{
  m_irRemotesMap.clear();
}

unsigned int CIRTranslator::TranslateButton(const std::string &szDevice, const std::string &szButton)
{
  // Find the device
  auto it = m_irRemotesMap.find(szDevice);
  if (it == m_irRemotesMap.end())
    return 0;

  // Find the button
  auto it2 = (*it).second->find(szButton);
  if (it2 == (*it).second->end())
    return 0;

  // Convert the button to code
  if (strnicmp((*it2).second.c_str(), "obc", 3) == 0)
    return TranslateUniversalRemoteString((*it2).second);

  return TranslateString((*it2).second);
}

uint32_t CIRTranslator::TranslateString(std::string strButton)
{
  if (strButton.empty())
    return 0;

  uint32_t buttonCode = 0;

  StringUtils::ToLower(strButton);

  if (strButton == "left") buttonCode = XINPUT_IR_REMOTE_LEFT;
  else if (strButton == "right") buttonCode = XINPUT_IR_REMOTE_RIGHT;
  else if (strButton == "up") buttonCode = XINPUT_IR_REMOTE_UP;
  else if (strButton == "down") buttonCode = XINPUT_IR_REMOTE_DOWN;
  else if (strButton == "select") buttonCode = XINPUT_IR_REMOTE_SELECT;
  else if (strButton == "back") buttonCode = XINPUT_IR_REMOTE_BACK;
  else if (strButton == "menu") buttonCode = XINPUT_IR_REMOTE_MENU;
  else if (strButton == "info") buttonCode = XINPUT_IR_REMOTE_INFO;
  else if (strButton == "display") buttonCode = XINPUT_IR_REMOTE_DISPLAY;
  else if (strButton == "title") buttonCode = XINPUT_IR_REMOTE_TITLE;
  else if (strButton == "play") buttonCode = XINPUT_IR_REMOTE_PLAY;
  else if (strButton == "pause") buttonCode = XINPUT_IR_REMOTE_PAUSE;
  else if (strButton == "reverse") buttonCode = XINPUT_IR_REMOTE_REVERSE;
  else if (strButton == "forward") buttonCode = XINPUT_IR_REMOTE_FORWARD;
  else if (strButton == "skipplus") buttonCode = XINPUT_IR_REMOTE_SKIP_PLUS;
  else if (strButton == "skipminus") buttonCode = XINPUT_IR_REMOTE_SKIP_MINUS;
  else if (strButton == "stop") buttonCode = XINPUT_IR_REMOTE_STOP;
  else if (strButton == "zero") buttonCode = XINPUT_IR_REMOTE_0;
  else if (strButton == "one") buttonCode = XINPUT_IR_REMOTE_1;
  else if (strButton == "two") buttonCode = XINPUT_IR_REMOTE_2;
  else if (strButton == "three") buttonCode = XINPUT_IR_REMOTE_3;
  else if (strButton == "four") buttonCode = XINPUT_IR_REMOTE_4;
  else if (strButton == "five") buttonCode = XINPUT_IR_REMOTE_5;
  else if (strButton == "six") buttonCode = XINPUT_IR_REMOTE_6;
  else if (strButton == "seven") buttonCode = XINPUT_IR_REMOTE_7;
  else if (strButton == "eight") buttonCode = XINPUT_IR_REMOTE_8;
  else if (strButton == "nine") buttonCode = XINPUT_IR_REMOTE_9;
  // Additional keys from the media center extender for xbox remote
  else if (strButton == "power") buttonCode = XINPUT_IR_REMOTE_POWER;
  else if (strButton == "mytv") buttonCode = XINPUT_IR_REMOTE_MY_TV;
  else if (strButton == "mymusic") buttonCode = XINPUT_IR_REMOTE_MY_MUSIC;
  else if (strButton == "mypictures") buttonCode = XINPUT_IR_REMOTE_MY_PICTURES;
  else if (strButton == "myvideo") buttonCode = XINPUT_IR_REMOTE_MY_VIDEOS;
  else if (strButton == "record") buttonCode = XINPUT_IR_REMOTE_RECORD;
  else if (strButton == "start") buttonCode = XINPUT_IR_REMOTE_START;
  else if (strButton == "volumeplus") buttonCode = XINPUT_IR_REMOTE_VOLUME_PLUS;
  else if (strButton == "volumeminus") buttonCode = XINPUT_IR_REMOTE_VOLUME_MINUS;
  else if (strButton == "channelplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS;
  else if (strButton == "channelminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS;
  else if (strButton == "pageplus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_PLUS;
  else if (strButton == "pageminus") buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS;
  else if (strButton == "mute") buttonCode = XINPUT_IR_REMOTE_MUTE;
  else if (strButton == "recordedtv") buttonCode = XINPUT_IR_REMOTE_RECORDED_TV;
  else if (strButton == "guide") buttonCode = XINPUT_IR_REMOTE_GUIDE;
  else if (strButton == "livetv") buttonCode = XINPUT_IR_REMOTE_LIVE_TV;
  else if (strButton == "liveradio") buttonCode = XINPUT_IR_REMOTE_LIVE_RADIO;
  else if (strButton == "epgsearch") buttonCode = XINPUT_IR_REMOTE_EPG_SEARCH;
  else if (strButton == "star") buttonCode = XINPUT_IR_REMOTE_STAR;
  else if (strButton == "hash") buttonCode = XINPUT_IR_REMOTE_HASH;
  else if (strButton == "clear") buttonCode = XINPUT_IR_REMOTE_CLEAR;
  else if (strButton == "enter") buttonCode = XINPUT_IR_REMOTE_ENTER;
  else if (strButton == "xbox") buttonCode = XINPUT_IR_REMOTE_DISPLAY; // Same as display
  else if (strButton == "playlist") buttonCode = XINPUT_IR_REMOTE_PLAYLIST;
  else if (strButton == "teletext") buttonCode = XINPUT_IR_REMOTE_TELETEXT;
  else if (strButton == "red") buttonCode = XINPUT_IR_REMOTE_RED;
  else if (strButton == "green") buttonCode = XINPUT_IR_REMOTE_GREEN;
  else if (strButton == "yellow") buttonCode = XINPUT_IR_REMOTE_YELLOW;
  else if (strButton == "blue") buttonCode = XINPUT_IR_REMOTE_BLUE;
  else if (strButton == "subtitle") buttonCode = XINPUT_IR_REMOTE_SUBTITLE;
  else if (strButton == "language") buttonCode = XINPUT_IR_REMOTE_LANGUAGE;
  else if (strButton == "eject") buttonCode = XINPUT_IR_REMOTE_EJECT;
  else if (strButton == "contentsmenu") buttonCode = XINPUT_IR_REMOTE_CONTENTS_MENU;
  else if (strButton == "rootmenu") buttonCode = XINPUT_IR_REMOTE_ROOT_MENU;
  else if (strButton == "topmenu") buttonCode = XINPUT_IR_REMOTE_TOP_MENU;
  else if (strButton == "dvdmenu") buttonCode = XINPUT_IR_REMOTE_DVD_MENU;
  else if (strButton == "print") buttonCode = XINPUT_IR_REMOTE_PRINT;
  else CLog::Log(LOGERROR, "Remote Translator: Can't find button %s", strButton.c_str());
  return buttonCode;
}

uint32_t CIRTranslator::TranslateUniversalRemoteString(const std::string &szButton)
{
  if (szButton.empty() || szButton.length() < 4 || strnicmp(szButton.c_str(), "obc", 3))
    return 0;

  const char *szCode = szButton.c_str() + 3;

  // Button Code is 255 - OBC (Original Button Code) of the button
  uint32_t buttonCode = 255 - atol(szCode);
  if (buttonCode > 255)
    buttonCode = 0;

  return buttonCode;
}
