#ifndef VRPN_ANALOG_H
#define VRPN_ANALOG_H

#include <stddef.h> // for NULL

#include "vrpn_BaseClass.h"  // for vrpn_Callback_List, etc
#include "vrpn_Configure.h"  // for VRPN_API, VRPN_CALLBACK
#include "vrpn_Connection.h" // for vrpn_CONNECTION_LOW_LATENCY, etc
#include "vrpn_Shared.h"     // for timeval
#include "vrpn_Types.h"      // for vrpn_int32, vrpn_float64, etc

#ifndef VRPN_CLIENT_ONLY
#include "vrpn_Serial.h" // for ::vrpn_SER_PARITY_NONE, etc
#endif

#define vrpn_CHANNEL_MAX 128

// analog status flags
const int vrpn_ANALOG_SYNCING = (2);
const int vrpn_ANALOG_REPORT_READY = (1);
const int vrpn_ANALOG_PARTIAL = (0);
const int vrpn_ANALOG_RESETTING = (-1);
const int vrpn_ANALOG_FAIL = (-2);

// Analog time value meaning "go find out what time it is right now"
const struct timeval vrpn_ANALOG_NOW = {0, 0};

class VRPN_API vrpn_Analog : public vrpn_BaseClass {
public:
    vrpn_Analog(const char *name, vrpn_Connection *c = NULL);

    // Print the status of the analog device
    void print(void);

    vrpn_int32 getNumChannels(void) const;

protected:
    vrpn_float64 channel[vrpn_CHANNEL_MAX];
    vrpn_float64 last[vrpn_CHANNEL_MAX];
    vrpn_int32 num_channel;
    struct timeval timestamp;
    vrpn_int32 channel_m_id; //< channel message id (message from server)
    int status;

    virtual int register_types(void);

    //------------------------------------------------------------------
    // Routines used to send data from the server
    virtual vrpn_int32 encode_to(char *buf);
    /// Send a report only if something has changed (for servers)
    /// Optionally, tell what time to stamp the value with
    virtual void
    report_changes(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY,
                   const struct timeval time = vrpn_ANALOG_NOW);
    /// Send a report whether something has changed or not (for servers)
    /// Optionally, tell what time to stamp the value with
    virtual void
    report(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY,
           const struct timeval time = vrpn_ANALOG_NOW);
};

#ifndef VRPN_CLIENT_ONLY
class VRPN_API vrpn_Serial_Analog : public vrpn_Analog {
public:
    vrpn_Serial_Analog(const char *name, vrpn_Connection *connection,
                       const char *port, int baud = 9600, int bits = 8,
                       vrpn_SER_PARITY parity = vrpn_SER_PARITY_NONE,
                       bool rts_flow = false);
    ~vrpn_Serial_Analog();

protected:
    int serial_fd;
    char portname[1024];
    int baudrate;
    unsigned char buffer[1024];
    int bufcounter;

    int read_available_characters(char *buffer, int bytes);
};
#endif

// vrpn_Analog_Server
// Tom Hudson, March 1999
//
// A *Sample* Analog server.  Use this or derive your own from vrpn_Analog with
// this as a guide.
//
// Write whatever values you want into channels(), then call report()
// or report_changes().  (Original spec only called for report_changes(),
// but vrpn_Analog's assumption that "no new data = same data" doesn't
// match the BLT stripchart assumption  of "no intervening data = ramp".
//
// For a sample application, see server_src/sample_analog.C

class VRPN_API vrpn_Analog_Server : public vrpn_Analog {

public:
    vrpn_Analog_Server(const char *name, vrpn_Connection *c,
                       vrpn_int32 numChannels = vrpn_CHANNEL_MAX);

    /// Makes public the protected base class function
    virtual void
    report_changes(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY,
                   const struct timeval time = vrpn_ANALOG_NOW);

    /// Makes public the protected base class function
    virtual void
    report(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY,
           const struct timeval time = vrpn_ANALOG_NOW);

    /// For this server, the user must normally call report() or
    /// report_changes() directly.  This mainloop() only takes
    /// care of the things any server object should do.
    virtual void mainloop() { server_mainloop(); };

    /// Exposes an array of values for the user to write into.
    vrpn_float64 *channels(void) { return channel; }

    /// Sets the size of the array;  returns the size actually set.
    /// (May be clamped to vrpn_CHANNEL_MAX)
    /// This should be used before mainloop is ever called.
    vrpn_int32 setNumChannels(vrpn_int32 sizeRequested);
};

/// Analog server that can scale and clip its range to -1..1.
// This is useful for joysticks, to allow them to be centered and
// scaled to cover the whole range.  Rather than writing directly
// into the channels array, call the setChannel() method.

class VRPN_API vrpn_Clipping_Analog_Server : public vrpn_Analog_Server {
public:
    vrpn_Clipping_Analog_Server(const char *name, vrpn_Connection *c,
                                vrpn_int32 numChannels = vrpn_CHANNEL_MAX);

    /// Set the clipping values for the specified channel.
    /// min maps to -1, values between lowzero and highzero map to 0,
    /// max maps to 1.  Values less than min map to -1, values larger
    /// than max map to 1. Default for each channel is -1,0,0,1
    /// It is possible to compress the range to [0..1] by setting the
    /// minimum equal to the lowzero.
    /// Returns 0 on success, -1 on failure.
    int setClipValues(int channel, double min, double lowzero, double highzero,
                      double max);

    /// This method should be used to set the value of a channel.
    /// It will be scaled and clipped as described in setClipValues.
    /// It returns 0 on success and -1 on failure.
    int setChannelValue(int channel, double value);

protected:
    typedef struct {
        double minimum_val; // Value mapped to -1
        double lower_zero;  // Minimum value mapped to 0
        double upper_zero;  // Maximum value mapped to 0
        double maximum_val; // Value mapped to 1
    } clipvals_struct;

    clipvals_struct clipvals[vrpn_CHANNEL_MAX];
};

//----------------------------------------------------------
//************** Users deal with the following *************

// User routine to handle a change in analog values.  This is called when
// the analog callback is called (when a message from its counterpart
// across the connection arrives).

typedef struct _vrpn_ANALOGCB {
    struct timeval msg_time;                // Timestamp of analog data
    vrpn_int32 num_channel;                 // how many channels
    vrpn_float64 channel[vrpn_CHANNEL_MAX]; // analog values
} vrpn_ANALOGCB;

typedef void(VRPN_CALLBACK *vrpn_ANALOGCHANGEHANDLER)(void *userdata,
                                                      const vrpn_ANALOGCB info);

// Open an analog device that is on the other end of a connection
// and handle updates from it.  This is the type of analog device
// that user code will deal with.

class VRPN_API vrpn_Analog_Remote : public vrpn_Analog {
public:
    // The name of the analog device to connect to
    // Optional argument to be used when the Remote should listen on
    // a connection that is already open.
    vrpn_Analog_Remote(const char *name, vrpn_Connection *c = NULL);

    // This routine calls the mainloop of the connection it's on
    virtual void mainloop();

    // (un)Register a callback handler to handle analog value change
    virtual int register_change_handler(void *userdata,
                                        vrpn_ANALOGCHANGEHANDLER handler)
    {
        return d_callback_list.register_handler(userdata, handler);
    };
    virtual int unregister_change_handler(void *userdata,
                                          vrpn_ANALOGCHANGEHANDLER handler)
    {
        return d_callback_list.unregister_handler(userdata, handler);
    }

protected:
    vrpn_Callback_List<vrpn_ANALOGCB> d_callback_list;

    static int VRPN_CALLBACK
    handle_change_message(void *userdata, vrpn_HANDLERPARAM p);
};

#endif
