/*

ARIA header files for use with ARNL 1.7.1

Copyright(C) 2004, 2005 ActivMedia Robotics, LLC. 
Copyright(C) 2006, 2007, 2008, 2009 MobileRobots Inc.
All rights reserved.

This copy of Aria was relicensed for use with Arnl and the Arnl
license by MobileRobots Inc.  If you wish to download a seperate
distribution of Aria licensed under the GPL or a commercial license go to
http://www.mobilerobots.com/SOFTWARE/aria.html or contact MobileRobots
Inc, at robots@mobilerobots.com or MobileRobots Inc,
10 Columbia Drive, Amherst, NH 03031; 800-639-9481

MobileRobots Inc hereby grants to other individuals or
organizations permission to use this software with Arnl and in
compliance with the Arnl license.  This software may not be
distributed to others except by MobileRobots Inc.

MobileRobots Inc does not make any representations about the
suitability of this software for any purpose.  It is provided "as is"
without express or implied warranty.

*/
#ifndef ARSOCKET_H
#define ARSOCKET_H


#ifndef WIN32
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#endif


#include <string>
#include "ariaTypedefs.h"
#include "ArMutex.h"
#include "ariaUtil.h"

class ArFunctor;


/// socket communication wrapper
/**
   ArSocket is a layer which allows you to use the sockets networking
   interface in an operating system independent manner. All of the standard
   commonly used socket functions are implemented such as open(), close(),
   connect(), accept(), read(), write(), hostToNetOrder(), netToHostOrder().  ArSocket extends some of these functions to set useful options (see method documentation for details). It also provides additional useful functions like
   writeString(), readString, setCloseCallback(), and more.

   In Windows, the sockets subsystem needs to be initialized and shutdown
   by the program. So when a program starts it must call Aria::init() and
   call Aria::shutdown() when it exits. (Or, to only initialize the socket
   system, and not do any other global Aria initialization, use ArSocket::init()
   and ArSocket::shutdown().)

   Some calls set an error code on failure in addition to returning false. This value is available from getError().
   If getError() returns something other than NoErr, a text description of the error may be available from getErrorStr().  

   @sa @ref socketServerExample.cpp
   @sa @ref socketClientExample.cpp
*/
class ArSocket
{
public:

  enum Type {UDP, TCP, Unknown};
  enum Error {NoErr, NetFail, ConBadHost, ConNoRoute, ConRefused, NameLookup};

  /// Constructor
  AREXPORT ArSocket();

  /// Constructor which automatically connects to a server as a client
  AREXPORT ArSocket(const char *host, int port, Type type);

  /// Constructor which outomatically opens a server port
  AREXPORT ArSocket(int port, bool doClose, Type type);

  /// Destructor
  AREXPORT ~ArSocket();

  /// Initialize the OS sockets system, if neccesary
  AREXPORT static bool init();

  /// Shutdown the OS sockets system, if neccesary
  AREXPORT static void shutdown();

  /** @internal */
  AREXPORT static bool ourInitialized;

  /// Copy socket structures
  AREXPORT bool copy(int fd, bool doclose);

  /// Copy socket structures
  AREXPORT void copy(ArSocket *s)
    {myFD=s->myFD; myDoClose=false; mySin=s->mySin;}

  /// Transfer ownership of a socket
  /** transfer() will transfer ownership to this socket. The input socket
      will no longer close the file descriptor when it is destructed.
  */
  AREXPORT void transfer(ArSocket *s)
    {myFD=s->myFD; myDoClose=true; s->myDoClose=false; mySin=s->mySin;
      myType=s->myType; strcpy(myRawIPString, s->myRawIPString); 
      myIPString = s->myIPString; }

  /// Connect as a client to a server
  AREXPORT bool connect(const char *host, int port, Type type = TCP,
			const char *openOnIP = NULL);

  /** Open a server port

      Opens a server port, that is, a port that is bound to a local port (and optionally, address) 
      and listens for new incoming connections.  Use accept() to wait for a
      new incoming connection from a client.

      In additon, internally this method calls setLinger(0), setReuseAddress(),
      and setNonBlock() to turn on these options (having these on is 
      particularly useful for servers).  
      
      @param port Port number
      @param type ArSocket::TCP or ArSocket::UDP.
      @param openOnIP If given, only bind to the interface
        accociated with this address (Linux only) (by default, all interfaces are used)
  */
  AREXPORT bool open(int port, Type type, const char *openOnIP = NULL);

  /// Simply create a port.
  AREXPORT bool create(Type type);

  /** Find the first valid unused port after @a startPort, and bind the socket to it.
      @param startPort first port to try
	    @param openOnIP If given, only check ports open on the interface accociated with this address (Linux only)
  */
  AREXPORT bool findValidPort(int startPort, const char *openOnIP = NULL);

  /// Connect the socket to the given address
  AREXPORT bool connectTo(const char *host, int port);

  /// Connect the socket to the given address
  AREXPORT bool connectTo(struct sockaddr_in *sin);

  /// Accept a new connection
  AREXPORT bool accept(ArSocket *sock);

  /// Close the socket
  AREXPORT bool close();

  /// Write data to the socket
  AREXPORT int write(const void *buff, size_t len);

  /// Read data from the socket
  AREXPORT int read(void *buff, size_t len, unsigned int msWait = 0);

  /// Send a message (short string) on the socket
  AREXPORT int sendTo(const void *msg, int len);

  /// Send a message (short string) on the socket
  AREXPORT int sendTo(const void *msg, int len, struct sockaddr_in *sin);

  /// Receive a message (short string) from the socket
  AREXPORT int recvFrom(void *msg, int len, sockaddr_in *sin);

  /// Convert a hostname string to an address structure
  AREXPORT static bool hostAddr(const char *host, struct in_addr &addr);

  /// Convert an address structure to a hostname string
  AREXPORT static bool addrHost(struct in_addr &addr, char *host);

  /// Get the localhost address
  AREXPORT static std::string getHostName();

  /// Get socket information (socket "name"). Aspects of this "name" are accessible with sockAddrIn(), inAddr(), inPort() 
  AREXPORT bool getSockName();

  /// Accessor for the sockaddr
  struct sockaddr_in * sockAddrIn() {return(&mySin);}

  /// Accessor for the in_addr part of sockaddr
  struct in_addr * inAddr() {return(&mySin.sin_addr);}

  /// Accessor for the port of the sockaddr
  unsigned short int inPort() {return(mySin.sin_port);}

  /// Convert @a addr into string numerical address
  AREXPORT static void inToA(struct in_addr *addr, char *buff);

  /// Size of the sockaddr
  static size_t sockAddrLen() {return(sizeof(struct sockaddr_in));}

#ifdef WIN32
  /// Max host name length
  static size_t maxHostNameLen() {return(MAXGETHOSTSTRUCT);}
#else
  /// Max host name length
  static size_t maxHostNameLen() {return(MAXHOSTNAMELEN);}
#endif

  /// Convert an int from host byte order to network byte order
  AREXPORT static unsigned int hostToNetOrder(int i);

  /// Convert an int from network byte order to host byte order
  AREXPORT static unsigned int netToHostOrder(int i);

  /// Set the linger value
  AREXPORT bool setLinger(int time);

  /// Set broadcast value
  AREXPORT bool setBroadcast();

  /// Set the reuse address value
  AREXPORT bool setReuseAddress();

  /// Set socket to nonblocking.  Most importantly, calls to read() will return immediately, even if no data is available.
  AREXPORT bool setNonBlock();

  /// Change the doClose value
  AREXPORT void setDoClose(bool yesno) {myDoClose=yesno;}

  /// Get the file descriptor
  AREXPORT int getFD() const {return(myFD);}

  /// Get the protocol type
  AREXPORT Type getType() const {return(myType);}

  /// Get a string containing a description of the last error. Only valid if getError() does not return NoErr.
  AREXPORT const std::string & getErrorStr() const {return(myErrorStr);}

  /// Get a code representing the last error
  AREXPORT Error getError() const {return(myError);}

  /// Sets whether we're  error tracking or not
  AREXPORT void setErrorTracking(bool errorTracking)
    { myErrorTracking = errorTracking; }

  /// Gets whether we're doing error tracking or not
  AREXPORT bool getErrorTracking(void) { return myErrorTracking; }

  /// Gets if we've had a bad write (you have to use error tracking for this)
  AREXPORT bool getBadWrite(void) const { return myBadWrite; }

  /// Gets if we've had a bad read (you have to use error tracking for this)
  AREXPORT bool getBadRead(void) const { return myBadRead; }


#ifndef SWIG
  /** @brief Writes a string to the socket (adding end of line characters)
   *  @swigomit
   *  @sa writeStringPlain()
   */
  AREXPORT int writeString(const char *str, ...);
#endif

  /// Same as writeString(), but no varargs 
  AREXPORT int writeStringPlain(const char *str) { return writeString(str); }

  /// Reads a string from the socket
  AREXPORT char *readString(unsigned int msWait = 0);
  /// Whether to ignore carriage return characters in readString or not
  AREXPORT void setReadStringIgnoreReturn(bool ignore)
    { myStringIgnoreReturn = ignore; }
  /// Clears the partial string read
  AREXPORT void clearPartialReadString(void);
  /// Compares a string against what was partially read
  AREXPORT int comparePartialReadString(const char *partialString);
  /// Gets the time we last successfully read a string from the socket
  AREXPORT ArTime getLastStringReadTime(void) { return myLastStringReadTime; }
  /// Sets echoing on the readString calls this socket does
  AREXPORT void setEcho(bool echo) 
  { myStringAutoEcho = false; myStringEcho = echo; }
  /// Gets if we are echoing on the readString calls this socket does
  AREXPORT bool getEcho(void) { return myStringEcho; }
  /// Sets whether we log the writeStrings or not
  AREXPORT void setLogWriteStrings(bool logWriteStrings) 
    { myLogWriteStrings = logWriteStrings; }
  /// Gets whether we log the writeStrings or not
  AREXPORT bool getLogWriteStrings(void) { return myLogWriteStrings; }
  /// Sets whether we use the wrong (legacy) end chars or not
  AREXPORT void setStringUseWrongEndChars(bool useWrongEndChars) 
    { myStringWrongEndChars = useWrongEndChars; }
  /// Gets whether we log the writeStrings or not
  AREXPORT bool getStringUseWrongEndChars(void) 
    { return myStringWrongEndChars; }
  /// Gets the raw ip number as a string
  AREXPORT const char *getRawIPString(void) const { return myRawIPString; }
  /// Gets the ip number as a string (this can be modified though)
  AREXPORT const char *getIPString(void) const { return myIPString.c_str(); }
  /// Sets the ip string
  AREXPORT void setIPString(const char *ipString) 
    { if (ipString != NULL) myIPString = ipString; else myIPString = ""; }
  /// Sets the callback for when the socket is closed (nicely or harshly)
  AREXPORT void setCloseCallback(ArFunctor *functor) 
    { myCloseFunctor = functor; }
  /// Sets the callback for when the socket is closed (nicely or harshly)
  AREXPORT ArFunctor *getCloseCallback(void) { return myCloseFunctor; }
  /// Gets the number of writes we've done
  long getSends(void) { return mySends; }
  /// Gets the number of bytes we've written
  long getBytesSent(void) { return myBytesSent; }
  /// Gets the number of reads we've done
  long getRecvs(void) { return myRecvs; }
  /// Gets the number of bytes we've read
  long getBytesRecvd(void) { return myBytesRecvd; }
  /// Resets the tracking information on the socket
  void resetTracking(void) 
    { mySends = 0; myBytesSent = 0; myRecvs = 0; myBytesRecvd = 0; }
  
  /// Sets NODELAY option on TCP socket, which can reduce latency for small packet sizes.
  AREXPORT bool setNoDelay(bool flag);
  /// Sets the ip string
protected:
  /// internal function that sets the ip string from the inAddr
  void setRawIPString(void);
  /// internal function that echos strings from read string
  void doStringEcho(void);
  // internal crossplatform init (mostly for string reading stuff)
  void internalInit(void);

  // separates out a host string (possibly host:port) into a host and
  // the port that should be used.
  void separateHost(const char *rawHost, int rawPort, char *useHost, 
		    size_t useHostSize, int *port);

  Type myType;
  Error myError;
  std::string myErrorStr;
  bool myDoClose;

#ifdef WIN32
  SOCKET myFD;
#else
  int myFD;
#endif

  bool myNonBlocking;
  struct sockaddr_in mySin;

  bool myLogWriteStrings;
  ArMutex myReadStringMutex;
  ArMutex myWriteStringMutex;
  bool myStringAutoEcho;
  bool myStringEcho;
  bool myStringIgnoreReturn;
  bool myStringWrongEndChars;
  char myStringBuf[1100];
  size_t myStringPos;
  char myStringBufEmpty[1];
  size_t myStringPosLast;
  std::string myIPString;
  char myRawIPString[128];
  ArTime myLastStringReadTime;
  bool myStringGotEscapeChars;
  bool myStringGotComplete;
  bool myStringHaveEchoed;

  long mySends;
  long myBytesSent;
  long myRecvs;
  long myBytesRecvd;

  bool myBadWrite;
  bool myBadRead;
  bool myErrorTracking;

  // A functor to call when the socket closes
  ArFunctor *myCloseFunctor;
};


#endif // ARSOCKET_H
  
