
"""
Woven object collections.

THIS MODULE IS HIGHLY EXPERIMENTAL AND MAY BE DEPRECATED SOON.
"""

from __future__ import nested_scopes

__version__ = "$Revision: 1.12 $"[11:-2]

# System Imports
import sys
import os

# Twisted Imports

from twisted.internet.defer import Deferred

from twisted.web.resource import Resource, IResource
from twisted.web.static import redirectTo, addSlash, File, Data
from twisted.web.server import NOT_DONE_YET

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

# Sibling Imports
from twisted.web.woven.view import View

class _ChildJuggler(Resource):
    isLeaf = 1
    def __init__(self, d):
        Resource.__init__(self)
        self.d = d

    def render(self, request):
        # TODO: getChild stuffs
        self.d.addCallback(self._cbChild, request).addErrback(
            self._ebChild,request)
        return NOT_DONE_YET

    def _cbChild(self, child, request):
        request.render(child)
        return child

    def _ebChild(self, reason, request):
        request.processingFailed(reason)
        return reason


class ModelLoader(Resource):
    """Resource for loading models.  (see loadModel)
    """
    def __init__(self, parent, templateFile=None):
        Resource.__init__(self)
        self.parent = parent
        self.templateFile = templateFile

    def modelClass(self, other):
        return other

    def getChild(self, path, request):
        d = self.loadModel(path, request)
        templateFile = (self.templateFile or self.__class__.__name__+'.html')
        d.addCallback(
            lambda result: self.parent.makeView(self.modelClass(result),
                                                templateFile, 1))
        return _ChildJuggler(d)

    def loadModelNow(self, path, request):
        """Override this rather than loadModel if your model-loading is
        synchronous.
        """
        raise NotImplementedError("%s.loadModelNow" % (reflect.qual(self.__class__)))

    def loadModel(self, path, request):
        """Load a model, for the given path and request.

        @rtype: L{Deferred}
        """
        from twisted.internet.defer import execute
        return execute(self.loadModelNow, path, request)


from twisted.web import microdom
from twisted.web import domhelpers

class TapestryView(View):
    tapestry = None
    parentCount = 0
    def lookupTemplate(self, request):
        fullFile = os.path.join(self.templateDirectory, self.templateFile)
        document = microdom.parse(open(fullFile))
        if self.tapestry:
            return self.tapestry.templateMutate(document, self.parentCount)
        return document

class Tapestry(Resource):
    """
    I am a top-level aggregation of Woven objects: a full `site' or
    `application'.
    """
    viewFactory = TapestryView
    def __init__(self, templateDirectory, viewFactory=None, metaTemplate=None):
        """
        Create a tapestry with a specified template directory.
        """
        Resource.__init__(self)
        self.templateDirectory = templateDirectory
        if viewFactory is not None:
            self.viewFactory = viewFactory
        if metaTemplate:
            self.metaTemplate = microdom.parse(open(
                os.path.join(templateDirectory, metaTemplate)))
        else:
            self.metaTemplate = None

    def templateMutate(self, document, parentCount=0):
        if self.metaTemplate:
            newDoc = self.metaTemplate.cloneNode(1)
            if parentCount:
                dotdot = parentCount * '../'
                for ddname in 'href', 'src', 'action':
                    for node in domhelpers.findElementsWithAttribute(newDoc, ddname):
                        node.setAttribute(ddname, dotdot + node.getAttribute(ddname))
            ttl = domhelpers.findNodesNamed(newDoc, "title")[0]
            ttl2 = domhelpers.findNodesNamed(document, "title")[0]
            ttl.childNodes[:] = []
            for n in ttl2.childNodes:
                ttl.appendChild(n)
            body = domhelpers.findElementsWithAttribute(newDoc, "class", "__BODY__")[0]
            body2 = domhelpers.findNodesNamed(document, "body")[0]
            ndx = body.parentNode.childNodes.index(body)
            body.parentNode.childNodes[ndx:ndx+1] = body2.childNodes
            for n in body2.childNodes:
                n.parentNode = body.parentNode
            f = open("garbage.html", "wb")
            f.write(newDoc.toprettyxml())
            return newDoc
        return document

    def makeView(self, model, name, parentCount=0):
        v = self.viewFactory(model, name)
        v.parentCount = parentCount
        v.templateDirectory = self.templateDirectory
        v.tapestry = self
        v.importViewLibrary(self)
        return v

    def getSubview(self, request, node, model, viewName):
        mod = sys.modules[self.__class__.__module__]
        # print "I'm getting a subview", mod, viewName

        # try just the name
        vm = getattr(mod, viewName, None)
        if vm:
            return vm(model)

        # try the name + a V
        vn2 = "V"+viewName.capitalize()
        vm = getattr(mod, vn2, None)
        if vm:
            return vm(model)

        vm = getattr(self, 'wvfactory_'+viewName, None)
        if vm:
            return vm(request, node, model)

    def render(self, request):
        return redirectTo(addSlash(request), request)

    def getChild(self, path, request):
        if path == '': path = 'index'
        path = path.replace(".","_")
        cm = getattr(self, "wchild_"+path, None)
        if cm:
            p = cm(request)
            if isinstance(p, Deferred):
                return _ChildJuggler(p)
            adapter = components.getAdapter(p, IResource, None)
            if adapter is not None:
                return adapter
        # maybe we want direct support for ModelLoader?
        # cl = getattr(self, "wload_"+path, None) #???
        return Resource.getChild(self, path, request)
