// ---------------------------------------------------------------------------
// - HttpReply.cpp                                                           -
// - afnix:wam module - http replay class implementation                     -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2006 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Vector.hpp"
#include "Utility.hpp"
#include "Runnable.hpp"
#include "HttpReply.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the content type header as a mime property
  static const String HTTP_MIME_NAME = "Content-Type";
  static const String HTTP_MIME_PVAL = "text/plain";

  // the http location name
  static const String HTTP_RLOC_NAME = "Location";
  // the http cookie version 0 name
  static const String HTTP_COK0_NAME = "Set-Cookie";
  // the http cookie version 1 name
  static const String HTTP_COK1_NAME = "Set-Cookie2";

  // the http status name
  static const String HTTP_STAT_NAME = "Status";
  // the http status code
  static const String HTTP_STAT_C100 = "100 Continue";
  static const String HTTP_STAT_C101 = "101 Switching Protocols";
  static const String HTTP_STAT_C200 = "200 OK";
  static const String HTTP_STAT_C201 = "201 Created";
  static const String HTTP_STAT_C202 = "202 Accepted";
  static const String HTTP_STAT_C203 = "203 Non-Authoritative Information";
  static const String HTTP_STAT_C204 = "204 No Content";
  static const String HTTP_STAT_C205 = "205 Reset Content";
  static const String HTTP_STAT_C206 = "206 Partial Content";
  static const String HTTP_STAT_C300 = "300 Multiple Choices";
  static const String HTTP_STAT_C301 = "301 Moved Permanently";
  static const String HTTP_STAT_C302 = "302 Found";
  static const String HTTP_STAT_C303 = "303 See Other";
  static const String HTTP_STAT_C304 = "304 Not Modified";
  static const String HTTP_STAT_C305 = "305 Use Proxy";
  static const String HTTP_STAT_C307 = "307 Temporary Redirect";
  static const String HTTP_STAT_C400 = "400 Bad Request";
  static const String HTTP_STAT_C401 = "401 Unauthorized";
  static const String HTTP_STAT_C402 = "402 Payment Required";
  static const String HTTP_STAT_C403 = "403 Forbidden";
  static const String HTTP_STAT_C404 = "404 Not Found";
  static const String HTTP_STAT_C405 = "405 Method Not Allowed";
  static const String HTTP_STAT_C406 = "406 Not Acceptable";
  static const String HTTP_STAT_C407 = "407 Proxy Authentication Required";
  static const String HTTP_STAT_C408 = "408 Request Time-out";
  static const String HTTP_STAT_C409 = "409 Conflict";
  static const String HTTP_STAT_C410 = "410 Gone";
  static const String HTTP_STAT_C411 = "411 Length Required";
  static const String HTTP_STAT_C412 = "412 Precondition Failed";
  static const String HTTP_STAT_C413 = "413 Request Entity Too Large";
  static const String HTTP_STAT_C414 = "414 Request-URI Too Large";
  static const String HTTP_STAT_C415 = "415 Unsupported Media Type";
  static const String HTTP_STAT_C416 = "416 Requested range not satisfiable";
  static const String HTTP_STAT_C417 = "417 Expectation Failed";
  static const String HTTP_STAT_C500 = "500 Internal Server Error";
  static const String HTTP_STAT_C501 = "501 Not Implemented";
  static const String HTTP_STAT_C502 = "502 Bad Gateway";
  static const String HTTP_STAT_C503 = "503 Service Unavailable";
  static const String HTTP_STAT_C504 = "504 Gateway Time-out";
  static const String HTTP_STAT_C505 = "505 HTTP Version not supported";

  // this procecude returns the status message by code
  static String get_status_mesg (const long code) {
    switch (code) {
    case 100: return HTTP_STAT_C100;
    case 101: return HTTP_STAT_C101;
    case 200: return HTTP_STAT_C200;
    case 201: return HTTP_STAT_C201;
    case 202: return HTTP_STAT_C202;
    case 203: return HTTP_STAT_C203;
    case 204: return HTTP_STAT_C204;
    case 205: return HTTP_STAT_C205;
    case 206: return HTTP_STAT_C206;
    case 300: return HTTP_STAT_C300;
    case 301: return HTTP_STAT_C301;
    case 302: return HTTP_STAT_C302;
    case 303: return HTTP_STAT_C303;
    case 304: return HTTP_STAT_C304;
    case 305: return HTTP_STAT_C305;
    case 307: return HTTP_STAT_C307;
    case 400: return HTTP_STAT_C400;
    case 401: return HTTP_STAT_C401;
    case 402: return HTTP_STAT_C402;
    case 403: return HTTP_STAT_C403;
    case 404: return HTTP_STAT_C404;
    case 405: return HTTP_STAT_C405;
    case 406: return HTTP_STAT_C406;
    case 407: return HTTP_STAT_C407;
    case 408: return HTTP_STAT_C408;
    case 409: return HTTP_STAT_C409;
    case 410: return HTTP_STAT_C410;
    case 411: return HTTP_STAT_C411;
    case 412: return HTTP_STAT_C412;
    case 413: return HTTP_STAT_C413;
    case 414: return HTTP_STAT_C414;
    case 415: return HTTP_STAT_C415;
    case 416: return HTTP_STAT_C416;
    case 417: return HTTP_STAT_C417;
    case 500: return HTTP_STAT_C500;
    case 501: return HTTP_STAT_C501;
    case 502: return HTTP_STAT_C502;
    case 503: return HTTP_STAT_C503;
    case 504: return HTTP_STAT_C504;
    case 505: return HTTP_STAT_C505;
    default:
      break;
    }
    throw Exception ("http-error", "invalid http status code", 
		     Utility::tostring (code));
  }

  // this procedure format a property into a string header
  static String get_header_string (const Property& prop) {
    // get the property name
    String result = prop.getname ();
    // add separator
    result += ": ";
    // add value
    result += prop.getpval ();
    // here we are
    return result;
  }

  // this procedure write a property list to an output stream
  static void write_plist (const Plist& plist, Output& os) {
    long plen = plist.length ();
    for (long i = 0; i < plen; i++) {
      // get the property
      Property* prop = plist.get (i);
      if (prop == nilp) continue;
      // format the string and write
      os.writeln (get_header_string (*prop));
    }
    os.newline ();
  }

  // this procedure write a property list to a buffer
  static void write_plist (const Plist& plist, Buffer& buf) {
    long plen = plist.length ();
    for (long i = 0; i < plen; i++) {
      // get the property
      Property* prop = plist.get (i);
      if (prop == nilp) continue;
      // format the string and write
      buf.add (get_header_string (*prop));
      buf.add (eolc);
    }
    buf.add (eolc);
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create an empty http reply

  HttpReply::HttpReply (void) {
    // add the content-type property
    sethead (HTTP_MIME_NAME, HTTP_MIME_PVAL);
  }

  // create an empty http reply with a type

  HttpReply::HttpReply (const String& type) {
    sethead (HTTP_MIME_NAME, type);
  }

  // return the class name

  String HttpReply::repr (void) const {
    return "HttpReply";
  }

  // add a cookie in the header

  void HttpReply::sethead (const Cookie& cook) {
    wrlock ();
    try {
      if (cook.getvers () == 0) {
	sethead (HTTP_COK0_NAME, cook.tostring ());
      } else if (cook.getvers () == 1) {
	sethead (HTTP_COK1_NAME, cook.tostring ());
      } else {
	throw Exception ("http-error", "invalid cookie version");
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a header property

  void HttpReply::sethead (const String& name, const Literal& lval) {
    wrlock ();
    try {
      d_head.set (name, lval.tostring ());
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a string in the http buffer

  void HttpReply::addhbuf (const Literal& lval) {
    wrlock ();
    try {
      d_hbuf.add (lval.tostring ());
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a mime document in the http buffer

  void HttpReply::addhbuf (const Mime& mime) {
    wrlock ();
    try {
      // get he mime type
      String type = mime.getmime ();
      sethead (HTTP_MIME_NAME, type);
      // while the mime dcoument to the http buffer
      mime.write (d_hbuf);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // add a buffer in the http buffer

  void HttpReply::addhbuf (const Buffer& buf) {
    wrlock ();
    try {
      d_hbuf.add (buf);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // write the http reply to an output stream

  void HttpReply:: write (Output& os) const {
    rdlock ();
    try {
      // write the plist
      write_plist (d_head, os);
      // write the buffer
      d_hbuf.write (os);
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // write the http reply to a buffer

  void HttpReply:: write (Buffer& buf) const {
    rdlock ();
    try {
      // write the plist
      write_plist (d_head, buf);
      // write the buffer
      buf.add (d_hbuf);
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set a http status by code

  void HttpReply::setstatus (const long code) {
    wrlock ();
    try {
      // get the status message
      String mesg = get_status_mesg (code);
      sethead (HTTP_STAT_NAME, mesg);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // redirect a to a certain location

  void HttpReply::redirect (const String& url) {
    wrlock ();
    try {
      // set a status 303 (see other)
      setstatus (303);
      // set the redirection location
      sethead (HTTP_RLOC_NAME, url);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 5;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the object supported quarks
  static const long QUARK_WRITE     = zone.intern ("write");
  static const long QUARK_SETCOOK   = zone.intern ("set-cookie");
  static const long QUARK_SETHEAD   = zone.intern ("set-header");
  static const long QUARK_ADDHBUF   = zone.intern ("add-buffer");
  static const long QUARK_REDIRECT  = zone.intern ("redirect");
  static const long QUARK_SETSTATUS = zone.intern ("set-status");

  // create a new object in a generic way

  Object* HttpReply::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    // check for 0 argument
    if (argc == 0) return new HttpReply;
    //check for 1 argument
    if (argc == 1) {
      String type = argv->getstring (0);
      return new HttpReply (type);
    }
    // wrong arguments
    throw Exception ("argument-error", 
		     "too many arguments with http reply constructor");
  }

  // return true if the given quark is defined

  bool HttpReply::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true) {
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark

  Object* HttpReply::apply (Runnable* robj, Nameset* nset, const long quark,
			    Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_WRITE) {
	Output* os = (robj == nilp) ? nilp : robj->getos ();
	if (os == nilp) return nilp;
	write (*os);
	return nilp;
      }
    }

    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_WRITE) {
	Object* obj = argv->get (0);
	// check for an output stream
	Output* os = dynamic_cast <Output*> (obj);
	if (os != nilp) {
	  write (*os);
	  return nilp;
	}
	// check for a buffer
	Buffer* buf = dynamic_cast <Buffer*> (obj);
	if (buf != nilp) {
	  write (*buf);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object with write",
			 Object::repr (obj));
      }
      if (quark == QUARK_ADDHBUF) {
	Object* obj = argv->get (0);
	// check for a literal
	Literal* lobj = dynamic_cast <Literal*> (obj);
	if (lobj != nilp) {
	  addhbuf (*lobj);
	  return nilp;
	}
	// check for a buffer
	Buffer* buf = dynamic_cast <Buffer*> (obj);
	if (buf != nilp) {
	  addhbuf (*buf);
	  return nilp;
	}
	// check for a mime document
	Mime* mobj = dynamic_cast <Mime*> (obj);
	if (mobj != nilp) {
	  addhbuf (*mobj);
	  return nilp;
	}
	throw Exception ("type-error", "invalid object with add-buffer",
			 Object::repr (obj));
      }
      if (quark == QUARK_REDIRECT) {
	String url = argv->getstring (0);
	redirect (url);
	return nilp;
      }
      if (quark == QUARK_SETSTATUS) {
	long code = argv->getint (0);
	setstatus (code);
	return nilp;
      }
      if (quark == QUARK_SETCOOK) {
        Object*   obj = argv->get (0);
        Cookie*  cobj = dynamic_cast <Cookie*> (obj);
        if (cobj == nilp) {
          throw Exception ("type-error", "invalid object with set-cookie",
                           Object::repr (obj));
        }
	sethead (*cobj);
	return nilp;
      }
    }
    // dispatch 2 argument
    if (argc == 2) {
      if (quark == QUARK_SETHEAD) {
	String   name = argv->getstring (0);
        Object*   obj = argv->get (1);
        Literal* lobj = dynamic_cast <Literal*> (obj);
        if (lobj == nilp) {
          throw Exception ("type-error", "invalid object with set-header",
                           Object::repr (obj));
        }
	sethead (name, *lobj);
	return nilp;
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
