from qp.fill.directory import get_crumb_tree, Directory, gen_crumb_tree_menu_items
from qp.fill.html import htmltag, href
from qp.lib.spec import Specified, add_getters_and_setters, nspec, string, spec, specify, instance
from qp.pub.common import get_path, get_publisher
from urlparse import urljoin


class MenuItem (Specified):

    css_class_is = nspec(
        string,
        "The name of the css class that should be used for this menu item, if known.")
    crumb_is = spec(
        string,
        "The label that is shown on this menu item")
    title_is = nspec(
        string,
        "The longer explanation of this menu item, if known.")
    relative_path_is = nspec(
        string,
        "The path that leads to this menu item.")
    path_is = nspec(
        string,
        "The full path that leads to this menu item.")
    sub_menu_is = nspec(
        instance('Menu'),
        "The the next menu down, if this item is selected.")
    selected_in_is = nspec(
        instance("Menu"),
        "The menu this item is selected in, if any")

    def __init__(self, relative_path=None, crumb=None, title=None, css_class=None):
        if relative_path is not None:
            path = urljoin(get_path(), relative_path)
        else:
            path = None
        specify(self,
            relative_path=relative_path,
            crumb=crumb,
            title=title,
            css_class=css_class,
            selected_in=None,
            sub_menu=None,
            path=path)

    def is_selected(self):
        return self.get_selected_in() is not None

    def is_final_selected(self):
        if self.get_path():
            if self.get_path() == get_path():
                return True
            if self.get_path() + '/' == get_path():
                return True
        return False

    def get_css_class_list(self, classes=None):
        if classes is None:
            classes = []
        else:
            classes = list(classes)
        classes.append("menu_item")
        if self.get_css_class():
            classes.append(self.get_css_class())
        if self.get_last_component() is not None:
            classes.append("menu_item_%s" % self.get_last_component().replace('.', '_'))
        if self.is_final_selected():
            classes.append("final_selected_menu_item")
        if self.is_selected():
            classes.append("selected_menu_item")
        return sorted(set(classes))

    def get_last_component(self):
        if self.get_path():
            return self.get_path().rsplit('/', 1)[1]

    def __str__(self):
        return "<MenuItem %s %s>" % (self.get_path(), self.get_crumb())

add_getters_and_setters(MenuItem)


class Menu (Specified):

    menu_items_is = spec(
        [MenuItem],
        "The MenuItems in this Menu")

    path_is = spec(
        string,
        "The full path that leads to this menu.")

    selected_menu_item_is = nspec(
        MenuItem,
        "The element of menu_items that is selected.")

    def __init__(self, menu_items):
        specify(self,
            menu_items=menu_items,
            path=menu_items[0].get_path().rsplit('/', 1)[1],
            selected_menu_item=None)
        candidates = [menu_item
            for menu_item in self.get_menu_items()
            if menu_item.get_path() and get_path().startswith(menu_item.get_path())]
        if candidates:
            self.set_selected_menu_item(
                max(candidates, key=lambda x: len(x.get_path() or '')))
            self.get_selected_menu_item().set_selected_in(self)

    def is_selected(self):
        return self.get_selected_menu_item() is not None

    def is_final_selected(self):
        if self.get_path():
            if self.get_path() == get_path():
                return True
            if self.get_path() + '/' == get_path():
                return True
        return False

    def get_css_class_list(self, classes=None):
        if classes is None:
            classes = []
        else:
            classes = list(classes)
        classes.append("menu")
        if self.is_selected():
            classes.append("selected_menu")
        else:
            classes.append("unselected_menu")
        if self.is_final_selected():
            classes.append("final_selected_menu")
        return sorted(set(classes))

    def get_last_component(self):
        components = self.get_path().rsplit('/', 1)
        return components[-1]

    def get_unselected_menu_items(self):
        selected = self.get_selected_menu_item()
        return [menu_item for menu_item in self.get_menu_items()
            if menu_item.get_path() is not None and menu_item is not selected]

    def __str__(self):
        return '\n'.join(map(str, self.menu_items)) + '\n'

add_getters_and_setters(Menu)

def get_directory_for_path(path):
    """
    Returns the last Directory instance it encounters in trying to traverse the given path.
    """
    assert path and path.startswith('/'), repr(path)
    components = path.split('/')[1:]
    directory = get_publisher().get_root_directory()
    for component in components:
        name = directory._q_translate(component)
        if name is None:
            obj = directory._q_lookup(component)
        else:
            obj = getattr(directory, name)
        if isinstance(obj, Directory):
            directory = obj
        else:
            break
    return directory

def get_menu_for_directory(directory, base_path):
    menu_items = [
        MenuItem(relative_path=relative_path, crumb=crumb, title=title, css_class=css_class)
        for relative_path, crumb, title, css_class in gen_crumb_tree_menu_items(base_path, directory)]
    return Menu(menu_items)

def get_menu_for_path(path):
    return get_menu_for_directory(get_directory_for_path(path), path)

def format_menu_for_path(path=None, classes=None):
    if path is None:
        path = get_path()
        if not path.endswith('/'):
            path = path.rsplit('/', 1)[0] + '/'
    return format_menu(get_menu_for_path(path), classes=classes)

def get_menu_list():
    menu_list = []
    for crumb_list in get_crumb_tree():
        menu_items = [
            MenuItem(relative_path=relative_path, crumb=crumb, title=title, css_class=css_class)
            for relative_path, crumb, title, css_class in crumb_list]
        menu_list.append(Menu(menu_items))
        if len(menu_list) > 1:
            selected_menu_item = menu_list[-2].get_selected_menu_item()
            if selected_menu_item is not None:
                selected_menu_item.set_sub_menu(menu_list[-1])
    return menu_list

def format_menu_item:xml(menu_item, classes=None, depth=None, nested=True):
    href(menu_item.get_path(), menu_item.get_crumb(), title=menu_item.get_title(), classes=classes)
    if nested:
        format_menu(menu_item.get_sub_menu(), depth=depth + 1, nested=nested)

def format_menu:xml(menu, classes=None, depth=0, nested=True):
    if menu:
        if classes is None:
            classes = []
        if depth is not None:
            classes.append("menu_depth_%s" % depth)
        '\n\n'
        htmltag('div', classes=menu.get_css_class_list(classes=classes))
        '\n'
        selected_menu_item = menu.get_selected_menu_item()
        for menu_item_position, menu_item in enumerate(menu.get_menu_items()):
            classes = []
            if depth is not None:
                classes.append("menu_item_depth_%s" % depth)
            classes.append("menu_item_pos%s" % menu_item_position)
            classes.append("menu_item_pos%s" % (menu_item_position - len(menu.get_menu_items())))
            if menu_item is selected_menu_item:
                classes.append("selected_menu_item")
            else:
                classes.append("unselected_menu_item")
            classes.append("menu_item_component_%s" % menu_item.get_last_component())
            format_menu_item(menu_item, classes=classes, depth=depth, nested=nested)
            '\n'
        '<div class="close"></div>'
        '</div>\n'

def format_menu_list:xml(menu_list=None, classes=None):
    if menu_list is None:
        menu_list = get_menu_list()
    if classes is None:
        classes = []
    classes.append('menu_list')
    classes.append('menu_list_nested')
    format_menu(menu_list[0], classes=classes, depth=0)

def format_flat_menu_list:xml(menu_list=None, classes=None):
    if menu_list is None:
        menu_list = [menu for menu in get_menu_list() if menu.get_unselected_menu_items()]
    if classes is None:
        classes = []
    classes.append('menu_list')
    classes.append('menu_list_flat')
    '\n'
    htmltag('div', classes=classes)
    '\n'
    for menu_position, menu in enumerate(menu_list):
        classes = ["menu"]
        classes.append("menu_pos%s" % menu_position)
        classes.append("menu_pos%s" % (menu_position - len(menu_list)))
        format_menu(menu, classes=classes, depth=menu_position, nested=False)
    '<div class="close"></div>'
    '\n</div>\n'
