/*
 *  Copyright 2011 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/libjingle/xmpp/pubsubtasks.h"

#include <string>
#include <vector>

#include "webrtc/libjingle/xmpp/constants.h"
#include "webrtc/libjingle/xmpp/receivetask.h"

// An implementation of the tasks for XEP-0060
// (http://xmpp.org/extensions/xep-0060.html).

namespace buzz {

namespace {

bool IsPubSubEventItemsElem(const XmlElement* stanza,
                            const std::string& expected_node) {
  if (stanza->Name() != QN_MESSAGE) {
    return false;
  }

  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
  if (event_elem == NULL) {
    return false;
  }

  const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
  if (items_elem == NULL) {
    return false;
  }

  const std::string& actual_node = items_elem->Attr(QN_NODE);
  return (actual_node == expected_node);
}


// Creates <pubsub node="node"><items></pubsub>
XmlElement* CreatePubSubItemsElem(const std::string& node) {
  XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false);
  items_elem->AddAttr(QN_NODE, node);
  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false);
  pubsub_elem->AddElement(items_elem);
  return pubsub_elem;
}

// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
// Takes ownership of payload.
XmlElement* CreatePubSubPublishItemElem(
    const std::string& node,
    const std::string& itemid,
    const std::vector<XmlElement*>& children) {
  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
  XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false);
  publish_elem->AddAttr(QN_NODE, node);
  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
  item_elem->AddAttr(QN_ID, itemid);
  for (std::vector<XmlElement*>::const_iterator child = children.begin();
       child != children.end(); ++child) {
    item_elem->AddElement(*child);
  }
  publish_elem->AddElement(item_elem);
  pubsub_elem->AddElement(publish_elem);
  return pubsub_elem;
}

// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
// Takes ownership of payload.
XmlElement* CreatePubSubRetractItemElem(const std::string& node,
                                        const std::string& itemid) {
  XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
  XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false);
  retract_elem->AddAttr(QN_NODE, node);
  retract_elem->AddAttr(QN_NOTIFY, "true");
  XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
  item_elem->AddAttr(QN_ID, itemid);
  retract_elem->AddElement(item_elem);
  pubsub_elem->AddElement(retract_elem);
  return pubsub_elem;
}

void ParseItem(const XmlElement* item_elem,
               std::vector<PubSubItem>* items) {
  PubSubItem item;
  item.itemid = item_elem->Attr(QN_ID);
  item.elem = item_elem;
  items->push_back(item);
}

// Right now, <retract>s are treated the same as items with empty
// payloads.  We may want to change it in the future, but right now
// it's sufficient for our needs.
void ParseRetract(const XmlElement* retract_elem,
                  std::vector<PubSubItem>* items) {
  ParseItem(retract_elem, items);
}

void ParseEventItemsElem(const XmlElement* stanza,
                         std::vector<PubSubItem>* items) {
  const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
  if (event_elem != NULL) {
    const XmlElement* items_elem =
        event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
    if (items_elem != NULL) {
      for (const XmlElement* item_elem =
             items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM);
           item_elem != NULL;
           item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) {
        ParseItem(item_elem, items);
      }
      for (const XmlElement* retract_elem =
             items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT);
           retract_elem != NULL;
           retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) {
        ParseRetract(retract_elem, items);
      }
    }
  }
}

void ParsePubSubItemsElem(const XmlElement* stanza,
                          std::vector<PubSubItem>* items) {
  const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB);
  if (pubsub_elem != NULL) {
    const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS);
    if (items_elem != NULL) {
      for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM);
           item_elem != NULL;
           item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) {
        ParseItem(item_elem, items);
      }
    }
  }
}

}  // namespace

PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent,
                                     const Jid& pubsubjid,
                                     const std::string& node)
    : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) {
}

void PubSubRequestTask::HandleResult(const XmlElement* stanza) {
  std::vector<PubSubItem> items;
  ParsePubSubItemsElem(stanza, &items);
  SignalResult(this, items);
}

int PubSubReceiveTask::ProcessStart() {
  if (SignalUpdate.is_empty()) {
    return STATE_DONE;
  }
  return ReceiveTask::ProcessStart();
}

bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) {
  return MatchStanzaFrom(stanza, pubsubjid_) &&
      IsPubSubEventItemsElem(stanza, node_) && !SignalUpdate.is_empty();
}

void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) {
  std::vector<PubSubItem> items;
  ParseEventItemsElem(stanza, &items);
  SignalUpdate(this, items);
}

PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent,
                                     const Jid& pubsubjid,
                                     const std::string& node,
                                     const std::string& itemid,
                                     const std::vector<XmlElement*>& children)
    : IqTask(parent, STR_SET, pubsubjid,
             CreatePubSubPublishItemElem(node, itemid, children)),
      itemid_(itemid) {
}

void PubSubPublishTask::HandleResult(const XmlElement* stanza) {
  SignalResult(this);
}

PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent,
                                     const Jid& pubsubjid,
                                     const std::string& node,
                                     const std::string& itemid)
    : IqTask(parent, STR_SET, pubsubjid,
             CreatePubSubRetractItemElem(node, itemid)),
      itemid_(itemid) {
}

void PubSubRetractTask::HandleResult(const XmlElement* stanza) {
  SignalResult(this);
}

}  // namespace buzz