text: #include "Fractal/IInputDevice.h"
  #include "Fractal/Ref.h"
  
  #include "ctHashMap.h"
  #include "ctVector.h"
  #include "ctString.h"
  #include "ctPool.h"
  
  #include <time.h>
  
  namespace Fractal
  {
    static ctVector<IInputDevice*> _devices;
    static ctVector<ctString>       _emptyNames;
  
    namespace Impl
    {
      class InputDevice : public IInputDevice
      {
        FRACTAL_DEFAULT_INTERFACE;
  
        class ButtonInput : public IButtonInput
        {
          friend InputDevice;
  
          FRACTAL_DEFAULT_INTERFACE;
  
        public:
          bool IsPressed() const
          {
            return m_pressed;
          }
  
          bool IsReleased() const
          {
            return m_released;
          }
  
          bool IsDown() const
          {
            return m_down;
          }
  
        protected:
          int64_t m_downTime = 0;
          int64_t m_pressedTime = 0;
          int64_t m_releasedTime = 0;
  
          bool m_down = false;
          bool m_pressed = false;
          bool m_released = false;
        };
  
        class AnalogInput : public IAnalogInput
        {
          friend InputDevice;
  
          FRACTAL_DEFAULT_INTERFACE;
  
        public:
          float GetValue() const
          {
            return m_value;
          }
  
          float GetDelta() const
          {
            return m_value - m_lastValue;
          }
  
          bool HasChanged() const
          {
            return m_value != m_lastValue;
          }
  
        protected:
          int64_t m_valueTime = 0;
          int64_t m_lastValueTime = 0;
  
          float m_value = 0;
          float m_lastValue = 0;
        };
  
      public:
        InputDevice(int64_t buttonCount, int64_t analogCount, IInputDeviceServer* pServer)
        {
          // Setup buttons
          m_buttons.resize(buttonCount);
          m_analogs.resize(analogCount);
  
          for (auto& pButton : m_buttons)
            pButton = flNew ButtonInput;
  
          for (auto& pButton : m_analogs)
            pButton = flNew AnalogInput;
  
          // Setup device and server
          m_pServer = pServer ? pServer : Fractal_CreateInputDeviceServer();
  
          _devices.push_back(this);
        }
  
        ~InputDevice()
        {
          for (int64_t i = 0; i < _devices.size(); ++i)
            if (_devices[i] == this)
              _devices.erase(i);
        }
  
        void SetServer(flIN IInputDeviceServer* pServer)
        {
          m_pServer = pServer;
        }
  
        IInputDeviceServer const* GetServer() const
        {
          return m_pServer.Get();
        }
  
        IInputDeviceServer* GetServer()
        {
          return m_pServer.Get();
        }
  
        int64_t GetButtonCount() const
        {
          return m_buttons.size();
        }
  
        int64_t GetAnalogCount() const
        {
          return m_analogs.size();
        }
  
        IButtonInput* GetButton(flIN int64_t index)
        {
          return index < 0 || index >= GetButtonCount() ? nullptr : m_buttons[index].Get();
        }
  
        IAnalogInput* GetAnalog(flIN int64_t index)
        {
          return index < 0 || index >= GetAnalogCount() ? nullptr : m_analogs[index].Get();
        }
  
        IButtonInput const* GetButton(flIN int64_t index) const
        {
          return index < 0 || index >= GetButtonCount() ? nullptr : m_buttons[index].Get();
        }
  
        IAnalogInput const* GetAnalog(flIN int64_t index) const
        {
          return index < 0 || index >= GetAnalogCount() ? nullptr : m_analogs[index].Get();
        }
  
        void Update()
        {
          m_pServer->Lock(); // Lock to stop new events from being received
  
          int64_t id = 0;
          int64_t timestamp = 0;
          bool    pressed = false;
          int64_t updateClock = clock();
  
          // Update buttons
          for (auto& button : m_buttons)
          {
            ButtonInput* pInput = (ButtonInput*)button.Get();
  
            pInput->m_pressed = false;
            pInput->m_released = false;
  
            if (pInput->m_down) // If the button is down, update the down time
              pInput->m_downTime = updateClock;
          }
  
          // Update analogs
          for (auto& analog : m_analogs)
          {
            AnalogInput* pInput = (AnalogInput*)analog.Get();
  
            pInput->m_lastValue = pInput->m_value;
            pInput->m_lastValueTime = pInput->m_valueTime;
            pInput->m_valueTime = updateClock;
          }
  
          // Receive button events
          while (m_pServer->GetEvent(&id, &pressed, &timestamp))
          {
            ButtonInput* pButton = (ButtonInput*)GetButton(id);
  
            if (pButton)
            {
              pButton->m_down = pressed;
              if (pressed)
              {
                pButton->m_pressed = true;
                pButton->m_pressedTime = timestamp;
              }
              else
              {
                pButton->m_released = true;
                pButton->m_releasedTime = timestamp;
              }
            }
          }
  
          // Receive analog events
          float value = 0;
          bool  addValue = false;
          while (m_pServer->GetEvent(&id, &value, &addValue, &timestamp))
          {
            AnalogInput* pAnalog = (AnalogInput*)GetAnalog(id);
  
            if (pAnalog)
            {
              pAnalog->m_value = addValue ? pAnalog->m_value + value : value;
              pAnalog->m_valueTime = timestamp;
            }
          }
  
          m_pServer->Unlock();
  
          OnPostUpdate();
        }
  
        void OnPreUpdate() { /*No Default behavior*/ }
        void OnPostUpdate() { /*No Default behavior*/ }
  
      private:
        Ref<IInputDeviceServer> m_pServer = nullptr;
  
        ctVector<Ref<IButtonInput>> m_buttons;
        ctVector<Ref<IAnalogInput>> m_analogs;
      };
    }
  }
  
  Fractal::IInputDevice* Fractal_CreateInputDevice(flIN int64_t buttonCount, flIN int64_t analogCount, flIN Fractal::IInputDeviceServer* pServer)
  {
    return flNew Fractal::Impl::InputDevice(buttonCount, analogCount, pServer);
  }
  
  int64_t Fractal_InputDevice_GetRegisteredCount()
  {
    return Fractal::_devices.size();
  }
  
  Fractal::IInputDevice* Fractal_InputDevice_GetDevice(flIN int64_t index)
  {
    return Fractal::_devices[index];
  }
  