text: /*
   *      Copyright (C) 2005-2013 Team XBMC
   *      http://xbmc.org
   *
   *  This Program is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation; either version 2, or (at your option)
   *  any later version.
   *
   *  This Program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with XBMC; see the file COPYING.  If not, see
   *  <http://www.gnu.org/licenses/>.
   *
   */
  
  #include "threads/SystemClock.h"
  #include "system.h"
  
  #ifdef HAS_EVENT_SERVER
  
  #include "EventClient.h"
  #include "EventPacket.h"
  #include "threads/SingleLock.h"
  #include "input/ButtonTranslator.h"
  #include <map>
  #include <queue>
  #include "filesystem/File.h"
  #include "utils/log.h"
  #include "utils/TimeUtils.h"
  #include "dialogs/GUIDialogKaiToast.h"
  #include "guilib/GraphicContext.h"
  #include "input/Key.h"
  #include "guilib/LocalizeStrings.h"
  #include "utils/StringUtils.h"
  
  using namespace EVENTCLIENT;
  using namespace EVENTPACKET;
  
  struct ButtonStateFinder
  {
    ButtonStateFinder(const CEventButtonState& state)
      : m_keycode(state.m_iKeyCode)
      , m_map(state.m_mapName)
      , m_button(state.m_buttonName)
    {}
  
    bool operator()(const CEventButtonState& state)
    {
      return state.m_mapName    == m_map
          && state.m_iKeyCode   == m_keycode
          && state.m_buttonName == m_button;
    }
    private:
    unsigned short m_keycode;
    std::string    m_map;
    std::string    m_button;
  };
  
  /************************************************************************/
  /* CEventButtonState                                                    */
  /************************************************************************/
  void CEventButtonState::Load()
  {
    if ( m_iKeyCode == 0 )
    {
      if ( (m_mapName.length() > 0) && (m_buttonName.length() > 0) )
      {
        if ( m_mapName.compare("KB") == 0 ) // standard keyboard map
        {
          m_iKeyCode = CButtonTranslator::TranslateKeyboardString( m_buttonName.c_str() );
        }
        else if  ( m_mapName.compare("XG") == 0 ) // xbox gamepad map
        {
          m_iKeyCode = CButtonTranslator::TranslateGamepadString( m_buttonName.c_str() );
        }
        else if  ( m_mapName.compare("R1") == 0 ) // xbox remote map
        {
          m_iKeyCode = CButtonTranslator::TranslateRemoteString( m_buttonName.c_str() );
        }
        else if  ( m_mapName.compare("R2") == 0 ) // xbox unviversal remote map
        {
          m_iKeyCode = CButtonTranslator::TranslateUniversalRemoteString( m_buttonName.c_str() );
        }
        else if ( (m_mapName.length() > 3) &&
                  (StringUtils::StartsWith(m_mapName, "LI:")) ) // starts with LI: ?
        {
  #if defined(HAS_LIRC) || defined(HAS_IRSERVERSUITE)
          std::string lircDevice = m_mapName.substr(3);
          m_iKeyCode = CButtonTranslator::GetInstance().TranslateLircRemoteString( lircDevice.c_str(),
                                                                     m_buttonName.c_str() );
  #else
          CLog::Log(LOGERROR, "ES: LIRC support not enabled");
  #endif
        }
        else
        {
          Reset(); // disable key since its invalid
          CLog::Log(LOGERROR, "ES: Could not map %s : %s to a key", m_mapName.c_str(),
                    m_buttonName.c_str());
        }
      }
    }
    else
    {
      if (m_mapName.length() > 3 &&
          (StringUtils::StartsWith(m_mapName, "JS")) )
      {
        m_joystickName = m_mapName.substr(2);  // <num>:joyname
        m_iControllerNumber = (unsigned char)(*(m_joystickName.c_str()))
          - (unsigned char)'0'; // convert <num> to int
        m_joystickName = m_joystickName.substr(2); // extract joyname
      }
    }
  }
  
  /************************************************************************/
  /* CEventClient                                                         */
  /************************************************************************/
  bool CEventClient::AddPacket(CEventPacket *packet)
  {
    if (!packet)
      return false;
  
    ResetTimeout();
    if ( packet->Size() > 1 )
    {
      // TODO: limit payload size
      if (m_seqPackets[ packet->Sequence() ])
      {
        if(!m_bSequenceError)
          CLog::Log(LOGWARNING, "CEventClient::AddPacket - received packet with same sequence number (%d) as previous packet from eventclient %s", packet->Sequence(), m_deviceName.c_str());
        m_bSequenceError = true;
        delete m_seqPackets[ packet->Sequence() ];
      }
  
      m_seqPackets[ packet->Sequence() ] = packet;
      if (m_seqPackets.size() == packet->Size())
      {
        unsigned int iSeqPayloadSize = 0;
        for (unsigned int i = 1 ; i<=packet->Size() ; i++)
        {
          iSeqPayloadSize += m_seqPackets[i]->PayloadSize();
        }
        unsigned int offset = 0;
        void *newPayload = NULL;
        newPayload = malloc(iSeqPayloadSize);
        if (newPayload)
        {
          unsigned char *payloadPtr = (unsigned char *)newPayload;
          unsigned int packets = packet->Size(); // packet can be deleted in this loop
          for (unsigned int i = 1 ; i<=packets ; i++)
          {
            memcpy((void*)(payloadPtr + offset), m_seqPackets[i]->Payload(),
                   m_seqPackets[i]->PayloadSize());
            offset += m_seqPackets[i]->PayloadSize();
            if (i>1)
            {
              delete m_seqPackets[i];
              m_seqPackets[i] = NULL;
            }
          }
          m_seqPackets[1]->SetPayload(iSeqPayloadSize, newPayload);
          m_readyPackets.push(m_seqPackets[1]);
          m_seqPackets.clear();
        }
        else
        {
          CLog::Log(LOGERROR, "ES: Could not assemble packets, Out of Memory");
          FreePacketQueues();
          return false;
        }
      }
    }
    else
    {
      m_readyPackets.push(packet);
    }
    return true;
  }
  
  void CEventClient::ProcessEvents()
  {
    if (!m_readyPackets.empty())
    {
      while ( ! m_readyPackets.empty() )
      {
        ProcessPacket( m_readyPackets.front() );
        if ( ! m_readyPackets.empty() ) // in case the BYE packet cleared the queues
        {
          delete m_readyPackets.front();
          m_readyPackets.pop();
        }
      }
    }
  }
  
  bool CEventClient::GetNextAction(CEventAction &action)
  {
    CSingleLock lock(m_critSection);
    if (!m_actionQueue.empty())
    {
      // grab the next action in line
      action = m_actionQueue.front();
      m_actionQueue.pop();
      return true;
    }
    else
    {
      // we got nothing
      return false;
    }
  }
  
  bool CEventClient::ProcessPacket(CEventPacket *packet)
  {
    if (!packet)
      return false;
  
    bool valid = false;
  
    switch (packet->Type())
    {
    case PT_HELO:
      valid = OnPacketHELO(packet);
      break;
  
    case PT_BYE:
      valid = OnPacketBYE(packet);
      break;
  
    case PT_BUTTON:
      valid = OnPacketBUTTON(packet);
      break;
  
    case PT_MOUSE:
      valid = OnPacketMOUSE(packet);
      break;
  
    case PT_NOTIFICATION:
      valid = OnPacketNOTIFICATION(packet);
      break;
  
    case PT_PING:
      valid = true;
      break;
  
    case PT_LOG:
      valid = OnPacketLOG(packet);
      break;
  
    case PT_ACTION:
      valid = OnPacketACTION(packet);
      break;
  
    default:
      CLog::Log(LOGDEBUG, "ES: Got Unknown Packet");
      break;
    }
  
    if (valid)
      ResetTimeout();
  
    return valid;
  }
  
  bool CEventClient::OnPacketHELO(CEventPacket *packet)
  {
    // TODO: check it last HELO packet was received less than 5 minutes back
    //       if so, do not show notification of connection.
    if (Greeted())
      return false;
  
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
  
    // parse device name
    if (!ParseString(payload, psize, m_deviceName))
      return false;
  
    CLog::Log(LOGNOTICE, "ES: Incoming connection from %s", m_deviceName.c_str());
  
    // icon type
    unsigned char ltype;
    if (!ParseByte(payload, psize, ltype))
      return false;
    m_eLogoType = (LogoType)ltype;
  
    // client's port (if any)
    unsigned short dport;
    if (!ParseUInt16(payload, psize, dport))
      return false;
    m_iRemotePort = (unsigned int)dport;
  
    // 2 x reserved uint32 (8 bytes)
    unsigned int reserved;
    ParseUInt32(payload, psize, reserved);
    ParseUInt32(payload, psize, reserved);
  
    // image data if any
    std::string iconfile = "special://temp/helo";
    if (m_eLogoType != LT_NONE && psize>0)
    {
      switch (m_eLogoType)
      {
      case LT_JPEG:
        iconfile += ".jpg";
        break;
  
      case LT_GIF:
        iconfile += ".gif";
        break;
  
      default:
        iconfile += ".png";
        break;
      }
      XFILE::CFile file;
      if (!file.OpenForWrite(iconfile, true) || file.Write((const void *)payload, psize) != psize)
      {
        CLog::Log(LOGERROR, "ES: Could not write icon file");
        m_eLogoType = LT_NONE;
      }
    }
  
    m_bGreeted = true;
    if (m_eLogoType == LT_NONE)
    {
      CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(33200),
                                            m_deviceName.c_str());
    }
    else
    {
      CGUIDialogKaiToast::QueueNotification(iconfile.c_str(),
                                            g_localizeStrings.Get(33200),
                                            m_deviceName.c_str());
    }
    return true;
  }
  
  bool CEventClient::OnPacketBYE(CEventPacket *packet)
  {
    if (!Greeted())
      return false;
  
    m_bGreeted = false;
    FreePacketQueues();
    m_currentButton.Reset();
  
    return true;
  }
  
  bool CEventClient::OnPacketBUTTON(CEventPacket *packet)
  {
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
  
    std::string map, button;
    unsigned short flags;
    unsigned short bcode;
    unsigned short amount;
  
    // parse the button code
    if (!ParseUInt16(payload, psize, bcode))
      return false;
  
    // parse flags
    if (!ParseUInt16(payload, psize, flags))
      return false;
  
    // parse amount
    if (!ParseUInt16(payload, psize, amount))
      return false;
  
    // parse the map to use
    if (!ParseString(payload, psize, map))
      return false;
  
    // parse button name
    if (flags & PTB_USE_NAME)
    {
      if (!ParseString(payload, psize, button))
        return false;
    }
  
    unsigned int keycode;
    if(flags & PTB_USE_NAME)
      keycode = 0;
    else if(flags & PTB_VKEY)
      keycode = bcode|KEY_VKEY;
    else if(flags & PTB_UNICODE)
      keycode = bcode|ES_FLAG_UNICODE;
    else
      keycode = bcode;
  
    float famount = 0;
    bool active = (flags & PTB_DOWN) ? true : false;
  
    if(flags & PTB_USE_AMOUNT)
    {
      if(flags & PTB_AXIS)
        famount = (float)amount/65535.0f*2.0f-1.0f;
      else
        famount = (float)amount/65535.0f;
    }
    else
      famount = (active ? 1.0f : 0.0f);
  
    if(flags & PTB_QUEUE)
    {
      /* find the last queued item of this type */
      CSingleLock lock(m_critSection);
  
      CEventButtonState state( keycode,
                               map,
                               button,
                               famount,
                               (flags & (PTB_AXIS|PTB_AXISSINGLE)) ? true  : false,
                               (flags & PTB_NO_REPEAT)             ? false : true,
                               (flags & PTB_USE_AMOUNT)            ? true : false );
  
      /* correct non active events so they work with rest of code */
      if(!active)
      {
        state.m_bActive = false;
        state.m_bRepeat = false;
        state.m_fAmount = 0.0;
      }
  
      std::list<CEventButtonState>::reverse_iterator it;
      it = find_if( m_buttonQueue.rbegin() , m_buttonQueue.rend(), ButtonStateFinder(state));
  
      if(it == m_buttonQueue.rend())
      {
        if(active)
          m_buttonQueue.push_back(state);
      }
      else
      {
        if(!active && it->m_bActive)
        {
          /* since modifying the list invalidates the referse iteratator */
          std::list<CEventButtonState>::iterator it2 = (++it).base();
  
          /* if last event had an amount, we must resend without amount */
          if(it2->m_bUseAmount && it2->m_fAmount != 0.0)
            m_buttonQueue.push_back(state);
  
          /* if the last event was waiting for a repeat interval, it has executed already.*/
          if(it2->m_bRepeat)
          {
            if(it2->m_iNextRepeat > 0)
              m_buttonQueue.erase(it2);
            else
              it2->m_bRepeat = false;
          }
  
        }
        else if(active && !it->m_bActive)
        {
          m_buttonQueue.push_back(state);
          if(!state.m_bRepeat && state.m_bAxis && state.m_fAmount != 0.0)
          {
            state.m_bActive = false;
            state.m_bRepeat = false;
            state.m_fAmount = 0.0;
            m_buttonQueue.push_back(state);
          }
        }
        else
          it->m_fAmount = state.m_fAmount;
      }
    }
    else
    {
      CSingleLock lock(m_critSection);
      if ( flags & PTB_DOWN )
      {
        m_currentButton.m_iKeyCode   = keycode;
        m_currentButton.m_mapName    = map;
        m_currentButton.m_buttonName = button;
        m_currentButton.m_fAmount    = famount;
        m_currentButton.m_bRepeat    = (flags & PTB_NO_REPEAT)  ? false : true;
        m_currentButton.m_bAxis      = (flags & PTB_AXIS)       ? true : false;
        m_currentButton.m_iNextRepeat = 0;
        m_currentButton.SetActive();
        m_currentButton.Load();
      }
      else
      {
        /* when a button is released that had amount, make sure *
         * to resend the keypress with an amount of 0           */
        if((flags & PTB_USE_AMOUNT) && m_currentButton.m_fAmount > 0.0)
        {
          CEventButtonState state( m_currentButton.m_iKeyCode,
                                   m_currentButton.m_mapName,
                                   m_currentButton.m_buttonName,
                                   0.0,
                                   m_currentButton.m_bAxis,
                                   false,
                                   true );
  
          m_buttonQueue.push_back (state);
        }
        m_currentButton.Reset();
      }
    }
  
    return true;
  }
  
  bool CEventClient::OnPacketMOUSE(CEventPacket *packet)
  {
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
    unsigned char flags;
    unsigned short mx, my;
  
    // parse flags
    if (!ParseByte(payload, psize, flags))
      return false;
  
    // parse x position
    if (!ParseUInt16(payload, psize, mx))
      return false;
  
    // parse x position
    if (!ParseUInt16(payload, psize, my))
      return false;
  
    {
      CSingleLock lock(m_critSection);
      if ( flags & PTM_ABSOLUTE )
      {
        m_iMouseX = mx;
        m_iMouseY = my;
        m_bMouseMoved = true;
      }
    }
  
    return true;
  }
  
  bool CEventClient::OnPacketNOTIFICATION(CEventPacket *packet)
  {
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
    std::string title, message;
  
    // parse caption
    if (!ParseString(payload, psize, title))
      return false;
  
    // parse message
    if (!ParseString(payload, psize, message))
      return false;
  
    // icon type
    unsigned char ltype;
    if (!ParseByte(payload, psize, ltype))
      return false;
    m_eLogoType = (LogoType)ltype;
  
    // reserved uint32
    unsigned int reserved;
    ParseUInt32(payload, psize, reserved);
  
    // image data if any
    std::string iconfile = "special://temp/notification";
    if (m_eLogoType != LT_NONE && psize>0)
    {
      switch (m_eLogoType)
      {
      case LT_JPEG:
        iconfile += ".jpg";
        break;
  
      case LT_GIF:
        iconfile += ".gif";
        break;
  
      default:
        iconfile += ".png";
        break;
      }
  
      XFILE::CFile file;
      if (!file.OpenForWrite(iconfile, true) || file.Write((const void *)payload, psize) != psize)
      {
        CLog::Log(LOGERROR, "ES: Could not write icon file");
        m_eLogoType = LT_NONE;
      }
    }
  
    if (m_eLogoType == LT_NONE)
    {
      CGUIDialogKaiToast::QueueNotification(title.c_str(),
                                            message.c_str());
    }
    else
    {
      CGUIDialogKaiToast::QueueNotification(iconfile.c_str(),
                                            title.c_str(),
                                            message.c_str());
    }
    return true;
  }
  
  bool CEventClient::OnPacketLOG(CEventPacket *packet)
  {
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
    std::string logmsg;
    unsigned char ltype;
  
    if (!ParseByte(payload, psize, ltype))
      return false;
    if (!ParseString(payload, psize, logmsg))
      return false;
  
    CLog::Log((int)ltype, "%s", logmsg.c_str());
    return true;
  }
  
  bool CEventClient::OnPacketACTION(CEventPacket *packet)
  {
    unsigned char *payload = (unsigned char *)packet->Payload();
    int psize = (int)packet->PayloadSize();
    std::string actionString;
    unsigned char actionType;
  
    if (!ParseByte(payload, psize, actionType))
      return false;
    if (!ParseString(payload, psize, actionString))
      return false;
  
    switch(actionType)
    {
    case AT_EXEC_BUILTIN:
    case AT_BUTTON:
      {
        CSingleLock lock(m_critSection);
        m_actionQueue.push(CEventAction(actionString.c_str(), actionType));
      }
      break;
  
    default:
      CLog::Log(LOGDEBUG, "ES: Failed - ActionType: %i ActionString: %s", actionType, actionString.c_str());
      return false;
      break;
    }
    return true;
  }
  
  bool CEventClient::ParseString(unsigned char* &payload, int &psize, std::string& parsedVal)
  {
    if (psize <= 0)
      return false;
  
    unsigned char *pos = (unsigned char *)memchr((void*)payload, (int)'\0', psize);
    if (!pos)
      return false;
  
    parsedVal = (char*)payload;
    psize -= ((pos - payload) + 1);
    payload = pos+1;
    return true;
  }
  
  bool CEventClient::ParseByte(unsigned char* &payload, int &psize, unsigned char& parsedVal)
  {
    if (psize <= 0)
      return false;
  
    parsedVal = *payload;
    payload++;
    psize--;
    return true;
  }
  
  bool CEventClient::ParseUInt32(unsigned char* &payload, int &psize, unsigned int& parsedVal)
  {
    if (psize < 4)
      return false;
  
    parsedVal = ntohl(*((unsigned int *)payload));
    payload+=4;
    psize-=4;
    return true;
  }
  
  bool CEventClient::ParseUInt16(unsigned char* &payload, int &psize, unsigned short& parsedVal)
  {
    if (psize < 2)
      return false;
  
    parsedVal = ntohs(*((unsigned short *)payload));
    payload+=2;
    psize-=2;
    return true;
  }
  
  void CEventClient::FreePacketQueues()
  {
    CSingleLock lock(m_critSection);
    while ( ! m_readyPackets.empty() )
    {
      delete m_readyPackets.front();
      m_readyPackets.pop();
    }
  
    std::map<unsigned int, EVENTPACKET::CEventPacket*>::iterator iter = m_seqPackets.begin();
    while (iter != m_seqPackets.end())
    {
      if (iter->second)
      {
        delete iter->second;
      }
      ++iter;
    }
    m_seqPackets.clear();
  }
  
  unsigned int CEventClient::GetButtonCode(std::string& joystickName, bool& isAxis, float& amount)
  {
    CSingleLock lock(m_critSection);
    unsigned int bcode = 0;
  
    if ( m_currentButton.Active() )
    {
      bcode = m_currentButton.KeyCode();
      joystickName = m_currentButton.JoystickName();
      isAxis = m_currentButton.Axis();
      amount = m_currentButton.Amount();
  
      if ( ! m_currentButton.Repeat() )
        m_currentButton.Reset();
      else
      {
        if ( ! CheckButtonRepeat(m_currentButton.m_iNextRepeat) )
          bcode = 0;
      }
      return bcode;
    }
  
    if(m_buttonQueue.empty())
      return 0;
  
  
    std::list<CEventButtonState> repeat;
    std::list<CEventButtonState>::iterator it;
    for(it = m_buttonQueue.begin(); bcode == 0 && it != m_buttonQueue.end(); ++it)
    {
      bcode        = it->KeyCode();
      joystickName = it->JoystickName();
      isAxis       = it->Axis();
      amount       = it->Amount();
  
      if(it->Repeat())
      {
        /* MUST update m_iNextRepeat before resend */
        bool skip = !it->Axis() && !CheckButtonRepeat(it->m_iNextRepeat);
  
        repeat.push_back(*it);
        if(skip)
        {
          bcode = 0;
          continue;
        }
      }
    }
  
    m_buttonQueue.erase(m_buttonQueue.begin(), it);
    m_buttonQueue.insert(m_buttonQueue.end(), repeat.begin(), repeat.end());
    return bcode;
  }
  
  bool CEventClient::GetMousePos(float& x, float& y)
  {
    CSingleLock lock(m_critSection);
    if (m_bMouseMoved)
    {
      x = (float)((m_iMouseX / 65535.0f) * g_graphicsContext.GetWidth());
      y = (float)((m_iMouseY / 65535.0f) * g_graphicsContext.GetHeight());
      m_bMouseMoved = false;
      return true;
    }
    return false;
  }
  
  bool CEventClient::CheckButtonRepeat(unsigned int &next)
  {
    unsigned int now = XbmcThreads::SystemClockMillis();
  
    if ( next == 0 )
    {
      next = now + m_iRepeatDelay;
      return true;
    }
    else if ( now > next )
    {
      next = now + m_iRepeatSpeed;
      return true;
    }
    return false;
  }
  
  bool CEventClient::Alive() const
  {
    // 60 seconds timeout
    if ( (time(NULL) - m_lastPing) > 60 )
      return false;
    return true;
  }
  
  #endif // HAS_EVENT_SERVER
  