#!/usr/bin/python2.4
#
# 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.

import UserDict

import element
import errors

import util

class Annotation(object):
  """Models an annotation on a document.

  Annotations are key/value pairs over a range of content. Annotations
  can be used to store data or to be interpreted by a client when displaying
  the data.
  """

  # Use the following constants to control the display of the client

  #: Reserved annotation for setting background color of text.
  BACKGROUND_COLOR = "style/backgroundColor"
  #: Reserved annotation for setting color of text.
  COLOR = "style/color"
  #: Reserved annotation for setting font family of text.
  FONT_FAMILY = "style/fontFamily"
  #: Reserved annotation for setting font family of text.
  FONT_SIZE = "style/fontSize"
  #: Reserved annotation for setting font style of text.
  FONT_STYLE = "style/fontStyle"
  #: Reserved annotation for setting font weight of text.
  FONT_WEIGHT = "style/fontWeight"
  #: Reserved annotation for setting text decoration.
  TEXT_DECORATION = "style/textDecoration"
  #: Reserved annotation for setting vertical alignment.
  VERTICAL_ALIGN = "style/verticalAlign"

  def __init__(self, name, value, start, end):
    self._name = name
    self._value = value
    self._start = start
    self._end = end

  @property
  def name(self):
    return self._name

  @property
  def value(self):
    return self._value

  @property
  def start(self):
    return self._start

  @property
  def end(self):
    return self._end

  def _shift(self, where, inc):
    """Shift annotation by 'inc' if it (partly) overlaps with 'where'."""
    if self._start >= where:
      self._start += inc
    if self._end >= where:
      self._end += inc

  def serialize(self):
    """Serializes the annotation.

    Returns:
      A dict containing the name, value, and range values.
    """
    return {'name': self._name,
            'value': self._value,
            'range': {'start': self._start,
                      'end': self._end}}


class Annotations(object, UserDict.DictMixin):
  """A dictionary-like object containing the annotations, keyed by name."""

  def __init__(self, operation_queue, blip):
    self._operation_queue = operation_queue
    self._blip = blip
    self._store = {}

  def __contains__(self, what):
    if isinstance(what, Annotation):
      what = what.name
    return what in self._store

  def _add_internal(self, name, value, start, end):
    """Internal add annotation does not send out operations."""
    if name in self._store:
      # TODO: use bisect to make this more efficient.
      new_list = []
      for existing in self._store[name]:
        if start > existing.end or end < existing.start:
          new_list.append(existing)
        else:
          if existing.value == value:
            # merge the annotations:
            start = min(existing.start, start)
            end = max(existing.end, end)
          else:
            # chop the bits off the existing annotation
            if existing.start < start:
              new_list.append(Annotation(
                  existing.name, existing.value, existing.start, start))
            if existing.end > end:
              new_list.append(Annotation(
                  existing.name, existing.value, existing.end, end))
      new_list.append(Annotation(name, value, start, end))
      self._store[name] = new_list
    else:
      self._store[name] = [Annotation(name, value, start, end)]

  def _delete_internal(self, name, start=0, end=-1):
    """Remove the passed annotaion from the internal representation."""
    if not name in self._store:
      return
    if end < 0:
      end = len(self._blip) + end

    new_list = []
    for a in self._store[name]:
      if start > a.end or end < a.start:
        new_list.append(a)
      elif start < a.start and end > a.end:
        continue
      else:
        if a.start < start:
          new_list.append(Annotation(name, a.value, a.start, start))
        if a.end > end:
          new_list.append(Annotation(name, a.value, end, a.end))
    if new_list:
      self._store[name] = new_list
    else:
      del self._store[name]

  def _shift(self, where, inc):
    """Shift annotation by 'inc' if it (partly) overlaps with 'where'."""
    for annotations in self._store.values():
      for annotation in annotations:
        annotation._shift(where, inc)

    # Merge fragmented annotations that should be contiguous, for example:
    # Annotation('foo', 'bar', 1, 2) and Annotation('foo', 'bar', 2, 3).
    for name, annotations in self._store.items():
      new_list = []
      for i, annotation in enumerate(annotations):
        name = annotation.name
        value = annotation.value
        start = annotation.start
        end = annotation.end

        # Find the last end index.
        for j, next_annotation in enumerate(annotations[i + 1:]):
  
        # Not contiguous, skip.
          if (end < next_annotation.start):
            break

          # Contigu
ous, merge.
          if (end == next_annotation.start and value == next_annotation.value):
            end = next_annotation.end
            del annotations[j]
        new_list.append(Annotation(name, value, start, end))
      self._store[name] = new_list

  def __len__(self):
    return len(self._store)

  def __getitem__(self, key):
    return self._store[key]

  def __iter__(self):
    for l in self._store.values():
      for ann in l:
        yield ann

  def names(self):
    """Return the names of the annotations in the store."""
    return self._store.keys()

  def serialize(self):
    """Return a list of the serialized annotations."""
    res = []
    for v in self._store.values():
      res += [a.serialize() for a in v]
    return res


class Blips(object, UserDict.DictMixin):
  """A dictionary-like object containing the blips, keyed on blip ID."""

  def __init__(self, blips):
    self._blips = blips

  def __contains__(self, blip_id):
    return blip_id in self._blips

  def __getitem__(self, blip_id):
    return self._blips[blip_id]

  def __iter__(self):
    return self._blips.__iter__()

  def __len__(self):
    return len(self._blips)

  def _add(self, ablip):
    self._blips[ablip.blip_id] = ablip

  def _remove_with_id(self, blip_id):
    del_blip = self._blips[blip_id]
    if del_blip:
      # Remove the reference to this blip from its parent.
      parent_blip = self._blips[blip_id].parent_blip
      if parent_blip:
        parent_blip._child_blip_ids.remove(blip_id)
    del self._blips[blip_id]

  def get(self, blip_id, default_value=None):
    """Retrieves a blip.

    Returns:
      A Blip object. If none found for the ID, it returns None,
      or if default_value is specified, it returns that.
    """
    return self._blips.get(blip_id, default_value)

  def serialize(self):
    """Serializes the blips.
    Returns:
      A dict of serialized blips.
    """
    res = {}
    for blip_id, item in self._blips.items():
      res[blip_id] = item.serialize()
    return res

  def values(self):
    """Return the blips themselves."""
    return self._blips.values()


class BlipRefs(object):
  """Represents a set of references to contents in a blip.

  For example, a BlipRefs instance can represent the results
  of a search, an explicitly set range, a regular expression,
  or refer to the entire blip. BlipRefs are used to express
  operations on a blip in a consistent way that can easily
  be transfered to the server.

  The typical way of creating a BlipRefs object is to use
  selector methods on the Blip object. Developers will not
  usually instantiate a BlipRefs obje