/*
 *  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.
 */

#pragma once

#include "EventPacket.h"
#include "ServiceBroker.h"
#include "Socket.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "threads/CriticalSection.h"
#include "threads/Thread.h"

#include <chrono>
#include <list>
#include <map>
#include <queue>
#include <utility>

namespace EVENTCLIENT
{

  #define ES_FLAG_UNICODE    0x80000000 // new 16bit key flag to support real unicode over EventServer

  class CEventAction
  {
  public:
    CEventAction()
    {
      actionType = 0;
    }
    CEventAction(const char* action, unsigned char type):
      actionName(action)
    {
      actionType = type;
    }

    std::string    actionName;
    unsigned char  actionType;
  };

  class CEventButtonState
  {
  public:
    CEventButtonState() : m_iNextRepeat{}
    {
      m_iKeyCode   = 0;
      m_fAmount    = 0.0f;
      m_bUseAmount = false;
      m_bRepeat    = false;
      m_bActive    = false;
      m_bAxis      = false;
      m_iControllerNumber = 0;
    }

    CEventButtonState(unsigned int iKeyCode,
                      std::string mapName,
                      std::string buttonName,
                      float fAmount,
                      bool isAxis,
                      bool bRepeat,
                      bool bUseAmount)
      : m_buttonName(std::move(buttonName)), m_mapName(std::move(mapName)), m_iNextRepeat{}
    {
      m_iKeyCode   = iKeyCode;
      m_fAmount    = fAmount;
      m_bUseAmount = bUseAmount;
      m_bRepeat    = bRepeat;
      m_bActive    = true;
      m_bAxis      = isAxis;
      m_iControllerNumber = 0;
      Load();
    }

    void Reset()     { m_bActive = false; }
    void SetActive() { m_bActive = true; }
    bool Active() const { return m_bActive; }
    bool Repeat() const { return m_bRepeat; }
    int  ControllerNumber() const { return m_iControllerNumber; }
    bool Axis() const { return m_bAxis; }
    unsigned int KeyCode() const { return m_iKeyCode; }
    float Amount() const  { return m_fAmount; }
    void Load();
    const std::string& JoystickName() const { return m_joystickName; }
    const std::string& CustomControllerName() const { return m_customControllerName; }

    // data
    unsigned int      m_iKeyCode;
    unsigned short    m_iControllerNumber;
    std::string       m_buttonName;
    std::string       m_mapName;
    std::string       m_joystickName;
    std::string       m_customControllerName;
    float             m_fAmount;
    bool              m_bUseAmount;
    bool              m_bRepeat;
    bool              m_bActive;
    bool              m_bAxis;
    std::chrono::time_point<std::chrono::steady_clock> m_iNextRepeat;
  };


  /**********************************************************************/
  /* UDP EventClient Class                                              */
  /**********************************************************************/
  // - clients timeout if they don't receive at least 1 ping in 1 minute
  // - sequence packets timeout after 5 seconds
  class CEventClient
  {
  public:
    CEventClient()
    {
      Initialize();
    }

    explicit CEventClient(SOCKETS::CAddress& addr):
      m_remoteAddr(addr)
    {
      Initialize();
    }

    void Initialize()
    {
      m_bGreeted = false;
      m_iMouseX = 0;
      m_iMouseY = 0;
      m_iCurrentSeqLen = 0;
      m_lastPing = 0;
      m_lastSeq = 0;
      m_iRemotePort = 0;
      m_bMouseMoved = false;
      m_bSequenceError = false;
      RefreshSettings();
    }

    const std::string& Name() const
    {
      return m_deviceName;
    }

    void RefreshSettings()
    {
      const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
      m_iRepeatDelay =
          std::chrono::milliseconds(settings->GetInt(CSettings::SETTING_SERVICES_ESINITIALDELAY));
      m_iRepeatSpeed = std::chrono::milliseconds(
          settings->GetInt(CSettings::SETTING_SERVICES_ESCONTINUOUSDELAY));
    }

    SOCKETS::CAddress& Address()
    {
      return m_remoteAddr;
    }

    virtual ~CEventClient()
    {
      FreePacketQueues();
    }

    // add packet to queue
    bool AddPacket(std::unique_ptr<EVENTPACKET::CEventPacket> packet);

    // return true if client received ping with the last 1 minute
    bool Alive() const;

    // process the packet queue
    bool ProcessQueue();

    // process the queued up events (packets)
    void ProcessEvents();

    // gets the next action in the action queue
    bool GetNextAction(CEventAction& action);

    // deallocate all packets in the queues
    void FreePacketQueues();

    // return event states
    unsigned int GetButtonCode(std::string& strMapName, bool& isAxis, float& amount, bool &isJoystick);

    // update mouse position
    bool GetMousePos(float& x, float& y);

  protected:
    bool ProcessPacket(EVENTPACKET::CEventPacket *packet);

    // packet handlers
    virtual bool OnPacketHELO(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketBYE(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketBUTTON(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketMOUSE(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketNOTIFICATION(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketLOG(EVENTPACKET::CEventPacket *packet);
    virtual bool OnPacketACTION(EVENTPACKET::CEventPacket *packet);
    bool CheckButtonRepeat(std::chrono::time_point<std::chrono::steady_clock>& next);

    // returns true if the client has received the HELO packet
    bool Greeted() { return m_bGreeted; }

    // reset the timeout counter
    void ResetTimeout()
    {
      m_lastPing = time(NULL);
    }

    // helper functions

    // Parses a null terminated string from payload.
    // After parsing successfully:
    //   1. payload is incremented to end of string
    //   2. psize is decremented by length of string
    //   3. parsedVal contains the parsed string
    //   4. true is returned
    bool ParseString(unsigned char* &payload, int &psize, std::string& parsedVal);

    // Parses a single byte (same behavior as ParseString)
    bool ParseByte(unsigned char* &payload, int &psize, unsigned char& parsedVal);

    // Parse a single 32-bit integer (converts from network order to host order)
    bool ParseUInt32(unsigned char* &payload, int &psize, unsigned int& parsedVal);

    // Parse a single 16-bit integer (converts from network order to host order)
    bool ParseUInt16(unsigned char* &payload, int &psize, unsigned short& parsedVal);

    std::string       m_deviceName;
    int               m_iCurrentSeqLen;
    time_t            m_lastPing;
    time_t            m_lastSeq;
    int               m_iRemotePort;
    bool              m_bGreeted;
    std::chrono::milliseconds m_iRepeatDelay;
    std::chrono::milliseconds m_iRepeatSpeed;
    unsigned int      m_iMouseX;
    unsigned int      m_iMouseY;
    bool              m_bMouseMoved;
    bool              m_bSequenceError;

    SOCKETS::CAddress m_remoteAddr;

    EVENTPACKET::LogoType m_eLogoType;
    CCriticalSection  m_critSection;

    std::map<unsigned int, std::unique_ptr<EVENTPACKET::CEventPacket>> m_seqPackets;
    std::queue<std::unique_ptr<EVENTPACKET::CEventPacket>> m_readyPackets;

    // button and mouse state
    std::list<CEventButtonState>  m_buttonQueue;
    std::queue<CEventAction>      m_actionQueue;
    CEventButtonState m_currentButton;
  };

}

