// ---------------------------------------------------------------------------
// - HttpReply.cpp                                                           -
// - afnix:nwg module - http reply 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 "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";

  // this procedure returns true if the header contains a status code that
  // prohibits the existence of a message body
  static bool is_body_valid (const Plist& head) {
    // check if the status property is defined
    if (head.exists (HTTP_STAT_NAME) == false) return true;
    // get the status value and check
    String mesg = head.getpval (HTTP_STAT_NAME);
    if (mesg == HttpProto::mapcode (100)) return false;
    if (mesg == HttpProto::mapcode (101)) return false;
    if (mesg == HttpProto::mapcode (204)) return false;
    if (mesg == HttpProto::mapcode (304)) return false;
    return true;
  }

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

  // set a cookie in the header

  void HttpReply::setcook (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 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;
    }
  }

  // set a http status by code

  void HttpReply::setstatus (const long code) {
    wrlock ();
    try {
      // get the status message
      sethead (HTTP_STAT_NAME, HttpProto::mapcode (code));
      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;
    }
  }

  // write the http reply to an output stream

  void HttpReply:: write (Output& os) const {
    rdlock ();
    try {
      // write the header
      HttpProto::write (os);
      // write the body
      if (is_body_valid (d_head) == true) {
	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 header
      HttpProto::write (buf);
      // write the body
      if (is_body_valid (d_head) == true) {
	buf.add (d_hbuf);
      }
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

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

  // the object supported quarks
  static const long QUARK_SETCOOK   = zone.intern ("set-cookie");
  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 ? HttpProto::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 1 argument
    if (argc == 1) {
      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));
        }
	setcook (*cobj);
	return nilp;
      }
    }
    // call the http proto method
    return HttpProto::apply (robj, nset, quark, argv);
  }
}
