#!/usr/bin/python
#
# Copyright (C) 2009 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.

"""Elements are non-text bits living in blips like images, gadgets etc.

This module defines the Element class and the derived classes.
"""


import base64
import logging
import sys

import util


class Element(object):
  """Elements are non-text content within a document.

  These are generally abstracted from the Robot. Although a Robot can query the
  properties of an element it can only interact with the specific types that
  the element represents.

  Properties of elements are both accessible directly (image.url) and through
  the properties dictionary (image.properties['url']). In general Element
  should not be instantiated by robots, but rather rely on the derived classes.
  """

  # INLINE_BLIP_TYPE is not a separate type since it shouldn't be instantiated,
  # only be used for introspection
  INLINE_BLIP_TYPE = "INLINE_BLIP"

  def __init__(self, element_type, **properties):
    """Initializes self with the specified type and any properties.

    Args:
      element_type: string typed member of ELEMENT_TYPE
      properties: either a dictionary of initial properties, or a dictionary
          with just one member properties that is itself a dictionary of
          properties. This allows us to both use
          e = Element(atype, prop1=val1, prop2=prop2...)
          and
          e = Element(atype, properties={prop1:val1, prop2:prop2..})
    """
    if len(properties) == 1 and 'properties' in properties:
      properties = properties['properties']
    self._type = element_type
    # as long as the operation_queue of an element in None, it is
    # unattached. After an element is acquired by a blip, the blip
    # will set the operation_queue to make sure all changes to the
    # element are properly send to the server.
    self._operation_queue = None
    self._properties = properties.copy()
  
  @property
  def type(self):
    """The type of this element."""
    return self._type

  @classmethod
  def from_json(cls, json):
    """Class method to instantiate an Element based on a json string."""
    etype = json['type']
    props = json['properties'].copy()

    element_class = ALL.get(etype)
    if not element_class:
      # Unknown type. Server could be newer than we are
      return Element(element_type=etype, properties=props)

    return element_class.from_props(props)

  def get(self, key, default=None):
    """Standard get interface."""
    return self._properties.get(key, default)

  def __getattr__(self, key):
    return self._properties[key]

  def serialize(self):
    """Custom serializer for Elements."""
    return util.serialize({'properties': util.non_none_dict(self._properties),
                           'type': self._type})


class Input(Element):
  """A single-line input element."""

  class_type = 'INPUT'

  def __init__(self, name, value=''):
    super(Input, self).__init__(Input.class_type,
                                name=name,
                                value=value,
                                default_value=value)

  @classmethod
  def from_props(cls, props):
    return Input(name=props.get('name'), value=props.get('value'))


class Check(Element):
  """A checkbox element."""

  class_type = 'CHECK'

  def __init__(self, name, value=''):
    super(Check, self).__init__(Check.class_type,
                                name=name, value=value, default_value=value)

  @classmethod
  def from_props(cls, props):
    return Check(name=props.get('name'), value=props.get('value'))


class Button(Element):
  """A button element."""

  class_type = 'BUTTON'

  def __init__(self, name, value):
    super(Button, self).__init__(Button.class_type,
                                 name=name, value=value)

  @classmethod
  def from_props(cls, props):
    return Button(name=props.get('name'), value=props.get('value'))


class Label(Element):
  """A label element."""

  class_type = 'LABEL'

  def __init__(self, label_for, caption):
    super(Label, self).__init__(Label.class_type,
                                name=label_for, value=caption)

  @classmethod
  def from_props(cls, props):
    return Label(label_for=props.get('name'), caption=props.get('value'))


class RadioButton(Element):
  """A radio button element."""

  class_type = 'RADIO_BUTTON'

  def __init__(self, name, group):
    super(RadioButton, self).__init__(RadioButton.class_type,
                                      name=name, value=group)

  @classmethod
  def from_props(cls, props):
    return RadioButton(name=props.get('name'), group=props.get('value'))


class RadioButtonGroup(Element):
  """A group of radio buttons."""

  class_type = 'RADIO_BUTTON_GROUP'

  def __init__(self, name, value):
    super(RadioButtonGroup, self).__init__(RadioButtonGroup.class_type,
                                           name=name, value=value)

  @classmethod
  def from_props(cls, props):
    return RadioButtonGroup(name=props.get('name'), value=props.get('value'))


class Password(Element):
  """A password element."""

  class_type = 'PASSWORD'

  def __init__(self, name, value):
    super(Password, self).__init__(Password.class_type,
                                   name=name, value=value)

  @classmethod
  def from_props(cls, props):
    return Password(name=props.get('name'), value=props.get('value'))


class TextArea(Element):
  """A text area element."""

  class_type = 'TEXTAREA'

  def __init__(self, name, value):
    super(TextArea, self).__init__(TextArea.class_type,
                                   name=name, value=value)

  @classmethod
  def from_props(cls, props):
    return TextArea(name=props.get('name'), value=props.get('value'))


class Line(Element):
  """A line element.
  
  Note that Lines are represented in the text as newlines.
  """

  class_type = 'LINE'

  # Possible line types:
  #: Designates line as H1, largest heading.
  TYPE_H1 = 'h1'
  #: Designates line as H2 heading.
  TYPE_H2 = 'h2'
  #: Designates line as H3 heading.
  TYPE_H3 = 'h3'
  #: Designates line as H4 heading.
  TYPE_H4 = 'h4'
  #: Designates line as H5, smallest heading.
  TYPE_H5 = 'h5'
  #: Designates line as a bulleted list item.
  TYPE_LI = 'li'

  # Possible values for align
  #: Sets line alignment to left.
  ALIGN_LEFT = 'l'
  #: Sets line alignment to right.
  ALIGN_RIGHT = 'r'
  #: Sets line alignment to centered.
  ALIGN_CENTER = 'c'
  #: Sets line alignment to justified.
  ALIGN_JUSTIFIED = 'j'

  def __init__(self,
               line_type=None,
               indent=None,
               alignment=None,
               direction=None):
    super(Line, self).__init__(Line.class_type,
                               lineType=line_type,
                               indent=indent,
                               alignment=alignment,
                               direction=direction)

  @classmethod
  def from_props(cls, props):
    return Line(line_type=props.get('lineType'),
                indent=props.get('indent'),
                alignment=props.get('alignment'),
                direction=props.get('direction'))


class Gadget(Element):
  """A gadget element."""

  class_type = 'GADGET'

  def __init__(self, url, props=None):
    if props is None:
      props = {}
    props['url'] = url
    super(Gadget, self).__init__(Gadget.class_type, properties=props)

  @classmethod
  def from_props(cls, props):
    return Gadget(props.get('url'), props)

  def serialize(self):
    """Gadgets allow for None values."""
    return {'properties': self._properties, 'type': self._type}
  
  def keys(self):
    """Get the valid keys for this gadget."""
    return [x for x in self._properties.keys() if x != 'url']


class Installer(Element):
  """An installer element."""

  class_type = 'INSTALLER'

  def __init__(self, manifest):
    super(Installer, self).__init__(Installer.class_type, manifest=manifest)

  @classmethod
  def from_props(cls, props):
    return Installer(props.get('manifest'))



class Image(Element):
  """An image element."""

  class_type = 'IMAGE'

  def __init__(self, url='', width=None, height=None,
               attachmentId=None, caption=None):
    super(Image, self).__init__(Image.class_type, url=url, width=width,
          height=height, attachmentId=attachmentId, caption=caption)

  @classmethod
  def from_props(cls, props):
    props = dict([(key.encode('utf-8'), value)
                  for key, value in props.items()])
    return apply(Image, [], props)

class Attachment(Element):
  """An attachment element.

  To create a new attachment, caption and data are needed.
  mimeType, attachmentId and attachmentUrl are sent via events.
  """

  class_type = 'ATTACHMENT'

  def __init__(self, caption=None, data=None, mimeType=None, attachmentId=None,
                attachmentUrl=None):
    Attachment.originalData = data
    super(Attachment, self).__init__(Attachment.class_type, caption=caption,
          data=data, mimeType=mimeType, attachmentId=attachmentId,
          attachmentUrl=attachmentUrl)

  def __getattr__(self, key):
    if key and key == 'data':
      return Attachment.originalData
    return super(Attachment, self).__getattr__(key)

  @classmethod
  def from_props(cls, props):
    props = dict([(key.encode('utf-8'), value)
                  for key, value in props.items()])
    return apply(Attachment, [], props)

  def serialize(self):
    """Serializes the attachment object into JSON.

    The attachment data is base64 encoded.
    """

    if self.data:
      self._properties['data'] = base64.encodestring(self.data)
    return super(Attachment, self).serialize()


def is_element(cls):
  """Returns whether the passed class is an element."""
  try:
    if not issubclass(cls, Element):
      return False
    h = hasattr(cls, 'class_type')
    return hasattr(cls, 'class_type')
  except TypeError:
    return False

ALL = dict([(item.class_type, item) for item in globals().copy().values()
            if is_element(item)])
