// ---------------------------------------------------------------------------
// - Uri.cpp                                                                 -
// - afnix:www module - uri 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 "Uri.hpp"
#include "Regex.hpp"
#include "Vector.hpp"
#include "Buffer.hpp"
#include "Unicode.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // escape a uri name by converting the special characters

  String Uri::escape (const String& name) {
    long len = name.length ();
    if (len == 0) return name;
    // create a buffer and loop
    Buffer buf;
    for (long i = 0; i < len; i++) {
      t_quad c = name[i];
      if (c == Unicode::toquad ('+')) {
	buf.add (' ');
	continue;
      }
      if (c == Unicode::toquad ('%')) {
	t_byte val = 0;
	if (++i < len) val  = Unicode::htob (name[i]) << 4;
	if (++i < len) val += Unicode::htob (name[i]);
	if (i >= len) {
	  throw Exception ("uri-error", "unterminated escape sequence");
	}
	buf.add ((char) val);
	continue;
      }
      buf.add (c);
    }
    return buf.tostring ();
  }

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

  // create a default uri

  Uri::Uri (void) {
    reset ();
  }

  // create a uri by name

  Uri::Uri (const String& uri) {
    reset ();
    parse (uri);
  }

  // copy constructs this uri

  Uri::Uri (const Uri& that) {
    that.rdlock ();
    d_scheme   = that.d_scheme;
    d_auth     = that.d_auth;
    d_path     = that.d_path;
    d_query    = that.d_query;
    d_fragment = that.d_fragment;
    that.unlock ();
  }

  // asssign an uri to this one

  Uri& Uri::operator = (const Uri& that) {
    wrlock ();
    that.rdlock ();
    d_scheme   = that.d_scheme;
    d_auth     = that.d_auth;
    d_path     = that.d_path;
    d_query    = that.d_query;
    d_fragment = that.d_fragment;
    that.unlock ();
    unlock ();
    return *this;
  }

  // return the class name

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

  // reset an uri information

  void Uri::reset (void) {
    wrlock ();
    d_scheme   = "";
    d_auth     = "";
    d_path     = "";
    d_query    = "";
    d_fragment = "";
    unlock ();
  }

  // parse an uri and update the uri data structure

  void Uri::parse (const String& uri) {
    // check for nothing first
    long len = uri.length ();
    if (len == 0) {
      reset ();
      return;
    }
    // ok let's deal with it
    wrlock ();
    reset  ();
    try {
      // the reminding string
      String rest;
      // extract the scheme
      Regex rsch ("($l$a*):(/+$N*)");
      if (rsch == uri) {
	d_scheme = rsch.getstr (0);
	rest     = rsch.getstr (1);
      } else {
	rest = uri;
      }
      // now extract the  authority
      Regex raut ("//(<$a+-._:>+)(</?#>?$N*)");
      if (raut == rest) {
	d_auth = raut.getstr (0);
	rest   = raut.getstr (1);
      }
      // now extract the path
      Regex rpth ("(/<$a+-_.%/>*)(<?#>?$N*)");
      if (rpth == rest) {
	d_path = Uri::escape (rpth.getstr (0));
	rest   = rpth.getstr (1);
      }
      // now extract the query
      Regex rqry ("$?(<$a+-_.%/=>*)(<$#>?$N*)");
      if (rqry == rest) {
	d_query = rqry.getstr (0);
	rest    = rqry.getstr (1);
      }
      // now extract the fragment
      Regex rfrg ("$#($N*)");
      if (rfrg == rest) {
	d_fragment = Uri::escape (rfrg.getstr (0));
	rest = "";
      }
      if (rest.length () != 0) {
	throw Exception ("uri-error", "incorrect uri format", uri);
      }
    } catch (...) {
      reset  ();
      unlock ();
      throw;
    }
  }

  // return the uri scheme
  
  String Uri::getscheme (void) const {
    rdlock ();
    String result = d_scheme;
    unlock ();
    return result;
  }

  // return the uri authority
  
  String Uri::getauth (void) const {
    rdlock ();
    String result = d_auth;
    unlock ();
    return result;
  }

  // return the uri path
  
  String Uri::getpath (void) const {
    rdlock ();
    String result = d_path;
    unlock ();
    return result;
  }

  // return the uri query
  
  String Uri::getquery (void) const {
    rdlock ();
    String result = d_query;
    unlock ();
    return result;
  }

  // return the uri fragment
  
  String Uri::getfragment (void) const {
    rdlock ();
    String result = d_fragment;
    unlock ();
    return result;
  }

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

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

  // the uri supported quarks
  static const long QUARK_PARSE       = zone.intern ("parse");
  static const long QUARK_GETAUTH     = zone.intern ("get-authority");
  static const long QUARK_GETPATH     = zone.intern ("get-path");
  static const long QUARK_GETQUERY    = zone.intern ("get-query");
  static const long QUARK_GETSCHEME   = zone.intern ("get-scheme");
  static const long QUARK_GETFRAGMENT = zone.intern ("get-fragment");

  // create a new object in a generic way

  Object* Uri::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();

    // check for 0 argument
    if (argc == 0) return new Uri;
    // check for 1 argument
    if (argc == 1) {
      String uri = argv->getstring (0);
      return new Uri (uri);
    }
    // invalid arguments
    throw Exception ("argument-error", "invalid arguments with uri object");
  }

  // return true if the given quark is defined

  bool Uri::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* Uri::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_GETAUTH)     return new String  (getauth     ());
      if (quark == QUARK_GETPATH)     return new String  (getpath     ());
      if (quark == QUARK_GETQUERY)    return new String  (getquery    ());
      if (quark == QUARK_GETSCHEME)   return new String  (getscheme   ());
      if (quark == QUARK_GETFRAGMENT) return new String  (getfragment ());
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_PARSE) {
	parse (argv->getstring (0));
	return nilp;
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
