
/* Ekiga -- A VoIP and Video-Conferencing application
 * Copyright (C) 2000-2009 Damien Sandras <dsandras@seconix.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *
 * Ekiga is licensed under the GPL license and as a special exception,
 * you have permission to link or otherwise combine this program with the
 * programs OPAL, OpenH323 and PWLIB, and distribute the combination,
 * without applying the requirements of the GNU GPL to the OPAL, OpenH323
 * and PWLIB programs, as long as you do follow the requirements of the
 * GNU GPL for all the rest of the software thus combined.
 */


/*
 *                         rl-heap.cpp  -  description
 *                         ------------------------------------------
 *   begin                : written in 2008 by Julien Puydt
 *   copyright            : (c) 2008 by Julien Puydt
 *   description          : resource-list heap implementation
 *
 */

#include <glib/gi18n.h>

#include "robust-xml.h"
#include "form-request-simple.h"
#include "xcap-core.h"

#include "rl-heap.h"

RL::Heap::Heap (Ekiga::ServiceCore& services_,
		boost::shared_ptr<xmlDoc> doc_,
		xmlNodePtr node_):
  services(services_),
  node(node_), name(NULL),
  root(NULL), user(NULL),
  username(NULL), password(NULL),
  doc(doc_), list_node(NULL)
{
  {
    xmlChar* xml_str = NULL;

    xml_str = xmlGetProp (node, BAD_CAST "writable");
    if (xml_str != NULL)
      xmlFree (xml_str);
    else {
      xmlSetProp (node, BAD_CAST "writable", BAD_CAST "0");
    }
  }

  for (xmlNodePtr child = node->children; child != NULL; child = child->next) {

    if (child->type == XML_ELEMENT_NODE
	&& child->name != NULL) {

      if (xmlStrEqual (BAD_CAST ("name"), child->name)) {

	name = child;
	continue;
      }
      if (xmlStrEqual (BAD_CAST ("root"), child->name)) {

	root = child;
	continue;
      }
      if (xmlStrEqual (BAD_CAST ("user"), child->name)) {

	user = child;
	continue;
      }
      if (xmlStrEqual (BAD_CAST ("username"), child->name)) {

	username = child;
	continue;
      }
      if (xmlStrEqual (BAD_CAST ("password"), child->name)) {

	password = child;
	continue;
      }
    }
  }

  if (name == NULL)
    name = xmlNewChild (node, NULL, BAD_CAST "name",
			BAD_CAST robust_xmlEscape(doc.get (),
						  _("Unnamed")).c_str ());
  if (root == NULL)
    root = xmlNewChild (node, NULL, BAD_CAST "root", BAD_CAST "");
  if (user == NULL)
    user = xmlNewChild (node, NULL, BAD_CAST "user", BAD_CAST "");
  if (username == NULL)
    username = xmlNewChild (node, NULL, BAD_CAST "username", BAD_CAST "");
  if (password == NULL)
    password = xmlNewChild (node, NULL, BAD_CAST "password", BAD_CAST "");

  refresh ();
}

RL::Heap::Heap (Ekiga::ServiceCore& services_,
		boost::shared_ptr<xmlDoc> doc_,
		const std::string name_,
		const std::string root_,
		const std::string user_,
		const std::string username_,
		const std::string password_,
		bool writable_):
  services(services_),
  node(NULL), name(NULL),
  root(NULL), user(NULL),
  username(NULL), password(NULL),
  doc(doc_), list_node(NULL)
{
  node = xmlNewNode (NULL, BAD_CAST "entry");
  if (writable_)
    xmlSetProp (node, BAD_CAST "writable", BAD_CAST "1");
  else
    xmlSetProp (node, BAD_CAST "writable", BAD_CAST "0");

  if ( !name_.empty ())
    name = xmlNewChild (node, NULL,
			BAD_CAST "name",
			BAD_CAST robust_xmlEscape (node->doc,
						   name_).c_str ());
  else
    name = xmlNewChild (node, NULL,
			BAD_CAST "name",
			BAD_CAST robust_xmlEscape (node->doc,
						   _("Unnamed")).c_str ());
  root = xmlNewChild (node, NULL,
		      BAD_CAST "root",
		      BAD_CAST robust_xmlEscape (node->doc,
						 root_).c_str ());
  user = xmlNewChild (node, NULL,
		      BAD_CAST "user",
		      BAD_CAST robust_xmlEscape (node->doc,
						 user_).c_str ());
  username = xmlNewChild (node, NULL,
			  BAD_CAST "username",
			  BAD_CAST robust_xmlEscape (node->doc,
						     username_).c_str ());
  password = xmlNewChild (node, NULL,
			  BAD_CAST "password",
			  BAD_CAST robust_xmlEscape (node->doc,
						     password_).c_str ());
  refresh ();
}

RL::Heap::~Heap ()
{
}

const std::string
RL::Heap::get_name () const
{
  std::string result;
  xmlChar* str = xmlNodeGetContent (name);
  if (str != NULL)
    result = (const char*)str;
  else
    result = _("Unnamed");

  xmlFree (str);

  return result;
}

void
RL::Heap::visit_presentities (boost::function1<bool, Ekiga::PresentityPtr > visitor) const
{
  bool go_on = true;

  for (std::map<PresentityPtr,std::list<boost::signals::connection> >::const_iterator
	 iter = presentities.begin ();
       go_on && iter != presentities.end ();
       ++iter)
    go_on = visitor (iter->first);
}

bool
RL::Heap::populate_menu (Ekiga::MenuBuilder& builder)
{
  builder.add_action ("add", _("_Add a new contact"),
		      boost::bind (&RL::Heap::new_entry, this));
  builder.add_action ("refresh", _("_Refresh contact list"),
		      boost::bind (&RL::Heap::refresh, this));
  builder.add_action ("properties", _("Contact list _properties"),
		      boost::bind (&RL::Heap::edit, this));
  return true;
}

bool
RL::Heap::populate_menu_for_group (std::string /*group*/,
				   Ekiga::MenuBuilder& /*builder*/)
{
  return false; // FIXME
}

xmlNodePtr
RL::Heap::get_node () const
{
  return node;
}

void
RL::Heap::refresh ()
{
  boost::shared_ptr<XCAP::Core> xcap = services.get<XCAP::Core> ("xcap-core");
  std::string root_str;
  std::string username_str;
  std::string password_str;
  std::string user_str;

  {
    xmlChar* str = xmlNodeGetContent (root);
    if (str != NULL)
      root_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (user);
    if (str != NULL)
      user_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (username);
    if (str != NULL)
      username_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (password);
    if (str != NULL)
      password_str = (const char*)str;
  }
  boost::shared_ptr<XCAP::Path> path(new XCAP::Path (root_str, "resource-lists",
					     user_str));
  path->set_credentials (username_str, password_str);
  path = path->build_child ("resource-lists");

  while ( !presentities.empty ()) {

    presentities.begin()->first->removed ();
    for (std::list<boost::signals::connection>::const_iterator iter2
	   = presentities.begin()->second.begin ();
	 iter2 != presentities.begin()->second.end ();
	 ++iter2)
      iter2->disconnect ();
    presentities.erase (presentities.begin()->first);
  }

  doc.reset ();

  xcap->read (path, boost::bind (&RL::Heap::on_document_received, this, _1, _2));
}

void
RL::Heap::on_document_received (bool error,
				std::string value)
{
  if (error) {

    // FIXME: do something
    std::cout << "XCAP error: " << value << std::endl;
  } else {

    parse_doc (value);
  }
}

void
RL::Heap::parse_doc (std::string raw)
{
  doc = boost::shared_ptr<xmlDoc> (xmlRecoverMemory (raw.c_str (), raw.length ()), xmlFreeDoc);
  if ( !doc)
    doc = boost::shared_ptr<xmlDoc> (xmlNewDoc (BAD_CAST "1.0"), xmlFreeDoc);
  xmlNodePtr doc_root = xmlDocGetRootElement (doc.get ());

  if (doc_root == NULL
      || doc_root->name == NULL
      || !xmlStrEqual (BAD_CAST "resource-lists", doc_root->name)) {

    std::cout << "Invalid document in " << __PRETTY_FUNCTION__ << std::endl;
    // FIXME: warn the user somehow?
    doc.reset ();
  } else {


    for (xmlNodePtr child = doc_root->children;
	 child != NULL;
	 child = child->next)
      if (child->type == XML_ELEMENT_NODE
	  && child->name != NULL
	  && xmlStrEqual (BAD_CAST ("list"), child->name)) {

	parse_list (child);
	break; // read only one!
      }
  }
}

void
RL::Heap::parse_list (xmlNodePtr list)
{
  std::string root_str;
  std::string user_str;
  std::string username_str;
  std::string password_str;
  bool writable = false;

  list_node = list;
  {
    xmlChar* str = xmlNodeGetContent (root);
    if (str != NULL)
      root_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (user);
    if (str != NULL)
      user_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (username);
    if (str != NULL)
      username_str = (const char*)str;
  }
  {
    xmlChar* str = xmlNodeGetContent (password);
    if (str != NULL)
      password_str = (const char*)str;
  }
  {
    xmlChar* str = xmlGetProp (node, BAD_CAST "writable");
    if (str != NULL) {

      if (xmlStrEqual (str, BAD_CAST "1"))
	writable = true;
      xmlFree (str);
    }
  }

  boost::shared_ptr<XCAP::Path> path(new XCAP::Path (root_str, "resource-lists",
					     user_str));
  path->set_credentials (username_str, password_str);
  path = path->build_child ("resource-lists");
  path = path->build_child ("list");

  for (xmlNodePtr child = list->children;
       child != NULL;
       child = child->next)
    if (child->type == XML_ELEMENT_NODE
	&& child->name != NULL
	&& xmlStrEqual (BAD_CAST ("entry"), child->name)) {

      PresentityPtr presentity(new Presentity (services, path, doc, child, writable));
      std::list<boost::signals::connection> conns;
      conns.push_back (presentity->updated.connect (boost::bind (boost::ref (presentity_updated), presentity)));
      conns.push_back (presentity->removed.connect (boost::bind(boost::ref (presentity_removed),presentity)));
      conns.push_back (presentity->trigger_reload.connect (boost::bind (&RL::Heap::refresh, this)));
      conns.push_back (presentity->questions.connect (boost::ref (questions)));
      presentities[presentity]=conns;
      presentity_added (presentity);
      continue;
    }
}

void
RL::Heap::push_presence (const std::string uri_,
			 const std::string presence)
{
  for (std::map<PresentityPtr,std::list<boost::signals::connection> >::const_iterator
	 iter = presentities.begin ();
       iter != presentities.end ();
       ++iter) {

    if (iter->first->get_uri () == uri_)
      iter->first->set_presence (presence);
  }
}

void
RL::Heap::push_status (const std::string uri_,
		       const std::string status)
{
  for (std::map<PresentityPtr,std::list<boost::signals::connection> >::const_iterator
	 iter = presentities.begin ();
       iter != presentities.end ();
       ++iter) {

    if (iter->first->get_uri () == uri_)
      iter->first->set_status (status);
  }
}

void
RL::Heap::edit ()
{
  boost::shared_ptr<Ekiga::FormRequestSimple> request = boost::shared_ptr<Ekiga::FormRequestSimple> (new Ekiga::FormRequestSimple (boost::bind (&RL::Heap::on_edit_form_submitted, this, _1, _2)));

  std::string name_str;
  std::string root_str;
  std::string username_str;
  std::string password_str;
  std::string user_str;
  bool writable = false;

  {
    xmlChar* str = xmlNodeGetContent (root);
    if (str != NULL) {

      root_str = (const char*)str;
      xmlFree (str);
    }
  }
  {
    xmlChar* str = xmlNodeGetContent (user);
    if (str != NULL) {

      user_str = (const char*)str;
      xmlFree (str);
    }
  }
  {
    xmlChar* str = xmlNodeGetContent (username);
    if (str != NULL) {

      username_str = (const char*)str;
      xmlFree (str);
    }
  }
  {
    xmlChar* str = xmlNodeGetContent (password);
    if (str != NULL) {

      password_str = (const char*)str;
      xmlFree (str);
    }
  }
  {
    xmlChar* str = xmlGetProp (node, BAD_CAST "writable");
    if (str != NULL) {

      if (xmlStrEqual (str, BAD_CAST "1"))
	writable = true;
      xmlFree (str);
    }
  }

  request->title (_("Edit contact list properties"));

  request->instructions (_("Please edit the following fields (no identifier"
			   " means global)"));

  request->text ("name", _("Contact list's name:"), get_name (), std::string ());
  /* "Document" used as a name -- uri point to the root of a document tree */
  request->text ("root", _("Document root:"), root_str, std::string ());
  request->text ("user", _("Identifier:"), user_str, std::string ());
  request->boolean ("writable", _("Writable:"), writable);
  request->text ("username", _("Server username:"), username_str, std::string ());
  request->private_text ("password", _("Server password:"), password_str, std::string ());

  questions (request);
}

void
RL::Heap::on_edit_form_submitted (bool submitted,
				  Ekiga::Form& result)
{
  if (!submitted)
    return;

  std::string name_str = result.text ("name");
  std::string root_str = result.text ("root");
  std::string user_str = result.text ("user");
  std::string username_str = result.text ("username");
  std::string password_str = result.private_text ("password");
  bool writable = result.boolean ("writable");

  if (writable)
    xmlSetProp (node, BAD_CAST "writable", BAD_CAST "1");
  else
    xmlSetProp (node, BAD_CAST "writable", BAD_CAST "0");
  robust_xmlNodeSetContent (node, &name, "name", name_str);
  robust_xmlNodeSetContent (node, &root, "root", root_str);
  robust_xmlNodeSetContent (node, &user, "user", user_str);
  robust_xmlNodeSetContent (node, &username, "username", username_str);
  robust_xmlNodeSetContent (node, &password, "password", password_str);

  trigger_saving ();
  updated ();
  refresh ();
}

void
RL::Heap::new_entry ()
{
  boost::shared_ptr<Ekiga::FormRequestSimple> request = boost::shared_ptr<Ekiga::FormRequestSimple> (new Ekiga::FormRequestSimple (boost::bind (&RL::Heap::on_new_entry_form_submitted, this, _1, _2)));

  request->title (_("Add a remote contact"));
  request->instructions (_("Please fill in this form to create a new "
			   "contact on a remote server"));

  std::set<std::string> all_groups;
  for (std::map<PresentityPtr,std::list<boost::signals::connection> >::const_iterator
	 iter = presentities.begin ();
       iter != presentities.end ();
       ++iter) {

    std::set<std::string> groups = iter->first->get_groups ();
    all_groups.insert (groups.begin (), groups.end ());
  }

  request->text ("name", _("Name:"), "", std::string ());
  request->text ("uri", _("Address:"), "", std::string ());
  request->editable_set ("groups", _("Choose groups:"),
			 std::set<std::string>(), all_groups);

  questions (request);
}

void
RL::Heap::on_new_entry_form_submitted (bool submitted,
				       Ekiga::Form& result)
{
  if (!submitted)
    return;

  std::string entry_name = result.text ("name");
  std::string entry_uri = result.text ("uri");
  std::set<std::string> entry_groups = result.editable_set ("groups");

  xmlNodePtr entry_node = xmlNewChild (list_node, NULL,
				       BAD_CAST "entry", NULL);
  xmlSetProp (entry_node, BAD_CAST "uri",
	      BAD_CAST robust_xmlEscape (doc.get (), entry_uri).c_str ());
  xmlNewChild (entry_node, NULL, BAD_CAST "display-name",
	       BAD_CAST robust_xmlEscape (doc.get (), entry_name).c_str ());
  xmlNsPtr ns = xmlSearchNsByHref (doc.get (), entry_node,
				   BAD_CAST "http://www.ekiga.org");
  if (ns == NULL) {

    // FIXME: we should handle the case, even if it shouldn't happen
  }

  for (std::set<std::string>::const_iterator iter = entry_groups.begin ();
       iter != entry_groups.end ();
       ++iter) {

    xmlNewChild (entry_node, ns, BAD_CAST "group",
		 BAD_CAST robust_xmlEscape (doc.get (), *iter).c_str ());
  }

  xmlBufferPtr buffer = xmlBufferCreate ();
  int res = xmlNodeDump (buffer, doc.get (), entry_node, 0, 0);

  if (res >= 0) {

    std::string root_str;
    std::string username_str;
    std::string password_str;
    std::string user_str;

    {
      xmlChar* str = xmlNodeGetContent (root);
      if (str != NULL)
	root_str = (const char*)str;
    }
    {
      xmlChar* str = xmlNodeGetContent (user);
      if (str != NULL)
	user_str = (const char*)str;
    }
    {
      xmlChar* str = xmlNodeGetContent (username);
      if (str != NULL)
	username_str = (const char*)str;
    }
    {
      xmlChar* str = xmlNodeGetContent (password);
      if (str != NULL)
	password_str = (const char*)str;
    }
    boost::shared_ptr<XCAP::Path> path(new XCAP::Path (root_str, "resource-lists",
					       user_str));
    path->set_credentials (username_str, password_str);
    path = path->build_child ("resource-lists");
    path = path->build_child ("list");
    path = path->build_child_with_attribute ("entry", "uri", entry_uri);
    boost::shared_ptr<XCAP::Core> xcap = services.get<XCAP::Core> ("xcap-core");
    xcap->write (path, "application/xcap-el+xml",
		 (const char*)xmlBufferContent (buffer),
		 boost::bind (&RL::Heap::new_entry_result, this, _1));
  }
  xmlBufferFree (buffer);
}

void
RL::Heap::new_entry_result (std::string error)
{
  if ( !error.empty ()) {

    std::cout << "XCAP Error: " << error << std::endl;
  }

  refresh ();
}
