// FGEventInput.hxx -- handle event driven input devices
//
// Written by Torsten Dreyer, started July 2009
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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 of the
// License, 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 this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// $Id$

#ifndef __FGEVENTINPUT_HXX
#define __FGEVENTINPUT_HXX

#include "FGCommonInput.hxx"

#include <vector>

#include "FGButton.hxx"
#include "FGDeviceConfigurationMap.hxx"
#include <simgear/structure/subsystem_mgr.hxx>

/*
 * A base structure for event data. 
 * To be extended for O/S specific implementation data
 */
struct FGEventData {
  FGEventData( double aValue, double aDt, int aModifiers ) : modifiers(aModifiers), value(aValue), dt(aDt) {}
  int modifiers;
  double value;
  double dt;
};

class FGEventSetting : public SGReferenced {
public:
  FGEventSetting( SGPropertyNode_ptr base );

  bool Test();

  /* 
   * access for the value property
   */
  double GetValue();

protected:
  double value;
  SGPropertyNode_ptr valueNode;
  SGSharedPtr<const SGCondition> condition;
};

typedef SGSharedPtr<FGEventSetting> FGEventSetting_ptr;
typedef std::vector<FGEventSetting_ptr> setting_list_t;

class FGReportSetting : public SGReferenced,
    public SGPropertyChangeListener
{
public:
    FGReportSetting( SGPropertyNode_ptr base );

    unsigned int getReportId() const
    {
        return reportId;
    }

    std::string getNasalFunctionName() const
    {
        return nasalFunction;
    }

    bool Test();

    std::string reportBytes(const std::string& moduleName) const;

    virtual void valueChanged(SGPropertyNode * node);
protected:
    unsigned int reportId;
    std::string nasalFunction;
    bool dirty;

};

typedef SGSharedPtr<FGReportSetting> FGReportSetting_ptr;
typedef std::vector<FGReportSetting_ptr> report_setting_list_t;

/*
 * A wrapper class for a configured event. 
 * 
 * <event>
 *   <desc>Change the view pitch</desc>
 *   <name>rel-x-rotate</name>
 *   <binding>
 *     <command>property-adjust</command>
 *     <property>sim/current-view/pitch-offset-deg</property>
 *     <factor type="double">0.01</factor>
 *     <min type="double">-90.0</min>
 *     <max type="double">90.0</max>
 *     <wrap type="bool">false</wrap>
 *   </binding>
 *   <mod-xyz>
 *    <binding>
 *      ...
 *    </binding>
 *   </mod-xyz>
 * </event>
 */
class FGInputDevice;
class FGInputEvent : public SGReferenced,FGCommonInput {
public:

  /*
   * Constructor for the class. The arg node shall point
   * to the property corresponding to the <event>  node
   */
  FGInputEvent( FGInputDevice * device, SGPropertyNode_ptr node );
  virtual ~FGInputEvent();

  /*
   * dispatch the event value through all bindings 
   */
  virtual void fire( FGEventData & eventData );

  /*
   * access for the name property
   */
  std::string GetName() const { return name; }

  /*
   * access for the description property
   */
  std::string GetDescription() const { return desc; }

  virtual void update( double dt );

  static FGInputEvent * NewObject( FGInputDevice * device, SGPropertyNode_ptr node );

protected:
  virtual void fire( SGBinding * binding, FGEventData & eventData );
  /* A more or less meaningfull description of the event */
  std::string desc;

  /* One of the predefined names of the event */
  std::string name;

  /* A list of SGBinding objects */
  binding_list_t bindings[KEYMOD_MAX];

  /* A list of FGEventSetting objects */
  setting_list_t settings;

  /* A pointer to the associated device */
  FGInputDevice * device;

  double lastDt;
  double intervalSec;
  double lastSettingValue;
};

class FGButtonEvent : public FGInputEvent {
public:
  FGButtonEvent( FGInputDevice * device, SGPropertyNode_ptr node );
  virtual void fire( FGEventData & eventData );

protected:
  bool repeatable;
  bool lastState;
};

class FGAxisEvent : public FGInputEvent {
public:
  FGAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node );
  void SetMaxRange( double value ) { maxRange = value; }
  void SetMinRange( double value ) { minRange = value; }
  void SetRange( double min, double max ) { minRange = min; maxRange = max; }
protected:
  virtual void fire( FGEventData & eventData );
  double tolerance;
  double minRange;
  double maxRange;
  double center;
  double deadband;
  double lowThreshold;
  double highThreshold;
  double lastValue;
};

class FGRelAxisEvent : public FGAxisEvent {
public:
  FGRelAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node );
protected:
  virtual void fire( SGBinding * binding, FGEventData & eventData );
};

class FGAbsAxisEvent : public FGAxisEvent {
public:
  FGAbsAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node ) : FGAxisEvent( device, node ) {}
protected:
  virtual void fire( SGBinding * binding, FGEventData & eventData );
};

typedef class SGSharedPtr<FGInputEvent> FGInputEvent_ptr;

/*
 * A abstract class implementing basic functionality of input devices for
 * all operating systems. This is the base class for the O/S-specific
 * implementation of input device handlers
 */
class FGInputDevice : public SGReferenced {
public:
  FGInputDevice() {}
  FGInputDevice( std::string aName, std::string aSerial = {} ) :
    name(aName), serialNumber(aSerial) {}
    
  virtual ~FGInputDevice();

  virtual void Open() = 0;
  virtual void Close() = 0;

  virtual void Send( const char * eventName, double value ) = 0;

  inline void Send( const std::string & eventName, double value ) {
    Send( eventName.c_str(), value );
  }

    virtual void SendFeatureReport(unsigned int reportId, const std::string& data);

  virtual const char * TranslateEventName( FGEventData & eventData ) = 0;


  void SetName( std::string name );
  std::string & GetName() { return name; }

  void SetSerialNumber( std::string serial );
  std::string& GetSerialNumber() { return serialNumber; }
    
  void HandleEvent( FGEventData & eventData );

  virtual void AddHandledEvent( FGInputEvent_ptr handledEvent );

  virtual void Configure( SGPropertyNode_ptr deviceNode );

  virtual void update( double dt );

  bool GetDebugEvents () const { return debugEvents; }

  bool GetGrab() const { return grab; }

  const std::string & GetNasalModule() const { return nasalModule; }

protected:
  // A map of events, this device handles
  std::map<std::string,FGInputEvent_ptr> handledEvents;

  // the device has a name to be recognized
  std::string name;

  // serial number string to disambiguate multiple instances
  // of the same device
  std::string serialNumber;
    
  // print out events comming in from the device
  // if true
  bool   debugEvents = false;

  // grab the device exclusively, if O/S supports this
  // so events are not sent to other applications
  bool   grab = false;

  SGPropertyNode_ptr deviceNode;
  std::string nasalModule;

    report_setting_list_t reportSettings;
};

typedef SGSharedPtr<FGInputDevice> FGInputDevice_ptr;


/*
 * The Subsystem for the event input device 
 */
class FGEventInput : public SGSubsystem,FGCommonInput {
public:
  FGEventInput();
  virtual ~FGEventInput();
  virtual void init();
  virtual void postinit();
  virtual void update( double dt );
  virtual void shutdown() override;

  const static unsigned MAX_DEVICES = 1000;
  const static unsigned INVALID_DEVICE_INDEX = MAX_DEVICES + 1;
protected:
  static const char * PROPERTY_ROOT;

  unsigned AddDevice( FGInputDevice * inputDevice );
  void RemoveDevice( unsigned index );

  std::map<int,FGInputDevice*> input_devices;
  FGDeviceConfigurationMap configMap;

  SGPropertyNode_ptr nasalClose;
};

#endif