# Source code of the Ubuntu-it website
# Copyright (C) 2018 Pietro Albini <pietroalbini@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import yaml
import flask

from .constants import UITWWW_DIR


class Navbar:

    def __init__(self):
        self.navbars = []
        self.match = {}
        self.generators = {}

    def add_generator(self, name, generator):
        """Add a subpages generator"""
        self.generators[name] = generator

    def current_navbars(self):
        """Get the list of current navbars"""
        if flask.request.endpoint is not None:
            matcher = (flask.request.endpoint, tuple(flask.request.view_args.items()))

            if matcher in self.match:
                return self._return_navbars(self.match[matcher])
            elif flask.request.endpoint in self.match:
                return self._return_navbars(self.match[flask.request.endpoint])

        return self._return_navbars([(0, -1)])

    def _return_navbars(self, navbars):
        """Return a list of navbars"""
        result = []
        for bar_id, active_id in navbars:
            bar = []
            for i, item in enumerate(self.navbars[bar_id]):
                bar.append((item[0], item[1](), i == active_id))
            result.append(bar)
        return result

    def install(self, app):
        """Install this navbar on the app"""
        with open(UITWWW_DIR + "/data/navbar.yml") as fn:
            config = yaml.safe_load(fn.read())
        self._prepare_navbar_cache(config, [])

        # Add the _navbars variable to the templates
        @app.context_processor
        def inject_navbar():
            return {
                "_navbars": self.current_navbars(),
            }

    def _prepare_navbar_cache(self, bar, parents):
        """Add the bar to the cache"""
        bar_id = len(self.navbars)
        self.navbars.append([])

        for item in bar:
            item.setdefault("endpoint-args", None)

            # Load the list of subs dynamically if requested
            if "generate-subs" in item:
                item["subs"] = self.generators[item["generate-subs"]]()

            # Add hte item to the navbar
            item_id = len(self.navbars[bar_id])
            self.navbars[bar_id].append((item["name"], self._url_of(item)))

            # Load sub-navbars recursively
            sub_id = None
            if "subs" in item:
                sub_id = self._prepare_navbar_cache(item["subs"], parents + [(bar_id, item_id)])

            if "endpoint" in item:
                # Generate the list of navbars to show for this match
                # If this item has childs the childs are shown
                navbars = parents + [(bar_id, item_id)]
                if sub_id is not None:
                    navbars.append((sub_id, -1))

                # Add a match for each endpoint pointing to the list of navbars
                match = {item["endpoint"]: item["endpoint-args"]}
                if "also-match" in item:
                    match.update(item["also-match"])
                for endpoint, args in match.items():
                    if args is None:
                        self.match[endpoint] = navbars
                    else:
                        self.match[(endpoint, tuple(args.items()))] = navbars

        return bar_id

    def _url_of(self, item):
        """Lazily the URL of an item"""
        if "external" in item:
            return lambda: item["external"]

        # This function return lambdas because flask.url_for is only available
        # during requests
        if item["endpoint-args"] is not None:
            return lambda: flask.url_for(item["endpoint"], **item["endpoint-args"])
        else:
            return lambda: flask.url_for(item["endpoint"])
