

import types
import pickle
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 xml(str):
    """Raw xml marker
    """


class directive(str):
    """Marker for a directive in a template
    """
    def __repr__(self):
        return "directive('%s')" % self


class Tag(object):
    """Tag instances represent html tags with a tag name, attributes,
    and children. Tag instances can be constructed using the Prototype
    tags in the 'tags' module, or may be constructed directly with a tag
    name. Tags have two special methods, __call__ and __getitem__,
    which make representing trees of XML natural using pure python
    syntax. See the docstrings for these methods for more details.
    """
    specials = ['data', 'renderer', 'observer', 'pattern', 'slot']

    produceContext = False

    def __init__(self, tag, attributes=None, children=None, specials=None):
        self.tagName = 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. This is implemented using
        __call__ because it then allows the natural syntax:
        
        table(width="100%", height="50%", border="1")
        
        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.
        
        pattern and slot attributes are special, and are not exported
        to the final page, but are used to locate nodes tagged with
        a given pattern or slot id.
        """
        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):
        """Add children to this tag. Multiple children may be added by
        passing a tuple or a list. Children may be other tag instances,
        strings, functions, or any object which has a registered
        ISerializable adapter.
        
        This is implemented using __getitem__ because it then allows
        the natural syntax:
        
        html[
            head[
                title["Hello World!"]
            ],
            body[
                "This is a page",
                h3["How are you!"],
                div(style="color: blue")["I hope you are fine."]
            ]
        ]
        """
        if not isinstance(children, (list, tuple)):
            children = [children]
        self.children.extend(children)
        return self

    def clone(self, deep=True):
        """Return a clone of this tag. If deep is True, clone all of this
        tag's children. Otherwise, the children list of the clone will
        be empty.
        """
        if deep:
            newchildren = [
                hasattr(ch, 'clone') and ch.clone() or ch
                for ch in self.children
            ]
        else:
            newchildren = []
        return Tag(self.tagName, attributes=self.attributes.copy(), children=newchildren, specials=self._specials.copy())

    def clear(self):
        """Clear any existing children from this tag.
        """
        self.children = []

    def __repr__(self):
        return "Tag(%r, attributes=%r, children=%s, specials=%r)" % (self.tagName, self.attributes, self.children, self._specials)


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 TagInContext(object):
    def __init__(self, tag, context):
        self.tag = tag
        self.context = context

    def __call__(self, **kw):
        self.tag(**kw)

    def __getitem__(self, item):
        self.tag[item]

    def __getattr__(self, name):
        return getattr(self.tag, name)

    def __repr__(self):
        children = self.tag.children and ", children=%r" % self.tag.children or ''
        return "TagInContext(%r, specials=%s%s)" % (self.tag.tagName, self.tag._specials, children)


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):
        yield '<%s />' % self.original
components.registerAdapter(ProtoSerializer, Proto, ISerializable)


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

    def serialize(self, context, stream):
        visible = bool(self.original.tagName)
        singleton = not self.original.renderer and not self.original.children and not self.original.data
        special = context.precompile and self.original._specials
        if special:
            context = context.with(self.original, data=self.original.data, renderer=self.original.renderer, observer=self.original.observer)
            context.tag.children = flatten(ISerializable(context.tag.children).serialize(context, stream))
            yield TagInContext(context.tag, context)
        else:
            if visible:
                yield '<%s' % self.original.tagName
                if self.original.attributes:
                    yield ' '
                    yield ' '.join(['%s="%s"' % (k, v) for (k, v) in self.original.attributes.items()])
            if singleton:
                if visible:
                    yield ' />'
            else:
                if visible:
                    yield '>'
                # TODO: Make this less buggy.
                if context.locate(IData) != self.original.data:
                    context = context.with(self.original, data=self.original.data, renderer=self.original.renderer, observer=self.original.observer)                    
                if self.original.renderer:
                    toBeRenderedBy = self.original.renderer
                    self.original.renderer = None
                    yield ISerializable(toBeRenderedBy).serialize(context, stream)
                    self.original.wasRenderedBy = toBeRenderedBy
                elif self.original.children:
                    for child in self.original.children:
                        yield ISerializable(child).serialize(context, stream)
                elif self.original.data:
                    yield ISerializable(self.original.data).serialize(context, stream)
                if visible:
                    yield '</'
                    yield self.original.tagName
                    yield '>'
components.registerAdapter(TagSerializer, Tag, ISerializable)


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

    def serialize(self, context, stream):
        ## quote it
        yield 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
        yield 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:
            yield ISerializable(item).serialize(context, stream)
components.registerAdapter(ListSerializer, list, ISerializable)
components.registerAdapter(ListSerializer, tuple, ISerializable)


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

    def serialize(self, context, stream):
        yield self.original
components.registerAdapter(XmlSerializer, xml, ISerializable)


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

    def serialize(self, context, stream):
        if context.precompile:
            yield self.original
        else:
            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)
            yield ISerializable(result).serialize(context, stream)
components.registerAdapter(FunctionSerializer, types.FunctionType, ISerializable)
components.registerAdapter(FunctionSerializer, type(str), ISerializable)


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

    def serialize(self, context, stream):
        originalContext = self.original.context
        tag = self.original.tag
        originalContext.precompile = context.precompile
        yield flatten(ISerializable(tag).serialize(originalContext, stream))
components.registerAdapter(TagInContextSerializer, TagInContext, ISerializable)


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; the result may be adapted to IGettable
        if possible.
        
        Return None if the adaptee does not have a child with the
        given name.
        
        TODO: Maybe returning None is bad, and .child should just
        raise whatever exception is natural
        """


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):
        # The directive (self.original) is the data at depth=1
        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):
        # The function (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, precompile=False):
        self.tag = tag
        self.parent = parent
        self._rememberances = {}
        self.precompile = precompile

    def __repr__(self):
        return "Context(_rememberances=%r)" % self._rememberances

    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
        return self

    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, self.precompile)
        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.
        """
        keeplooking = True
        while keeplooking:
            keeplooking = False
            for x in specialMatches(self.tag, 'pattern', pattern):
                keeplooking = True
                yield x.clone()
        raise RuntimeError, "Pattern %s was not found." % pattern


    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)


def flatten(gen):
    results = []
    accumulator = ''
    for item in gen:
        if isinstance(item, types.StringTypes):
            accumulator += item
        else:
            if isinstance(item, types.GeneratorType):
                for sub in flatten(item):
                    if isinstance(sub, types.StringTypes):
                        accumulator += sub
                    else:
                        if accumulator:
                            results.append(accumulator)
                        results.append(sub)
                        accumulator = ''
            elif isinstance(item, types.ListType):
                if accumulator:
                    results.append(accumulator)
                results.extend(item)
                accumulator=''
            else:
                if accumulator:
                    results.append(accumulator)
                results.append(item)
                accumulator = ''
    if accumulator:
        results.append(accumulator)
    return results


from copy import deepcopy


class Renderer(resource.Resource):
    def __init__(self, doc):
        context = WovenContext(precompile=True).remember(self, resource.IResource)
        self.doc = doc
        # uncomment this...
        #self.doc = flatten(ISerializable(doc).serialize(context, None))
        from pprint import pprint
        pprint(self.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().remember(self, resource.IResource)
        start = now()
        # and comment this...
        doc = self.doc.clone()
        # and uncomment this...
        # doc = self.doc
        result = flatten(ISerializable(doc).serialize(context, request))
        print "TIME", now() - start
        request.write(result[0])
        request.finish()
        return server.NOT_DONE_YET

