// ---------------------------------------------------------------------------
// - HttppProto.cpp                                                          -
// - afnix:nwg module - http protocol 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-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

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

namespace afnix {

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

  // 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 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;
  }

  // -------------------------------------------------------------------------
  // - public section                                                         -
  // -------------------------------------------------------------------------

  // map a http status code to a message

  String HttpProto::mapcode (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));
  }

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

  // add a header property

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

  // write the http header to the output stream

  void HttpProto:: write (Output& os) const {
    rdlock ();
    try {
      long hlen = d_head.length ();
      for (long i = 0; i < hlen; i++) {
	// get the property
	Property* prop = d_head.get (i);
	if (prop == nilp) continue;
	// format the string and write
	os.writeln (get_header_string (*prop), true);
      }
      os.newline (true);
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // write the http reply to a buffer

  void HttpProto:: write (Buffer& buf) const {
    rdlock ();
    try {
      long hlen = d_head.length ();
      for (long i = 0; i < hlen; i++) {
	// get the property
	Property* prop = d_head.get (i);
	if (prop == nilp) continue;
	// format the string and write
	buf.add (get_header_string (*prop));
	buf.add (crlc);
	buf.add (eolc);
      }
      buf.add (crlc);
      buf.add (eolc);
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

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

  // the object supported quarks
  static const long QUARK_WRITE   = zone.intern ("write");
  static const long QUARK_SETHEAD = zone.intern ("set-header");

  // return true if the given quark is defined

  bool HttpProto::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* HttpProto::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));
      }
    }
    // 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);
  }
}
