# -*- test-case-name: xmantissa.test.test_webnav -*-

from epsilon.structlike import record

from zope.interface import implements

from xmantissa.ixmantissa import ITab

class TabMisconfiguration(Exception):
    def __init__(self, info, tab):
        Exception.__init__(
            self,
            "Inconsistent tab item factory information",
            info, tab)

TabInfo = record('priority storeID children linkURL authoritative',
                 authoritative=None)

class Tab(object):
    """Represent part or all of the layout of a single navigation tab.

    @ivar name: This tab's name.

    @ivar storeID: A /-separated string containing URL segments to be
    rendered as part of a link on the web.

    @ivar priority: A float between 0 and 1 indicating the relative ordering of
    this tab amongst its peers.  Higher priorities sort sooner.

    @ivar children: A tuple of tabs beneath this one.

    @ivar authoritative: A flag indicating whether this instance of the
    conceptual tab with this name takes precedent over any other instance of
    the conceptual tab with this name.  It is an error for two instances of the
    same conceptual tab to be authoritative.

    """

    _item = None
    implements(ITab)

    def __init__(self, name, storeID, priority, children=(), authoritative=True, linkURL=None):
        self.name = name
        self.storeID = storeID
        self.priority = priority
        self.children = tuple(children)
        self.authoritative = authoritative
        self.linkURL = linkURL

    def __repr__(self):
        return '<%s%s %r/%0.3f %r [%r]>' % (self.authoritative and '*' or '',
                                            self.__class__.__name__,
                                            self.name,
                                            self.priority,
                                            self.storeID,
                                            self.children)

    def __iter__(self):
        raise TypeError("%r are not iterable" % (self.__class__.__name__,))

    def __getitem__(self, key):
        """Retrieve a sub-tab from this tab by name.
        """
        tabs = [t for t in self.children if t.name == key]
        assert len(tabs) < 2, "children mis-specified for " + repr(self)
        if tabs:
            return tabs[0]
        raise KeyError(key)

    def pathFromItem(self, item, avatar):
        """
        @param item: A thing that we linked to, and such.

        @return: a list of [child, grandchild, great-grandchild, ...] that
        indicates a path from me to the navigation for that item, or [] if
        there is no path from here to there.
        """
        for subnav in self.children:
            subpath = subnav.pathFromItem(item, avatar)
            if subpath:
                subpath.insert(0, self)
                return subpath
        else:
            myItem = self.loadForAvatar(avatar)
            if myItem is item:
                return [self]
        return []

def getTabs(navElements):
    # XXX TODO: multiple levels of nesting, this is hard-coded to 2.
    # Map primary tab names to a TabInfo
    primary = {}

    # Merge tab information from all nav plugins into one big structure
    for plg in navElements:
        for tab in plg.getTabs():
            if tab.name not in primary:
                primary[tab.name] = TabInfo(
                    priority=tab.priority,
                    storeID=tab.storeID,
                    children=list(tab.children),
                    linkURL=tab.linkURL)
            else:
                info = primary[tab.name]

                if info.authoritative:
                    if tab.authoritative:
                        raise TabMisconfiguration(info, tab)
                else:
                    if tab.authoritative:
                        info.authoritative = True
                        info.priority = tab.priority
                        info.storeID = tab.storeID
                info.children.extend(tab.children)

    # Sort the tabs and their children by their priority
    def key(o):
        return -o.priority

    resultTabs = []

    for (name, info) in primary.iteritems():
        info.children.sort(key=key)

        resultTabs.append(
            Tab(name, info.storeID, info.priority, info.children))

    resultTabs.sort(key=key)

    return resultTabs

def setTabURLs(tabs, webTranslator):
    """
    Sets the C{linkURL} attribute on each L{Tab} instance
    in C{tabs} that does not already have it set

    @param tabs: sequence of L{Tab} instances
    @param webTranslator: L{xmantissa.ixmantissa.IWebTranslator}
                          implementor

    @return: None
    """

    for tab in tabs:
        if not tab.linkURL:
            tab.linkURL = webTranslator.linkTo(tab.storeID)
        setTabURLs(tab.children, webTranslator)

def getSelectedTab(tabs, forURL):
    """
    Returns the tab that should be selected when the current
    resource lives at C{forURL}.  Call me after L{setTabURLs}

    @param tabs: sequence of L{Tab} instances
    @param forURL: L{nevow.url.URL}

    @return: L{Tab} instance
    """

    flatTabs = []

    def flatten(tabs):
        for t in tabs:
            flatTabs.append(t)
            flatten(t.children)

    flatten(tabs)
    forURL = '/' + forURL.path

    for t in flatTabs:
        if forURL == t.linkURL:
            return t

    flatTabs.sort(key=lambda t: len(t.linkURL), reverse=True)

    for t in flatTabs:
        if not t.linkURL.endswith('/'):
            linkURL = t.linkURL + '/'
        else:
            linkURL = t.linkURL

        if forURL.startswith(linkURL):
            return t


syntax highlighted by Code2HTML, v. 0.9.1