"""
Read the class notes for more information.

Example usage:

class Source(TemplateModel):
    class_name = models.CharField(...)

class FeedExtension(Source): pass
class TwitterExtension(Source): pass

Source.objects.create(class_name='FeedExtension')
<FeedExtension: FeedExtension object>

Source._children
{
    'FeedExtension': <class 'FeedExtension'>,
    'TwitterExtension': <class 'TwitterExtension'>
}
"""

from django.db.models.base import *

class TemplateModelBase(ModelBase):
    def __new__(cls, name, bases, attrs):
        """
        Here is where the magic happens. When the class is instantiated we dig
        down to find the base template class (which typically is the originating parent).
        We then append all subclasses into the ._children attribute on that
        parent class.
        """
        parents = [b for b in bases if isinstance(b, TemplateModelBase)]
        if not parents:
            # If this isn't a subclass of Model, don't do anything special.
            return super(ModelBase, cls).__new__(cls, name, bases, attrs)
        
        if parents[0] != TemplateModel:
            if 'Meta' not in attrs:
                class Meta:
                    abstract = True
            else:
                attrs['Meta'].abstract = True
            abstract = True
        else:
            abstract = False
        
        cls = cls._new_cls(name, bases, attrs)

        if abstract:
            parents = cls.__bases__
            
            while parents[0] != TemplateModel:
                parent = parents[0]
                parents = parent.__bases__

            parent._children[cls.__name__] = cls
            cls.__template_key__ = parent.__template_key__
            cls._parent = parent
        else:
            if not hasattr(cls, '_children'):
                cls._children = {}

        return cls

    def __call__(cls, *args, **kwargs):
        """
        When the class is called, we lookup the field name which is going to be called
        and attempt to extrapolate if that key has been included in instantiation. If
        it has we go ahead and return the child class instead of the parent class.
        """
        field = cls._meta.get_field_by_name(cls.__template_key__)[0]

        position = cls._meta.fields.index(field) 
        if len(args) > position: 
            # if it's in the args, we can get it easily by index 
            result = args[position]
        elif field.attname in kwargs:
            # retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a 
            # a ForeignKey.
            result = kwargs[field.attname]
        elif field.name != field.attname and field.name in kwargs:
            # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
            result = kwargs[field.name]
        else:
            raise NotImplementedError

        if hasattr(cls, '_children'):
            cls = cls._children[result]

        return super(TemplateModelBase, cls).__call__(*args, **kwargs)

    def _new_cls(cls, name, bases, attrs):
        """
        This is almost identical to ModelBase.__new__. Modifications
        are simply to remove some of the code which deals with models
        being abstract=True.
        """
        parents = [b for b in bases if isinstance(b, TemplateModelBase)]
        
        # Create the class.
        module = attrs.pop('__module__')
        new_class = super(ModelBase, cls).__new__(cls, name, bases, {'__module__': module})
        attr_meta = attrs.pop('Meta', None)
        abstract = getattr(attr_meta, 'abstract', False)
        if not attr_meta:
            meta = getattr(new_class, 'Meta', None)
        else:
            meta = attr_meta
        base_meta = getattr(new_class, '_meta', None)

        if getattr(meta, 'app_label', None) is None:
            # Figure out the app_label by looking one level up.
            # For 'django.contrib.sites.models', this would be 'sites'.
            model_module = sys.modules[new_class.__module__]
            kwargs = {"app_label": model_module.__name__.split('.')[-2]}
        else:
            kwargs = {}

        new_class.add_to_class('_meta', Options(meta, **kwargs))
        if not abstract:
            new_class.add_to_class('DoesNotExist',
                    subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
            new_class.add_to_class('MultipleObjectsReturned',
                    subclass_exception('MultipleObjectsReturned', MultipleObjectsReturned, module))
            if base_meta and not base_meta.abstract:
                # Non-abstract child classes inherit some attributes from their
                # non-abstract parent (unless an ABC comes before it in the
                # method resolution order).
                if not hasattr(meta, 'ordering'):
                    new_class._meta.ordering = base_meta.ordering
                if not hasattr(meta, 'get_latest_by'):
                    new_class._meta.get_latest_by = base_meta.get_latest_by

        if getattr(new_class, '_default_manager', None):
            new_class._default_manager = None

        # Bail out early if we have already created this class.
        m = get_model(new_class._meta.app_label, name, False)
        if m is not None:
            return m

        # Add all attributes to the class.
        for obj_name, obj in attrs.items():
            new_class.add_to_class(obj_name, obj)

        # Do the appropriate setup for any model parents.
        for base in parents:
            if not hasattr(base, '_meta'):
                # Things without _meta aren't functional models, so they're
                # uninteresting parents.
                continue

            # All the fields of any type declared on this model
            new_fields = new_class._meta.local_fields + \
                         new_class._meta.local_many_to_many + \
                         new_class._meta.virtual_fields
            field_names = set([f.name for f in new_fields])

            parent_fields = base._meta.local_fields + base._meta.local_many_to_many
            for field in parent_fields:
                if field.name in field_names:
                    raise FieldError('Local field %r in class %r clashes '\
                                     'with field of similar name from '\
                                     'abstract base class %r' % \
                                        (field.name, name, base.__name__))
                new_class.add_to_class(field.name, copy.deepcopy(field))

            # Inherit managers from the abstract base classes.
            base_managers = base._meta.abstract_managers
            base_managers.sort()
            for _, mgr_name, manager in base_managers:
                val = getattr(new_class, mgr_name, None)
                if not val or val is manager:
                    new_manager = manager._copy_to_model(new_class)
                    new_class.add_to_class(mgr_name, new_manager)

            # Inherit virtual fields (like GenericForeignKey) from the parent class
            for field in base._meta.virtual_fields:
                if base._meta.abstract and field.name in field_names:
                    raise FieldError('Local field %r in class %r clashes '\
                                     'with field of similar name from '\
                                     'abstract base class %r' % \
                                        (field.name, name, base.__name__))
                new_class.add_to_class(field.name, copy.deepcopy(field))

        if abstract:
            # Abstract base models can't be instantiated and don't appear in
            # the list of models for an app. We do the final setup for them a
            # little differently from normal models.
            attr_meta.abstract = False
            new_class.Meta = attr_meta
            return new_class

        new_class._prepare()
        register_models(new_class._meta.app_label, new_class)

        # Because of the way imports happen (recursively), we may or may not be
        # the first time this model tries to register with the framework. There
        # should only be one class for each model, so we always return the
        # registered version.
        return get_model(new_class._meta.app_label, name, False)
    _new_cls = classmethod(_new_cls)

class TemplateModel(Model):
    """
    The TemplateModel is designed to allow a sort-of inheritance. You may specify a base
    model, and inherit from it. This base is the only table created in the database, and
    must contain a class_name field (specifiable with __template_key__). Now whenever the
    model class is instantiated, it will automatically be replaced by the child class
    which inherited from it.
    """
    __metaclass__ = TemplateModelBase

    # The field name which should be used for storage of the subclasses name.
    __template_key__ = 'class_name'
    
    def _get_pk_val(self, meta):
        if hasattr(self, '_parent'):
            meta = self._parent._meta
        return Model._get_pk_val(self, meta)

    def save(self, force_insert=False, force_update=False):
        """
        To handle saving of a child class, we need to force the save method to
        be called on the parent table.
        """
        if force_insert and force_update:
            raise ValueError("Cannot force both insert and updating in "
                    "model saving.")
        self.save_base(cls=getattr(self, '_parent', self), force_insert=force_insert, force_update=force_update)