// ---------------------------------------------------------------------------
// - XmlNode.cpp                                                             -
// - afnix:xml module - xml base node 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 "XmlNode.hpp"
#include "Integer.hpp"
#include "Boolean.hpp"
#include "Runnable.hpp"
#include "XmlSystem.hpp"
#include "XmlReader.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"

namespace afnix {

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

  // create an empty node

  XmlNode::XmlNode (void) {
    d_eflg = false;
    p_pnod = nilp;
  }

  // create a node with an empty flag

  XmlNode::XmlNode (const bool eflg) {
    d_eflg = eflg;
    p_pnod = nilp;
  }

  // destroy this node

  XmlNode::~XmlNode (void) {
    Object::dref (p_pnod);
  }

  // return the document class name

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

  // make this document shared

  void XmlNode::mksho (void) {
    if (p_shared != nilp) return;
    Object::mksho ();
    d_chld.mksho  ();
  }

  // get the empty flag

  bool XmlNode::geteflg (void) const {
    rdlock ();
    bool result = d_eflg;
    unlock ();
    return result;
  }

  // set the source line number
  
  void XmlNode::setlnum (const long lnum) {
    wrlock ();
    d_lnum = lnum;
    unlock ();
  }

  // get the source line number

  long XmlNode::getlnum (void) const {
    rdlock ();
    long result= d_lnum;
    unlock ();
    return result;
  }

  // set the node source name
  
  void XmlNode::setsnam (const String& snam) {
    wrlock ();
    d_snam = snam;
    unlock ();
  }

  // get the node source name

  String XmlNode::getsnam (void) const {
    rdlock ();
    String result= d_snam;
    unlock ();
    return result;
  }

  // check if a node name is valid

  bool XmlNode::isname (const String& name) const {
    rdlock ();
    bool result = false;
    unlock ();
    return result;
  }

  // get the parent node

  XmlNode* XmlNode::getparent (void) const {
    rdlock ();
    XmlNode* result = p_pnod;
    unlock ();
    return result;
  }

  // set the parent node

  void XmlNode::setparent (XmlNode* node) {
    wrlock ();
    Object::iref (node);
    Object::dref (p_pnod);
    p_pnod = node;
    unlock ();
  }

  // return true if there is no children

  bool XmlNode::nilchild (void) const {
    rdlock ();
    try {
      bool result = d_chld.empty ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the number of children nodes

  long XmlNode::lenchild (void) const {
    rdlock ();
    try {
      long result = d_chld.length ();
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // append a child node to this node

  void XmlNode::addchild (XmlNode* node) {
    // check for nil first
    if (node == nilp) return;
    // lock, mark parent and append
    wrlock ();
    try {
      if (d_eflg == true) {
	throw Exception ("xml-error", "trying to add node in empty node");
      }
      // set the parent node
      node->setparent (this);
      // append the node
      d_chld.append (node);
      unlock ();
    } catch (...) {
      unlock();
      throw;
    }
  }

  // get a child node by index

  XmlNode* XmlNode::getchild (const long index) const {
    rdlock ();
    try {
      XmlNode* node = dynamic_cast <XmlNode*> (d_chld.get (index));
      unlock ();
      return node;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a child node by name if possible

  XmlNode* XmlNode::getchild (const String& name) const {
    rdlock ();
    try {
      long clen = lenchild ();
      for (long i = 0; i < clen; i++) {
	XmlNode* node = getchild (i);
	if (node == nilp) continue;
	if (node->isname (name) == true) {
	  unlock ();
	  return node;
	}
      }
      unlock ();
      return nilp;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // find a node by name or throw an exception

  XmlNode* XmlNode::lookchild (const String& name) const {
    rdlock ();
    try {
      XmlNode* result = getchild (name);
      if (result == nilp) {
	throw Exception ("lookup-error", "cannot find child node", name);
      }
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // parse a string and add the result as child nodes

  void XmlNode::parse (const String& s) {
    wrlock ();
    try {
      // create a default reader
      XmlReader xmlr;
      // parse the string
      xmlr.parse (s);
      // get the result root node
      XmlRoot* root = xmlr.getroot ();
      // get the child length and copy the nodes
      long nlen = (root == nilp) ? 0 : root->lenchild ();
      for (long i = 0; i < nlen; i++) addchild (root->getchild (i));
      // done
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

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

  // the object supported quarks
  static const long QUARK_WRITE     = zone.intern ("write");
  static const long QUARK_NAMEP     = zone.intern ("name-p");
  static const long QUARK_PARSE     = zone.intern ("parse");
  static const long QUARK_GETPARENT = zone.intern ("get-parent");
  static const long QUARK_SETPARENT = zone.intern ("set-parent");
  static const long QUARK_ADDCHILD  = zone.intern ("add-child");
  static const long QUARK_GETCHILD  = zone.intern ("get-child");
  static const long QUARK_NILCHILDP = zone.intern ("nil-child-p");
  static const long QUARK_LOOKCHILD = zone.intern ("lookup-child");
  static const long QUARK_CHILDLEN  = zone.intern ("child-length");
  static const long QUARK_GETLNUM   = zone.intern ("get-source-line");
  static const long QUARK_SETLNUM   = zone.intern ("set-source-line");
  static const long QUARK_GETSNAM   = zone.intern ("get-source-name");
  static const long QUARK_SETSNAM   = zone.intern ("set-source-name");

  // return true if the given quark is defined

  bool XmlNode::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* XmlNode::apply (Runnable* robj, Nameset* nset, const long quark,
			  Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // check for 0 argument
    if (argc == 0) {
      if (quark == QUARK_CHILDLEN)  return new Integer (lenchild ());
      if (quark == QUARK_GETLNUM)   return new Integer (getlnum  ());
      if (quark == QUARK_GETSNAM)   return new String  (getsnam  ());
      if (quark == QUARK_NILCHILDP) return new Boolean (nilchild ());
      if (quark == QUARK_GETPARENT) {
	rdlock ();
	Object* result = getparent ();
	robj->post (result);
	unlock ();
	return result;
      }
      if (quark == QUARK_WRITE) {
	Output* os = (robj == nilp) ? nilp : robj->getos ();
	if (os == nilp) return nilp;
	write (*os);
	return nilp;
      }
    }
    // check for 1 argument
    if (argc == 1) {
      if (quark == QUARK_NAMEP) {
	String name= argv->getstring (0);
	return new Boolean (isname (name));
      }
      if (quark == QUARK_PARSE) {
	String s = argv->getstring (0);
	parse (s);
	return nilp;
      }
      if (quark == QUARK_SETPARENT) {
	// get the node object
	Object*   obj = argv->get (0);
	XmlNode* node = dynamic_cast <XmlNode*> (obj);
	if ((obj != nilp) && (node == nilp)) {
	  throw Exception ("type-error", "invalid object with set-parent",
			   Object::repr (obj));
	}
	// set the node
	setparent (node);
	return nilp;
      }
      if (quark == QUARK_ADDCHILD) {
	// get the node object
	Object*   obj = argv->get (0);
	XmlNode* node = dynamic_cast <XmlNode*> (obj);
	if ((obj != nilp) && (node == nilp)) {
	  throw Exception ("type-error", "invalid object with add-child",
			   Object::repr (obj));
	}
	// set the node
	addchild (node);
	return nilp;
      }
      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_GETCHILD) {
	Object* obj = argv->get (0);
	// check for an integer
	Integer* iobj = dynamic_cast <Integer*> (obj);
	if (iobj != nilp) {
	  long index = iobj->tointeger ();
	  rdlock ();
	  try {
	    XmlNode* result = getchild (index);
	    robj->post (result);
	    unlock ();
	    return result;
	  } catch (...) {
	    unlock ();
	    throw;
	  }
	}
	// check for a string
	String* sobj = dynamic_cast <String*> (obj);
	if (sobj != nilp) {
	  rdlock ();
	  try {
	    XmlNode* result = getchild (*sobj);
	    robj->post (result);
	    unlock ();
	    return result;
	  } catch (...) {
	    unlock ();
	    throw;
	  }
	}
	throw Exception ("type-error", "invalid object with get-child",
			 Object::repr (obj));
      }
      if (quark == QUARK_LOOKCHILD) {
	String name = argv->getstring (0);
	rdlock ();
	try {
	  XmlNode* result = lookchild (name);
	  robj->post (result);
	  unlock ();
	  return result;
	} catch (...) {
	  unlock ();
	  throw;
	}
      }
      if (quark == QUARK_SETLNUM) {
	long lnum = argv->getint (0);
	setlnum (lnum);
	return nilp;
      }
      if (quark == QUARK_SETSNAM) {
	String snam = argv->getstring (0);
	setsnam (snam);
	return nilp;
      }
    }
    // call the object method
    return Object::apply (robj, nset, quark, argv);
  }
}
