#!/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.
#

"""Support for creating Kay forms from Datastore data models.

Taken from google.appengine.ext.db.djangoforms

Terminology notes:
  - forms: always refers to the Kay newforms subpackage
  - field: always refers to a Kay forms.Field instance
  - property: always refers to a db.Property instance

Mapping between properties and fields:

+====================+===================+==============+====================+
| Property subclass  | Field subclass    | datatype     | widget; notes      |
+====================+===================+==============+====================+
| StringProperty     | TextField         | unicode      | Textarea           |
|                    |                   |              | if multiline       |
+--------------------+-------------------+--------------+--------------------+
| TextProperty       | TextField         | unicode      | Textarea           |
+--------------------+-------------------+--------------+--------------------+
| BlobProperty       | FileField         | str          | skipped in v0.96   |
+--------------------+-------------------+--------------+--------------------+
| DateTimeProperty   | DateTimeField     | datetime     | skipped            |
|                    |                   |              | if auto_now[_add]  |
+--------------------+-------------------+--------------+--------------------+
| DateProperty       | DateField         | date         | ditto              |
+--------------------+-------------------+--------------+--------------------+
| TimeProperty       | TimeField         | time         | ditto              |
+--------------------+-------------------+--------------+--------------------+
| IntegerProperty    | IntegerField      | int or long  |                    |
+--------------------+-------------------+--------------+--------------------+
| FloatProperty      | FloatField        | float        | CharField in v0.96 |
+--------------------+-------------------+--------------+--------------------+
| BooleanProperty    | BooleanField      | bool         |                    |
+--------------------+-------------------+--------------+--------------------+
| UserProperty       | TextField         | users.User   |                    |
+--------------------+-------------------+--------------+--------------------+
| StringListProperty | TextField         | list of str  | Textarea           |
+--------------------+-------------------+--------------+--------------------+
| LinkProperty       | TextField         | str          |                    |
+--------------------+-------------------+--------------+--------------------+
| ReferenceProperty  | ModelField*       | db.Model     |                    |
+--------------------+-------------------+--------------+--------------------+
| _ReverseReferenceP.| None              | <iterable>   | always skipped     |
+====================+===================+==============+====================+

"""



import itertools
import logging


from google.appengine.api import users
from google.appengine.ext import db

from kay import exceptions
from kay.utils import forms
from kay.utils import datastructures
from kay.i18n import lazy_gettext as _
from kay.exceptions import ImproperlyConfigured
from kay.models import NamedModel

def monkey_patch(name, bases, namespace):
  """A 'metaclass' for adding new methods to an existing class.

  In this version, existing methods can't be overridden; this is by
  design, to avoid accidents.

  Usage example:

    class PatchClass(TargetClass):
      __metaclass__ = monkey_patch
      def foo(self, ...): ...
      def bar(self, ...): ...

  This is equivalent to:

    def foo(self, ...): ...
    def bar(self, ...): ...
    TargetClass.foo = foo
    TargetClass.bar = bar
    PatchClass = TargetClass

  Note that PatchClass becomes an alias for TargetClass; by convention
  it is recommended to give PatchClass the same name as TargetClass.
  """

  assert len(bases) == 1, 'Exactly one base class is required'
  base = bases[0]
  for name, value in namespace.iteritems():
    if name not in ('__metaclass__', '__module__'):
      assert name not in base.__dict__, "Won't override attribute %r" % (name,)
      setattr(base, name, value)
  return base




class Property(db.Property):
  __metaclass__ = monkey_patch

  def get_model_property_name(self):
    """Return the attribute name of this property in this property's Model class"""

    matching_prop_names = [prop_name for (prop_name, prop) in
            self.model_class.properties().items() if prop.name == self.name]
    if len(matching_prop_names) != 1:
      raise Exception('Model class "%s" must have exactly one property with'
                      ' the datastore storage name "%s". Found %d properties'
                      ' with that name: %s' % (
          self.model_class.__name__,
          self.name,
          len(matching_prop_names),
          matching_prop_names
        )
      )
    return matching_prop_names[0]

  def get_form_field(self, form_class=forms.TextField, **kwargs):
    """Return a Django form field appropriate for this property.

    Args:
      form_class: a forms.Field subclass, default forms.CharField

    Additional keyword arguments are passed to the form_class constructor,
    with certain defaults:
      required: self.required
      label: prettified self.verbose_name, if not None
      widget: a forms.Select instance if self.choices is non-empty
      initial: self.default, if not None

    Returns:
       A fully configured instance of form_class, or None if no form
       field should be generated for this property.
    """
    defaults = {'required': self.required}
    if self.verbose_name is None:
      defaults['label'] = (
        self.get_model_property_name().capitalize().replace('_', ' ')
      )
    else:
      defaults['label'] = self.verbose_name
    if self.choices:
      choices = []
      if not self.required or (self.default is None and
                               'initial' not in kwargs):
        choices.append(('', '---------'))
      for choice in self.choices:
        choices.append((unicode(choice), unicode(choice)))
      defaults['choices'] = choices
      form_class = forms.ChoiceField
    if self.default is not None:
      defaults['default'] = self.default
    defaults.update(kwargs)
    return form_class(**defaults)

  def get_value_for_form(self, instance):
    """Extract the property value from the instance for use in a form.

    Override this to do a property- or field-specific type conversion.

    Args:
      instance: a db.Model instance

    Returns:
      The property's value extracted from the instance, possibly
      converted to a type suitable for a form field; possibly None.