# -*- coding: utf-8 -*-

import os
import datetime
from new import classobj

from django.template.loader import Context, render_to_string
from django.core.exceptions import ObjectDoesNotExist

import xml_models
import xml_models.xpath_twister as xpath
import rest_client

from saasu_client import DEFAULT_POST_URL

__all__ = ['BaseModel', 'GETResponseBase', 'POSTResponseBase']


class CollectionList(list):

    def __init__(self, type):
        self.type = type

    def append(self, item):
        if not isinstance(item, self.type):
            raise TypeError, 'item is not of type %s' % self.type
        super(CollectionList, self).append(self.type(xml=item._xml))  #append the item to itself (the list)


class Collection(xml_models.BaseField):
    """
    Returns a collection found by the xpath expression.  Requires a
    field_type to be supplied, which can either be a field type,
    e.g. IntField, which returns a collection ints, or it can be a
    model type e.g. Person may contain a collection of Address
    objects.
    """

    def __init__(self, field_type, order_by=None, **kw):
        self.field_type = field_type
        self.order_by = order_by
        xml_models.BaseField.__init__(self,**kw)
        
    def parse(self, xml, namespace):
        matches = xpath.find_all(xml, self.xpath, namespace)

        if not xml_models.BaseField in self.field_type.__bases__:
            results = CollectionList(type=self.field_type)
            for match in matches:
                results.append(self.field_type(xml=match))
        else:
            field = self.field_type(xpath = '.')
            results = [field.parse(xpath.domify(match), namespace) for match in matches]
        if self.order_by:
            results.sort(lambda a,b : cmp(getattr(a, self.order_by), getattr(b, self.order_by)))
        return results
    
CollectionField = Collection


class Error(xml_models.Model):
    """ Error Message Entity """

    error_type = xml_models.CharField(xpath="/error/type")
    error_message = xml_models.CharField(xpath="/error/message")


class GETResponseBase(object):
    """ Error Response Entity """

    def __init__(self, object=None, xml=None, dom=None):
        """ put docstring here """
        self.__model__ = getattr(object, '__model__', None)
        self.object = object
        self.xml = xml
        self.dom = dom

    def register(self):
        """ put docstring here """
        # autogenerated fields
        __model__ = self.__model__[0].lower() + self.__model__[1:]
        errors = xml_models.Collection(Error, order_by="error_type", xpath="/%sResponse/errors/error" % __model__)
        
        def validate_on_load(cls):
            for e in cls.errors:
                exception = classobj(str(e.error_type), (Exception,), {})
                raise exception, e.error_message

        # autogenerated class
        cls = classobj('GETResponse', (xml_models.Model,), {
            'errors': errors,
            'validate_on_load': validate_on_load,
            })
        #print self.xml
        return cls(xml=self.xml, dom=self.dom)


class POSTResponseBase(object):
    """ Base Tasks Response Entity """

    def __init__(self, object=None, xml=None, dom=None):
        """ put docstring here """
        self.__model__ = getattr(object, '__model__', None)
        self.object = object
        self.xml = xml
        self.dom = dom

    def register(self):
        """ put docstring here """
        
        # If object already have 'uid' field update entry otherwise insert new
        if hasattr(self.object, 'uid') and self.object.uid:
            cmd = 'update'
        else:
            cmd = 'insert'

        # autogenerated fields
        uid = xml_models.IntField(xpath="/tasksResponse/%s%sResult/@%s" % (
            cmd, self.__model__, {'insert': 'insertedEntityUid', 'update': 'updatedEntityUid'}[cmd]), default=None)
        lastUpdatedUid = xml_models.CharField(xpath="/tasksResponse/%s%sResult/@lastUpdatedUid" % (cmd, self.__model__))
        errors = xml_models.Collection(Error, order_by="error_type", xpath="/tasksResponse/%s%sResult/errors/error" % (cmd, self.__model__))
        other_errors = xml_models.Collection(Error, order_by="error_type", xpath="/tasksResponse/errors/error")

        def validate_on_load(cls):
            for e in cls.errors:
                exception = classobj(str(e.error_type), (Exception,), {})
                raise exception, e.error_message
            for e in cls.other_errors:
                exception = classobj(str(e.error_type), (Exception,), {})
                raise exception, e.error_message

        # autogenerated class
        cls = classobj('POSTResponse', (xml_models.Model,), {
            'uid': uid,
            'lastUpdatedUid': lastUpdatedUid,
            'errors': errors,
            'other_errors': other_errors,
            'validate_on_load': validate_on_load,
            })
        #print self.xml
        return cls(xml=self.xml, dom=self.dom)


class XmlModelManager(xml_models.XmlModelManager):

    def create(self, **kw):
        """ put docstring here """
        query = XmlModelQuery(self, self.model, headers=self.headers).create(**kw)
        return query


class XmlModelQuery(xml_models.XmlModelQuery):

    def create(self, **kw):
        """ put docstring here """
        object = classobj('Model', (BaseModel,), {})
        for key in kw.keys():
            setattr(object, key, kw[key])
        (filepath, filename) = os.path.split(self.model.template_name)
        (shortname, extension) = os.path.splitext(filename)
        template_name = os.path.join(filepath, '%s_initial%s' % (shortname, extension))
        context = Context(object)
        xml_response = render_to_string(template_name, dictionary=kw, context_instance = context)
        return self.model(xml=xml_response)


class ModelBase(xml_models.ModelBase):

    def __init__(cls, name, bases, attrs):
        super(ModelBase, cls).__init__(name, bases, attrs)
        if not hasattr(cls, "_fields"):
            cls._fields = {}
        for field_name in attrs.keys():
            if isinstance(attrs[field_name], xml_models.BaseField):
                cls._fields[field_name] = attrs[field_name]
        if attrs.has_key("finders"):
            setattr(cls, "objects", XmlModelManager(cls, attrs["finders"]))
        else:
            setattr(cls, "objects", XmlModelManager(cls, {}))

    def __setattr__(cls, attr, value):
        if isinstance(value, (datetime.datetime, datetime.date)) and cls._fields.has_key(attr):
            value = value.strftime(cls._fields[attr].date_format)
        super(ModelBase, cls).__setattr__(attr, value)


class Model:
    __metaclass__ = ModelBase
    __doc__="""A model can be constructed with either an xml string, or an appropriate document supplied by
    the xpath_twister.domify() method.
    
    An example:
    
    class Person(xml_models.Model):
        namespace="urn:my.default.namespace"
        name = xml_models.CharField(xpath"/Person/@Name", default="John")
        nicknames = xml_models.CollectionField(CharField, xpath="/Person/Nicknames/Name")
        addresses = xml_models.CollectionField(Address, xpath="/Person/Addresses/Address")
        date_of_birth = xml_models.DateField(xpath="/Person/@DateOfBirth", date_format="%d-%m-%Y")
    """
    def __init__(self, xml=None, dom=None):
        self._xml = xml
        self._dom = dom
        self._cache = {}
        self.validate_on_load()

    """Override on your model to perform validation when the XML data is first passed in. This is to ensure the xml returned
       conforms to the validation rules.  We use this because some records are no use to us if they don't contain certain
       data."""
    def validate_on_load(self):
        pass

    def _get_xml(self):
        if self._dom is None:
            try :
                self._dom = xpath.domify(self._xml)
            except Exception, e:
                print self._xml
                print str(e)
                raise e
        return self._dom
        
    def _set_value(self, field, value):
        self._cache[field] = value
        
    def _parse_field(self, field):
        if not self._cache.has_key(field):
            namespace = None
            if hasattr(self, 'namespace'):
                namespace = self.namespace
            self._cache[field] = field.parse(self._get_xml(), namespace)
        return self._cache[field]


class BaseModel(Model):
    """ Base Model Entity """

    __model__ = '__not_implemented__'
    template_name = '__not_implemented__'

    def process_response(self, payload=None, headers={}, prefix='tasks'):
        """ put docstring here """
        response = rest_client.Client("").POST(url=DEFAULT_POST_URL % prefix, payload=payload, headers=headers)
        if not response.content:
            raise ObjectDoesNotExist, self
        if response.response_code == 404:
            raise ObjectDoesNotExist, self
        content = response.content.read()
        if not content:
            raise ObjectDoesNotExist, self
        return content

    def validate_on_load(self):
        """ put docstring here """
        GETResponseBase(object=self, xml=self._xml).register()

    def save(self, commit=True):
        """ put docstring here """
        # was:
        #output = render_to_string(self.template_name, {'object': self})
        context = Context()
        context.update(
            {
                'object':self,
                'date': self.date,
             }
        )
        output = render_to_string(self.template_name, context_instance = context)
        if commit == True:
            tasks_response = POSTResponseBase(object=self, xml=self.process_response(payload=output)).register()
            self.uid = tasks_response.uid
            self.lastUpdatedUid = tasks_response.lastUpdatedUid
        return self

