#ifndef VRPN_MUTEX_H
#define VRPN_MUTEX_H

#include <stddef.h> // for NULL

#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API
#include "vrpn_Types.h"     // for vrpn_int32, vrpn_uint32, etc

class VRPN_API vrpn_Connection;
struct vrpn_HANDLERPARAM;
// Every time a Mutex_Remote connects to a Mutex_Server, the server assigns
// a unique ID to the remote.
// HACK - because vrpn doesn't let us unicast within a multicast (multiple-
// connection server) (in any clean way), or identify at a MC server which
// connection a message came in over, this code is fragile - it depends
// on the fact that vrpn_Connection only allows one connection to be made
// before triggering the got_connection callback.  If connections were somehow
// batched, or we multithreaded vrpn_Connection, this would break.

class VRPN_API vrpn_Mutex {

public:
    vrpn_Mutex(const char *name, vrpn_Connection * = NULL);
    virtual ~vrpn_Mutex(void) = 0;

    void mainloop(void);

protected:
    vrpn_Connection *d_connection;

    vrpn_int32 d_myId;
    vrpn_int32 d_requestIndex_type;
    vrpn_int32 d_requestMutex_type;
    vrpn_int32 d_release_type;
    vrpn_int32 d_releaseNotification_type;
    vrpn_int32 d_grantRequest_type;
    vrpn_int32 d_denyRequest_type;
    vrpn_int32 d_initialize_type;

    void sendRequest(vrpn_int32 index);
    void sendRelease(void);
    void sendReleaseNotification(void);
    void sendGrantRequest(vrpn_int32 index);
    void sendDenyRequest(vrpn_int32 index);
};

class VRPN_API vrpn_Mutex_Server : public vrpn_Mutex {

public:
    vrpn_Mutex_Server(const char *name, vrpn_Connection * = NULL);
    virtual ~vrpn_Mutex_Server(void);

protected:
    enum state { HELD, FREE };

    state d_state;

    vrpn_int32 d_remoteIndex;
    ///< Counts remotes who have had IDs issued to them.

    static int VRPN_CALLBACK handle_requestIndex(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_requestMutex(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_release(void *, vrpn_HANDLERPARAM);

    static int VRPN_CALLBACK handle_gotConnection(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK
    handle_dropLastConnection(void *, vrpn_HANDLERPARAM);
};

class VRPN_API vrpn_Mutex_Remote : public vrpn_Mutex {

public:
    vrpn_Mutex_Remote(const char *name, vrpn_Connection * = NULL);
    virtual ~vrpn_Mutex_Remote(void);

    // ACCESSORS

    vrpn_bool isAvailable(void) const;
    ///< True from when release() is called or we receive a release
    ///< message from another process until request() is called or we
    ///< grant the lock to another process in response to its request
    ///< message.
    vrpn_bool isHeldLocally(void) const;
    ///< True from when RequestGranted callbacks are triggered until
    ///< release() is called.
    vrpn_bool isHeldRemotely(void) const;
    ///< True from when we grant the lock to another process in response
    ///< to its request message until we receive a release message from
    ///< another process.

    // MANIPULATORS

    void request(void);
    ///< Request the distributed lock.  Does not request the lock
    ///< if !isAvailable(), instead automatically triggering DeniedCallbacks.

    void release(void);
    ///< Release the distributed lock.  Does nothing if !isHeldLocally()
    ///< and there isn't a request pending.

    vrpn_bool addRequestGrantedCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when OUR request is granted.
    vrpn_bool addRequestDeniedCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when OUR request is denied.
    vrpn_bool addTakeCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when ANY peer gets the mutex.
    vrpn_bool addReleaseCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when ANY peer releases the
    ///< mutex.

protected:
    void requestIndex(void);

    enum state { OURS, REQUESTING, AVAILABLE, HELD_REMOTELY };

    state d_state;
    vrpn_int32 d_myIndex;
    vrpn_bool d_requestBeforeInit;

    static int VRPN_CALLBACK handle_grantRequest(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_denyRequest(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK
    handle_releaseNotification(void *, vrpn_HANDLERPARAM);

    static int VRPN_CALLBACK handle_initialize(void *, vrpn_HANDLERPARAM);

    static int VRPN_CALLBACK handle_gotConnection(void *, vrpn_HANDLERPARAM);

    void triggerGrantCallbacks(void);
    void triggerDenyCallbacks(void);
    void triggerTakeCallbacks(void);
    void triggerReleaseCallbacks(void);

    struct mutexCallback {
        int (*f)(void *);
        void *userdata;
        mutexCallback *next;
    };

    mutexCallback *d_reqGrantedCB;
    mutexCallback *d_reqDeniedCB;
    mutexCallback *d_takeCB;
    mutexCallback *d_releaseCB;
};

/// vrpn_PeerMutex
///
///   This class provides distributed mutual exclusion between every instance
/// with the same name for which addPeer() has been called.
///   If a process calls request() when isAvailable() returns true,
/// the mutex will attempt to secure a lock to whatever resource it is
/// governing;  either RequestGranted or RequestDenied callbacks will
/// be triggered.  If RequestGranted callbacks are triggered, the process
/// has the lock until it explicitly calls release() (and can verify this
/// by checking isHeldLocally()).  Once the lock-owner calls release(),
/// Release callbacks at every peer will be triggered.
///
///   Like most vrpn classes, the mainloop() must be called frequently.
///
///   Note that none of isAvailable(), isHeldLocally(), and isHeldRemotely()
/// are true between when request() is called and either RequestGranted or
/// RequestDenied callbacks are triggered.

// Known bugs -

//   The constructor that takes a Connection as an argument will incorrectly
// identify its IP address as the machine's default rather than the address
// used by the Connection.  This should not cause any errors in the protocol,
// but will bias the tiebreaking algorithm.  The same constructor will use
// the wrong port number;  without this information the tiebreaking algorithm
// fails.  Oops.  Use only one mutex per Connection for now.

// Possible bugs -

//   If on startup somebody else is holding the mutex we'll think it's
// available.  However, if we request it they'll deny it to us and
// we won't break.
//   If sites don't execute the same set of addPeer() commands, they may
// implicitly partition the network and not get true mutual exclusion.
// This could be fixed by sending an addPeer message.
//   If sites execute addPeer() while the lock is held, or being requested,
// we'll break.
//   - To fix:  send messages, but defer all executions of addPeer until the
// lock is released.  If we want to be really careful here, on getting an
// addPeer message when we think the lock is available we should request
// the lock and then (if we get it) release it immediately, without
// triggering any user callbacks.  Sounds tough to code?

// Handling more than 2 sites in a mutex requires multiconnection servers.
// It's been tested with 1-3 sites, and works fine.

// This is an O(n^2) network traffic implementation;
// for details (and how to fix if it ever becomes a problem),
// see the implementation notes in vrpn_Mutex.C.

class VRPN_API vrpn_PeerMutex {

public:
    vrpn_PeerMutex(const char *name, int port, const char *NICaddress = NULL);
    ///< This constructor opens a new connection/port for the mutex.

    ~vrpn_PeerMutex(void);
    ///< If isHeldLocally(), calls release().

    // ACCESSORS

    vrpn_bool isAvailable(void) const;
    ///< True from when release() is called or we receive a release
    ///< message from another process until request() is called or we
    ///< grant the lock to another process in response to its request
    ///< message.
    vrpn_bool isHeldLocally(void) const;
    ///< True from when RequestGranted callbacks are triggered until
    ///< release() is called.
    vrpn_bool isHeldRemotely(void) const;
    ///< True from when we grant the lock to another process in response
    ///< to its request message until we receive a release message from
    ///< another process.

    int numPeers(void) const;

    // MANIPULATORS

    void mainloop(void);

    void request(void);
    ///< Request the distributed lock.  Does not request the lock
    ///< if !isAvailable(), instead automatically triggering DeniedCallbacks.

    void release(void);
    ///< Release the distributed lock.  Does nothing if !isHeldLocally()
    ///< and there isn't a request pending.

    vrpn_bool addPeer(const char *stationName);
    ///< Takes a VRPN station name of the form "<host>:<port>".

    vrpn_bool addRequestGrantedCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when OUR request is granted.
    vrpn_bool addRequestDeniedCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when OUR request is denied.
    vrpn_bool addTakeCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when ANY peer gets the mutex.
    ///< (If several peers are competing for the mutex, and the
    ///<  implementation issues multiple "grants", these callbacks will
    ///<  only be triggered once between triggerings of ReleaseCallbacks.)
    vrpn_bool addReleaseCallback(void *userdata, int (*)(void *));
    ///< These callbacks are triggered when ANY peer releases the
    ///< mutex.

protected:
    enum state { OURS, REQUESTING, AVAILABLE, HELD_REMOTELY };

    char *d_mutexName;

    state d_state;

    int d_numPeersGrantingLock;
    ///< Counts the number of "grants" we've received after issuing
    ///< a request;  when this reaches d_numPeers, the lock is ours.

    vrpn_Connection *d_server;
    ///< Receive on this connection.
    vrpn_Connection **d_peer;
    ///< Send on these connections to other Mutex's well-known-ports.

    int d_numPeers;
    int d_numConnectionsAllocated;
    ///< Dynamic array size for d_peer and d_peerGrantedLock.

    vrpn_uint32 d_myIP;
    vrpn_uint32 d_myPort;
    vrpn_uint32 d_holderIP;
    vrpn_int32 d_holderPort;

    vrpn_int32 d_myId;
    vrpn_int32 d_request_type;
    vrpn_int32 d_release_type;
    vrpn_int32 d_grantRequest_type;
    vrpn_int32 d_denyRequest_type;
    // vrpn_int32 d_losePeer_type;

    static int VRPN_CALLBACK handle_request(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_release(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_grantRequest(void *, vrpn_HANDLERPARAM);
    static int VRPN_CALLBACK handle_denyRequest(void *, vrpn_HANDLERPARAM);

    static int VRPN_CALLBACK handle_losePeer(void *, vrpn_HANDLERPARAM);

    void sendRequest(vrpn_Connection *);
    void sendRelease(vrpn_Connection *);
    void sendGrantRequest(vrpn_Connection *, vrpn_uint32 IPnumber,
                          vrpn_uint32 PortNumber);
    void sendDenyRequest(vrpn_Connection *, vrpn_uint32 IPnumber,
                         vrpn_uint32 PortNumber);

    void triggerGrantCallbacks(void);
    void triggerDenyCallbacks(void);
    void triggerTakeCallbacks(void);
    void triggerReleaseCallbacks(void);

    void checkGrantMutex(void);

    void init(const char *name);

    struct mutexCallback {
        int (*f)(void *);
        void *userdata;
        mutexCallback *next;
    };

    mutexCallback *d_reqGrantedCB;
    mutexCallback *d_reqDeniedCB;
    mutexCallback *d_takeCB;
    mutexCallback *d_releaseCB;

    struct peerData {
        vrpn_uint32 IPaddress;
        vrpn_uint32 port;
        vrpn_bool grantedLock;
    };

    peerData *d_peerData;
    ///< Needed only to clean up when a peer shuts down (mid-request).
    ///< It isn't currently feasible to have all this data, so instead
    ///< we abort requests that were interrupted by a shutdown.

    vrpn_PeerMutex(const char *name, vrpn_Connection *c);
    ///< This constructor reuses a SERVER connection for the mutex.
    ///< BUG BUG BUG - do not use this constructor;  it does not reliably
    ///< resolve race conditions.
};

#endif // VRPN_MUTEX_H
