#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A handler that allows the user to send XMPP messages to their application."""


import cStringIO
import random
import string
from xml.etree import ElementTree

from google.appengine.tools.devappserver2.admin import admin_request_handler

ElementTree.register_namespace('cli', 'jabber:client')

REMOTE_IP = '0.1.0.10'


class _FormData(object):
  def __init__(self):
    self._data = []

  def __eq__(self, o):
    return isinstance(o, _FormData) and self._data == o._data

  def __str__(self):
    _, content = self.get_boundary_and_content()
    return content
  __repr__ = __str__

  def add_text(self, name, value, sub_type):
    self._data.append((name, value, sub_type))

  def get_boundary_and_content(self):
    """Returns the message boundary and entire content body for the form."""
    boundary = '----=' + ''.join(random.choice(string.letters + string.digits)
                                 for _ in range(25))
    s = cStringIO.StringIO()

    for name, value, sub_type in self._data:
      s.write('--%s\r\n' % boundary)
      s.write('Content-Type: text/%s; charset="UTF-8"\r\n' % sub_type)
      s.write('Content-Disposition: form-data; name="%s"\r\n' % name)
      s.write('\r\n')
      s.write('%s\r\n' % value.encode('utf-8'))
    s.write('--%s--\r\n' % boundary)
    return boundary, s.getvalue()


class XmppRequestHandler(admin_request_handler.AdminRequestHandler):

  def get(self):
    # TODO: Generate a warning if XMPP is not configured when the
    # configuration is sorted in the new servers world.
    self.response.write(self.render('xmpp.html', {}))

  def post(self):
    message_type = self.request.get('message_type')
    to = self.request.get('to')
    from_ = self.request.get('from')

    if message_type == 'chat':
      self._send_chat(to, from_)
    elif message_type == 'presence':
      if self.request.get('presence') == 'available':
        self._send_presence_available(to, from_)
      elif self.request.get('presence') == 'unavailable':
        self._send_presence_unavailable(to, from_)
      else:
        assert 0
    elif message_type == 'subscribe':
      self._send_subscription(to, from_)
    else:
      assert 0

  def _generate_chat(self, to, from_, body):
    data = _FormData()
    data.add_text('from', from_, 'plain')
    data.add_text('to', to, 'plain')
    data.add_text('body', body, 'plain')

    message_element = ElementTree.Element(
        ElementTree.QName('jabber:client', 'message'),
        {'from': from_, 'to': to, 'type': 'chat'})
    body_element = ElementTree.SubElement(
        message_element,
        ElementTree.QName('jabber:client', 'body'))
    body_element.text = body

    data.add_text('stanza',
                  ElementTree.tostring(message_element, encoding='utf-8'),
                  'xml')
    return data

  def _send_chat(self, to, from_):
    body = self.request.get('chat')
    form_data = self._generate_chat(to, from_, body)
    response = self._send('/_ah/xmpp/message/chat/', form_data)
    self.response.status = response.status

  def _generate_presence_available(self, to, from_, show=None):
    data = _FormData()
    data.add_text('from', from_, 'plain')
    data.add_text('to', to, 'plain')

    # If the "presence" attribute is absent, "available" is assumed and it is
    # not sent by Google Talk.
    presence_element = ElementTree.Element(
        ElementTree.QName('jabber:client', 'presence'),
        {'from': from_, 'to': to})

    if show:  # This is currently a dead code path.
      # The show element is optional according to RFC 3921, 2.2.2.1.
      data.add_text('show', show, 'plain')
      show_element = ElementTree.SubElement(
          presence_element,
          ElementTree.QName('jabber:client', 'show'))
      show_element.text = show

    data.add_text('stanza',
                  ElementTree.tostring(presence_element, 'utf-8'),
                  'xml')
    return data

  def _send(self, relative_url, form_data):
    boundary, content = form_data.get_boundary_and_content()
    return self.dispatcher.add_request(
        method='POST',
        relative_url=relative_url,
        headers=[('Content-Type',
                  'multipart/form-data; boundary="%s"' % boundary)],
        body=content,
        source_ip=REMOTE_IP,
        fake_login=True)

  def _send_presence_available(self, to, from_):
    # TODO: Support "show" values i.e. "away", "chat" "dnd" and "xa".
    # See RFC 3921, 2.2.2.1.
    form_data = self._generate_presence_available(to, from_)
    response = self._send('/_ah/xmpp/presence/available/', form_data)
    self.response.status = response.status

  def _generate_presence_type(self, to, from_, presence_type):
    data = _FormData()

    data.add_text('from', from_, 'plain')
    data.add_text('to', to, 'plain')

    presence_element = ElementTree.Element(
        ElementTree.QName('jabber:client', 'presence'),
        {'from': from_, 'to': to, 'type': presence_type})
    data.add_text('stanza',
                  ElementTree.tostring(presence_element, 'utf-8'),
                  'xml')

    return data

  def _send_presence_unavailable(self, to, from_):
    form_data = self._generate_presence_type(to, from_, 'unavailable')
    response = self._send('/_ah/xmpp/presence/unavailable/', form_data)
    self.response.status = response.status

  def _send_subscription(self, to, from_):
    subscription_type = self.request.get('subscription_type')
    # The subscription types defined in RFC 3921, 2.2.1
    assert subscription_type in ['subscribe',
                                 'subscribed',
                                 'unsubscribe',
                                 'unsubscribed']

    form_data = self._generate_presence_type(to, from_, subscription_type)
    response = self._send('/_ah/xmpp/subscription/%s/' % subscription_type,
                          form_data)
    self.response.status = response.status
