

import types
from time import time as now
from StringIO import StringIO

from twisted.python import components
from twisted.python.reflect import qual

from twisted.web import resource, server


class Proto(str):
    """Proto is a string subclass. Instances of Proto, which are constructed
    with a string, will construct Tag instances in response to __call__
    and __getitem__, delegating responsibility to the tag.
    """
    def __call__(self, **kw):
        return Tag(self)(**kw)

    def __getitem__(self, children):
        return Tag(self)[children]


class raw(str):
    """Raw xml marker
    """


class directive(str):
    """Marker for a directive in a template
    """


class Tag(object):
    specials = ['data', 'renderer', 'observer', 'pattern', 'slot']

    def __init__(self, tag, attributes=None, children=None, specials=None):
        self.tag = tag
        if attributes is None:
            self.attributes = {}
        else:
            self.attributes = attributes
        if children is None:
            self.children = []
        else:
            self.children = children
        if specials is None:
            self._specials = {}
        else:
            self._specials = specials

    def __call__(self, **kw):
        """Change attributes of this tag. Three magic attributes may
        have values other than string values, and will be remembered
        in the context stack for later retrieval:
        
        data
        renderer
        observer

        See WovenContext.with for details.
        """
        if not kw:
            return self

        for name in self.specials:
            if kw.has_key(name):
                setattr(self, name, kw[name])
                del kw[name]
        for k, v in kw.items():
            if k[0] == '_':
                k = k[1:]
            assert isinstance(v, str), "Non-special attribute values must be strings; %s found." % v
            self.attributes[k] = v
        return self

    def __getitem__(self, children):
        if not isinstance(children, (list, tuple)):
            children = [children]
        self.children.extend(children)
        return self

    def clone(self, deep=True):
        if deep:
            newchildren = [
                hasattr(ch, 'clone') and ch.clone() or ch
                for ch in self.children
            ]
        else:
            newchildren = []
        return Tag(self.tag, attributes=self.attributes.copy(), children=newchildren, specials=self._specials.copy())

    def __repr__(self):
        return "Tag(%r, attributes=%r)" % (self.tag, self.attributes)


def makeAccessors(special):
    def getSpecial(self):
        return self._specials.get(special)

    def setSpecial(self, data):
        self._specials[special] = data

    return getSpecial, setSpecial

for name in Tag.specials:
    setattr(Tag, name, property(*makeAccessors(name)))


class Invisible(Tag):
    """A Tag class which has a serializer which renders children
    but nothing for the Invisible instance itself.
    """
    pass


class ISerializable(components.Interface):
    def serialize(self, context, stream):
        """Serialize the adaptee to the given stream, with the given context
        stack if necessary.
        """


class ProtoSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        stream.write('<')
        stream.write(self.original)
        stream.write(' />')
components.registerAdapter(ProtoSerializer, Proto, ISerializable)


class TagSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        stream.write('<')
        stream.write(self.original.tag)
        if self.original.attributes:
            stream.write(' ')
            stream.write(
                ' '.join(
                    ['%s="%s"' % (k, v) for (k, v) in self.original.attributes.items()]
                )
            )
        handleChildren = None

        if not self.original.renderer and not self.original.children and not self.original.data:
            stream.write(' />')
        else:
            context = context.with(self.original, data=self.original.data, renderer=self.original.renderer, observer=self.original.observer)
            stream.write('>')
            if self.original.renderer:
                ISerializable(self.original.renderer).serialize(context, stream)
            elif self.original.children:
                for child in self.original.children:
                    ISerializable(child).serialize(context, stream)
            elif self.original.data:
                ISerializable(self.original.data).serialize(context, stream)
            stream.write('</')
            stream.write(self.original.tag)
            stream.write('>')
components.registerAdapter(TagSerializer, Tag, ISerializable)


class StringSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        ## quote it
        stream.write(self.original)
components.registerAdapter(StringSerializer, str, ISerializable)
components.registerAdapter(StringSerializer, unicode, ISerializable)


class StringCastSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        ## quote it
        stream.write(str(self.original))
components.registerAdapter(StringCastSerializer, int, ISerializable)
components.registerAdapter(StringCastSerializer, float, ISerializable)
components.registerAdapter(StringCastSerializer, long, ISerializable)
components.registerAdapter(StringCastSerializer, bool, ISerializable)


class ListSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        for item in self.original:
            ISerializable(item).serialize(context, stream)
components.registerAdapter(ListSerializer, list, ISerializable)
components.registerAdapter(ListSerializer, tuple, ISerializable)


class InvisibleSerializer(components.Adapter):
    __implements__ = ISerializable,
    
    def serialize(self, context, stream):
        if self.original.renderer is not None:
            ISerializable(self.original.renderer).serialize(context, stream)
components.registerAdapter(InvisibleSerializer, Invisible, ISerializable)


class XmlSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        stream.write(self.original)
components.registerAdapter(XmlSerializer, raw, ISerializable)


class FunctionSerializer(components.Adapter):
    __implements__ = ISerializable,

    def serialize(self, context, stream):
        data = convertToData(context, context.locate(IData))
        code = getattr(self.original, 'func_code', None)
        nocontext = code is None or code.co_argcount == 1
        if nocontext:
            result = self.original(data)
        else:
            result = self.original(context, data)
        ISerializable(result).serialize(context, stream)
components.registerAdapter(FunctionSerializer, types.FunctionType, ISerializable)
components.registerAdapter(FunctionSerializer, type(str), ISerializable)


def serialize(obj):
    sio = StringIO()
    ISerializable(obj).serialize(WovenContext(), sio)
    return sio.getvalue()


class IData(components.Interface):
    """Any python object to be used as model data to be passed
    to view functions. Used for marking the context stack only.
    
    ANY python object is said to implement IData.
    """


class IGettable(components.Interface):
    def get(self, context):
        """Return the data
        
        Return any object
        """

class ISettable(components.Interface):
    def set(self, context, data):
        """Set the data

        This might be removed.
        """

class IContainer(components.Interface):
    def child(self, context, name):
        """Return a conceptual child; an attribute, or a key,
        or the result of a function call.
    
        Returns any object; may be adapted to IAccessor if necessary
        
        Return None if the adaptee does not have a child with the
        given name.
        """


def convertToData(context, obj):
    adapter = IGettable(obj, None, persist=False)
    if adapter is not None:
        obj = adapter.get(context)
    return obj


class DirectiveAccessor(components.Adapter):
    __implements__ = IGettable,

    def get(self, context):
        data = context.locate(IData, depth=2)
        container = IContainer(
            data,
            None,
            persist=False
        )
        if container is None:
            raise RuntimeError, "%s does not implement IContainer, and there is no registered adapter." % data
        child = container.child(context, self.original)
        return convertToData(context, child)
components.registerAdapter(DirectiveAccessor, directive, IGettable)


class FunctionAccessor(components.Adapter):
    __implements__ = IGettable,
    def get(self, context):
        # self.original is the data at depth=1
        return self.original(context, context.locate(IData, depth=2))
components.registerAdapter(FunctionAccessor, types.FunctionType, IGettable)


class DictionaryContainer(components.Adapter):
    __implements__ = IContainer, IGettable
    
    def child(self, context, name):
        return self.original.get(name, None)

    def get(self, context):
        return dict(
            [(k, convertToData(context, v)) for (k, v) in self.original.items()]
        )
components.registerAdapter(DictionaryContainer, types.DictType, IContainer)
components.registerAdapter(DictionaryContainer, types.DictType, IGettable)


class ListContainer(components.Adapter):
    __implements__ = IContainer, IGettable

    def child(self, context, name):
        try:
            i = int(name)
        except ValueError:
            return None
        return self.original[i]

    def get(self, context):
        return [convertToData(context, x) for x in self.original]
components.registerAdapter(ListContainer, types.ListType, IContainer)
components.registerAdapter(ListContainer, types.TupleType, IGettable)


def specialMatches(tag, special, pattern):
    """Generate special attribute matches starting with the given tag;
    if a match is found, do not look any deeper below that match.
    """
    if getattr(tag, special, None) == pattern:
        yield tag
    else:
        for child in getattr(tag, 'children', []):
            for match in specialMatches(child, special, pattern):
                yield match


class WovenContext(object):
    cloned = False
    def __init__(self, parent=None, tag=None):
        self.tag = tag
        self.parent = parent
        self._rememberances = {}

    def __repr__(self):
        if self.parent:
            parent = "parent=%s" % self.parent
        else:
            parent = ''
        if self._rememberances:
            remember = "\n%s\n" % self._rememberances
        else:
            remember = ''
        return """Context(%s%s)""" % (remember, parent)

    def remember(self, adapter, interface=None):
        """Remember an object that implements some interfaces.
        Later, calls to locate which are passed an interface implemented
        by this object will return this object.
        
        If the 'interface' argument is supplied, this object will only
        be remembered for this interface, and not any of
        the other interfaces it implements.
        """
        if interface is None:
            interfaceList = components.getInterfaces(adapter)
            if not interfaceList:
                interfaceList = [IData]
        else:
            interfaceList = [interface]
        for interface in interfaceList:
            self._rememberances[qual(interface)] = adapter

    def with(self, tag, data=None, renderer=None, observer=None):
        """Remember a few things in a new context stack entry.
        
        tag: the tag the new context stack entry will be associated with.
        data: The data to pass to any rendering functions.
        renderer: The rendering function to use to render this node.
        observer: The observer function to notify when an
            event occurs to this node.
        """
        new = WovenContext(self, tag)
        if data is not None:
            new.remember(data, IData)
        if renderer is not None:
            # push a renderer onto the stack
            pass
        if observer is not None:
            # push an observer onto the stack
            pass
        return new

    def locate(self, interface, depth=1):
        """Locate an object which implements a given interface.
        Objects will be searched through the context stack top
        down.
        """
        key = qual(interface)
        if self.parent is None:
            return None
        if self._rememberances.has_key(key):
            depth -= 1
            if not depth:
                return self._rememberances[key]
        return self.parent.locate(interface, depth)

    def generatePatterns(self, pattern):
        """Generate clones of pattern tags forever, looping around to the beginning
        when we run out of unique matches.
        """
        while 1:
            for x in specialMatches(self.tag, 'pattern', pattern):
                yield x.clone()

    def locateSlots(self, slot):
        """Locate and return an existing slot in the current context tag, clearing children
        in the process. This will cause the existing tag to be cloned to avoid disturbing
        the template; you could return context.tag or another tag after calling this.
        """
        return specialMatches(self.tag, 'slot', slot)


class IRenderable(components.Interface):
    """Something that can be rendered
    """


class Renderer(resource.Resource):
    __implements__ = IRenderable,

    def __init__(self, doc):
        self.doc = doc
        resource.Resource.__init__(self)

    def getChild(self, name, request):
        if name == '':
            return self
        return resource.Resource.getChild(self, name, request)

    def render(self, request):
        context = WovenContext()
        context.remember(self, IRenderable)
        start = now()
        ISerializable(self.doc).serialize(context, request)
        print "TIME", now() - start
        request.finish()
        return server.NOT_DONE_YET

