/****************************************************************************
 *  simple_web_server.cpp                                                   *
 *   JRogers, Feb 2003                                                      *
 *   trivial single threaded web server                                     *
 *   based on C++ Socket class                                              *
 ****************************************************************************/
#include "Socket.h"
#include "SocketException.h"
#include <string>
#include <iostream>
#include <fstream>

using namespace std;

const int BSIZE_MAX = 1000;   // Buffer size

/****************************************************************************
 *  Filex class                                                             *
 *   file transfer control                                                  *
 ****************************************************************************/
class Filex
{
public:
  /// Constructors
  // Default --- can be intialized with open
  Filex( TCPDataSocket* socket )
  {
    _sockp = socket;
    _fn = _ct = _hdr = "";
    _sz = _pos = 0;
    _ok = _done = false;
  }

  // Initializing constructor
  //  If hdr != "" then hdr contains initial portion of response header
  //     open will add "Content type:" and "Content length:" lines
  //     and will terminate header
  //  If hdr == "" open will construct entire header 
  Filex( TCPDataSocket* socket, const string& fname, const string& hdr = "" )
  {
    _sockp = socket;
    _ok =  open( fname, hdr);
  }

  // Open --- opens file and initializes transfer data
  //  Does not send any data
  bool open( const string& fname, const string& hdr );

  // ok() returns status
  bool ok() const { return _ok; }

  // done() --- true iff transfer completed or status not ok
  bool done() const { return _done || ! _ok; }
  
  // send_header() --- send header
  void send_header()
  {
    if ( !_hsent )
      _sockp->send(_hdr);
    _hsent = true;
  }

  // send() --- send next block
  bool send();

private:
  TCPDataSocket* _sockp;  // Data socket
  ifstream _f;            // File input stream
  string _fn;             // File name
  string _ct;             // Content type
  size_t _sz;             // File size
  string _hdr;            // Header
  size_t _pos;            // current position
  bool _ok;               // valid
  bool _hsent;            // true if header has been sent
  bool _done;             // finished 
  char _buf[BSIZE_MAX];   // Buffer
};
  
/****************************************************************************
 * main                                                                     *
 ****************************************************************************/
int main ( int argc, int argv[] )
{
  cout << "running....\n";

  try
    {
      // Create the socket
      TCPServerSocket server( 50000 );

      while ( server.is_valid() )  // loops forever if server opened OK
	{
	  try
	    {
              // Accept a data connection
              TCPDataSocket* d_sockp = server.accept();

              if ( d_sockp != NULL )
                {
                  cout << "Connection from : " << d_sockp->addr() 
                       << " Port: " << d_sockp->port() << endl;

                  // Get request
                  istringstream& req = d_sockp->recv();
                  if ( req )
                    {
                      cout << "Request is: |" << req.str() << "|" << endl;
                      // Parse out operation and filename
                      string op;
                      string filename;
                      req >> op >> filename;
                      cout << "op = |" << op << "|" << endl;
                      cout << " filename= |" << filename << "|" << endl;

                      string header("");
                      if ( op != "GET" || filename.size() == 0 )
                        {
                          header = 
                            "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n";
                          filename = "400.html";
                        }

                      // Build file transfer object
                      Filex f( d_sockp, filename, header);
                      // Send header
                      if ( f.ok() )
                        f.send_header();
                      // Send body
                      while ( ! f.done() )
                        {
                          f.send();
                        }
                    }
                }
              // Discard data socket
              delete d_sockp;
            }
          catch ( SocketException& ex) 
            {
              cerr << ex.description() << "\nContinuing..." << endl;
            }
        }
    }
  catch ( SocketException& ex )
    {
      cout << ex.description() << "\nExiting.\n";
    }
  
  exit( EXIT_FAILURE );
}

/****************************************************************************
 *  Implementation of Filex class                                           *
 ****************************************************************************/
  
// Open --- opens file and initializes transfer 
//  Does not send any data
bool Filex::open( const string& fname, const string& hdr )
{
  ostringstream head;  // Collects header
  
  head << hdr;

  _fn = fname;
  _f.open(_fn.c_str() );
  _ok = static_cast<bool>(_f);

  if ( !_ok && hdr == "" )
    {
      head << "HTTP/1.0 404 Not Found\r\nConnection: close\r\n";
      _fn = "404.html";
      _f.open(_fn.c_str());
      if ( !_f )
        {
          cerr << "Cannot find " << fname << " _OR_ " << _fn << endl;
          exit(EXIT_FAILURE);
        }
    }
  else if ( hdr == "" )
    {
      head << "HTTP/1.0 200 Document Follows\r\nConnection: close\r\n";
    }
  
  // Get content type
  string ext("");
  string::size_type dot = _fn.rfind(".");
  if ( dot != string::npos )
    ext = _fn.substr(dot+1);
  if ( ext == "gif" || ext == "jpeg" || ext == "png" )
    _ct = "image/" + ext;
  else if ( ext == "html" || ext == "htm" || ext == "shtml" )
    _ct = "text/html";
  else
    _ct = "text/plain";
  head << "Content-Type: " << _ct << "\r\n";
  
  // Get file length
  _sz = _f.seekg(0, ios::end).tellg();
  _f.seekg(0, ios::beg);
  head << "Content-Length: " << _sz+2 << "\r\n";
  // Terminate header
  head << "\r\n";

  // 
  _hdr = head.str();
  _pos = 0;
  _done = _hsent = false;
  _ok = static_cast<bool>(_f);
  
  return _ok;
}

// send --- send next block
bool Filex::send()
{
  if ( _pos < _sz )
    _f.read( _buf, BSIZE_MAX );
  int nread = _f.gcount();
  if ( nread > 0 )
    _sockp->send( _buf, nread );
  cout << "Sent " << nread << " bytes" << endl;

  _pos += nread;
  _done = ( _pos == _sz );

  _ok = static_cast<bool>(_f);
  return (_ok);
}

