/*
 * File: nstream.hh
 * Author: Keith Schwarz (htiek@cs.stanford.edu)
 * --------------------------------------------------
 * This file is the interface for a stream object that lets you
 * send and receive data across a network connection.  The class,
 * nstream, is similar to the ifstream and ofstream classes,
 * except that it allows for both reading and writing.
 *
 * To use this class, you must first create an instance of an
 * nstream object by declaring
 *
 *      nstream myStream;
 *
 * At this point, you need to initialize the network connection.
 * To do this, you will either need to tell the stream to wait for
 * an incoming connection from a remote computer, or will need to
 * initiate a connection to a remote computer.
 *
 * To wait for an incoming connection, you use the open function,
 * which accepts as input a "port number."  This number is an
 * application-specific value that lets your program filter out
 * other incoming requests for other programs.  For example, if you
 * listen for incoming connections on port 1337, then other
 * applications will have to make a connection on port 1337 in order
 * for the nstream to receive the transmission.  Port numbers are
 * stored as shorts, so their values range between 0 and 32767.
 * Some ports are commonly used for particular applications,
 * for example:
 *
 *    - Ports 20 / 21: FTP (File transfer)
 *    - Port 25: SMTP (Sending email)
 *    - Port 80: HTTP (Web)
 *    - Port 143: IMAP (Receiving email)
 *    - Port 194: IRC (Chat)
 *    - Port 443: HTTPS (Secure web)
 *
 * For example, to have the stream listen on port 1337 for an
 * incoming connection, you would write
 *
 *      myStream.open(1337);
 *
 * The call to open will wait indefinitely for an incoming
 * connection and will return only when a connection has been
 * received or if a network error has occurred.  You can use
 * the is_open() or fail() methods to check if the
 * operation succeeded.  For example:
 *
 *    nstream myStream;
 *    myStream.open(1337); // Wait for connection on port 1337
 *    if(!myStream.is_open()) { .. handle error .. }
 *
 * If you'd like to use the nstream to connect to a remote
 * computer, you use a different version of the open function
 * which accepts as input two parameters, a host name and a
 * port number.  The port number is used as described above
 * to make sure that the right application on the receiving
 * machine accepts the connection.  The host name can either
 * be an IP address (e.g. "137.42.31.27") or the name of a
 * computer on the Internet (e.g. "www.google.com").
 *
 * Like the other version of "open," the two-argument version
 * of "open" will return only after a connection has been
 * established or the connection failed.  Use is_open() and
 * fail() to check for errors.
 *
 * Here's some sample code to illustrate how to make a
 * connection.  This opens up a connection to www.google.com
 * on port 80 (HTTP), and could be used in a program that does
 * web searches automatically:
 *
 *    nstream myStream;
 *    myStream.open("www.google.com", 80);
 *    if(!myStream.is_open()) { .. error .. }
 *
 * Once you have established the connection, you can use the
 * nstream as you would any other stream class (e.g. ifstream,
 * cout, ofstream).  Any data you transmit will be sent across
 * the connection.  When reading data, the stream will wait for
 * the remote machine to send data before returning.  This means
 * that you should be careful not to read any data unless you
 * are sure that the remote machine has sent something.
 *
 * A minor point to be aware of is that the nstream class is
 * buffered and thus may not immediately send the data that is
 * passed into it.  To ensure that the data you write is sent,
 * make sure to flush the stream using the "endl" manipulator
 * or the flush() method, as shown here:
 *
 * myStream << "This is some text." << endl;
 *
 *                   -or-
 *
 * myStream << "This is some text!";
 * myStream.flush();
 *
 * When using the nstream object, be sure to periodically check
 * that the stream is not in a fail state by using the .fail()
 * method.  Because the network connection might fail
 * at any point, you should never assume that any read or write
 * has completed successfully and should be prepared to handle
 * errors gracefully.
 */


#ifndef NStream_Included
#define NStream_Included

#include <iostream>
#include <string>

class nstream: public std::iostream {
public:
  /* Constructor: nstream()
   * Usage: nstream myStream;
   * --------------------------------
   * Creates a new nstream object that is not connected to any
   * computer.  Use one of the open() functions to connect to
   * another computer.
   */

  nstream();

  /* Destructor: ~nstream()
   * Usage: (implicit)
   * --------------------------------
   * Closes the connection and shuts down the stream.
   */

  ~nstream();

  /* Constructor: nstream(short portNum)
   * Usage: nstream myStream(137);
   * -----------------------------------------
   * Creates a new nstream object that waits on the specified
   * port for an incoming connection.  This is equivalent to
   * creating an empty nstream and then calling .open() and is
   * provided as a convenience.
   */

  explicit nstream(short portNum);

  /* Constructor: nstream(string hostName, short portNum)
   * Usage: nstream myStream("127.0.0.1", 137);
   * -----------------------------------------
   * Creates a new nstream object that connects to the specified
   * computer on the specified port.  This is equivalent to
   * creating an empty nstream and then calling .open() and is
   * provided as a convenience.
   */

  nstream(std::string hostName, short portNum);

  /* Function: open(string hostName, short portNum)
   * Usage: myStream.open("127.0.0.1", 137);
   * ------------------------------------------
   * Opens a connection to the specified computer on the given port.
   * If a connection already exists or the connection fails, puts the
   * stream into a fail state.
   */

  void open(std::string hostName, short portNum);

  /* Function: open(short portNum)
   * Usage: myStream.open(137);
   * ------------------------------------------
   * Waits for a computer to connect to the specified port.
   * If a connection already exists or the connection fails, puts the
   * stream into a fail state.
   */

  void open(short portNum);

  /* Function: is_open()
   * Usage: if(!myStream.is_open()) { .. error .. }
   * ----------------------------------------------
   * Returns whether a valid connection exists to the
   * remote machine.
   */

  bool is_open() const;

  /* Function: close()
   * Usage: myStream.close();
   * -----------------------------------------------
   * Closes any open connections and flushes unsent
   * data.  If the stream is not open, puts the stream
   * into a fail state.
   */

  void close();

  /* Because this is a stream class, all of the stream operations
   * you're familiar with are also part of nstream.  For example,
   * you can read data with >> or getline, and write data with <<.
   *
   * As an example:
   *
   * nstream networkConnection;
   * // ... set up connection ... //
   *
   * string line;
   * getline(networkConnection, line); // Read a line of text from the
   *                                   // other computer
   *
   * networkConnection << "Message received." << endl; // Send a line of text
   *                                                   // to the other computer
   */


private:
  /* Forward declaration of the streambuf object that will be used to
   * actually maintain the connection.
   */

  class SocketStreambuf;

  /* This stream buffer is used for the actual connection. */
  SocketStreambuf* connection;
};

#endif