"""
=========================
flask_flatpages.flatpages
=========================

Flatpages extension.

"""

import operator
import os
from itertools import takewhile

import six
if six.PY3:
    from inspect import getfullargspec
else:
    from inspect import getargspec as getfullargspec
from flask import abort
from werkzeug.utils import cached_property, import_string

from . import compat
from .page import Page
from .utils import force_unicode, pygmented_markdown


class FlatPages(object):
    """A collection of :class:`Page` objects."""

    #: Default configuration for FlatPages extension
    default_config = (
        ('root', 'pages'),
        ('extension', '.html'),
        ('encoding', 'utf-8'),
        ('html_renderer', pygmented_markdown),
        ('markdown_extensions', ['codehilite']),
        ('extension_configs', {}),
        ('auto_reload', 'if debug'),
        ('case_insensitive', False),
        ('instance_relative', False)
    )

    def __init__(self, app=None, name=None):
        """Initialize FlatPages extension.

        :param app: Your application. Can be omitted if you call
                    :meth:`init_app` later.
        :type app: A :class:`~flask.Flask` instance
        :param name: The name for this FlatPages instance. Used for looking
                    up config values using
                        'FLATPAGES_%s_%s' % (name.upper(), key)
                    By default, no name is used, so configuration is
                    done by specifying config values using
                        'FLATPAGES_%s' % (key)
                    Typically, you only need to set this parameter if you
                    want to use multiple :class:`FlatPages instances within the
                    same Flask application.
        :type name: string

        .. versionchanged:: 0.6

           New parameter `name` to support multiple FlatPages instances.
        """
        self.name = name

        if name is None:
            self.config_prefix = 'FLATPAGES'
        else:
            self.config_prefix = '_'.join(('FLATPAGES', name.upper()))

        #: dict of filename: (page object, mtime when loaded)
        self._file_cache = {}

        if app:
            self.init_app(app)

    def __iter__(self):
        """Iterate on all :class:`Page` objects."""
        return compat.itervalues(self._pages)

    def config(self, key):
        """Read actual configuration from Flask application config.

        :param key: Lowercase config key from :attr:`default_config` tuple
        """
        return self.app.config['_'.join((self.config_prefix, key.upper()))]

    def get(self, path, default=None):
        """
        Return the :class:`Page` object at ``path``, or ``default`` if there is
        no such page.
        """
        # This may trigger the property. Do it outside of the try block.
        pages = self._pages
        try:
            return pages[path]
        except KeyError:
            return default

    def get_or_404(self, path):
        """
        Return the :class:`Page` object at ``path``, or raise Flask's 404 error
        if there is no such page.
        """
        page = self.get(path)
        if not page:
            abort(404)
        return page

    def init_app(self, app):
        """
        Use to initialize an application, useful for passing an app later and
        app factory patterns.

        :param app: your application
        :type app: a :class:`~flask.Flask` instance
        """
        # Store default config to application
        for key, value in self.default_config:
            config_key = '_'.join((self.config_prefix, key.upper()))
            app.config.setdefault(config_key, value)

        # Register function to forget all pages if necessary
        app.before_request(self._conditional_auto_reset)

        # And finally store application to current instance and current
        # instance to application
        if 'flatpages' not in app.extensions:
            app.extensions['flatpages'] = {}
        app.extensions['flatpages'][self.name] = self
        self.app = app

    def reload(self):
        """Forget all pages.

        All pages will be reloaded next time they're accessed.
        """
        try:
            # This will "unshadow" the cached_property.
            # The property will be re-executed on next access.
            del self.__dict__['_pages']
        except KeyError:
            pass

    @property
    def root(self):
        """Full path to the directory where pages are looked for.

        This corresponds to the `FLATPAGES_%(name)s_ROOT` config value,
        interpreted as relative to the app's root directory, or as relative
        to the app's instance folder if `FLATPAGES_%(name)s_INSTANCE_RELATIVE`
        is set to `True`.

        """
        if self.config('instance_relative'):
            root_dir = os.path.join(self.app.instance_path,
                                    self.config('root'))
        else:
            root_dir = os.path.join(self.app.root_path, self.config('root'))
        return force_unicode(root_dir)

    def _conditional_auto_reset(self):
        """Reset if configured to do so on new requests."""
        auto = self.config('auto_reload')
        if auto == 'if debug':
            auto = self.app.debug
        if auto:
            self.reload()

    def _load_file(self, path, filename):
        """
        Load file from file system and put it to cached dict as :class:`Path`
        and `mtime` tuple.
        """
        mtime = os.path.getmtime(filename)
        cached = self._file_cache.get(filename)

        if cached and cached[1] == mtime:
            page = cached[0]
        else:
            encoding = self.config('encoding')

            if compat.IS_PY3:
                with open(filename, encoding=encoding) as handler:
                    content = handler.read()
            else:
                with open(filename) as handler:
                    content = handler.read().decode(encoding)

            page = self._parse(content, path)
            self._file_cache[filename] = (page, mtime)

        return page

    @cached_property
    def _pages(self):
        """
        Walk the page root directory and return a dict of ``{unicode path: page
        object}``.
        """
        def _walker():
            """
            Walk over directory and find all possible flatpages, i.e. files
            which end with the string or sequence given by
            ``FLATPAGES_%(name)s_EXTENSION``.
            """
            for cur_path, _, filenames in os.walk(self.root):
                rel_path = cur_path.replace(self.root, '').lstrip(os.sep)
                path_prefix = tuple(rel_path.split(os.sep)) if rel_path else ()

                for name in filenames:
                    if not name.endswith(extension):
                        continue

                    full_name = os.path.join(cur_path, name)
                    name_without_extension = [name[:-len(item)]
                                              for item in extension
                                              if name.endswith(item)][0]
                    path = u'/'.join(path_prefix + (name_without_extension, ))
                    if self.config('case_insensitive'):
                        path = path.lower()