import bleach
import bottle
import commonmark
import dateutil.parser
import functools
import importlib
import json
import os
import re
import urllib.parse

from string import Template
from urllib.parse import urlparse

# This is used to export the bottle object for the WSGI server
application = bottle.app()

# Load settings for this app
application.config.load_config('settings.ini')

# Set a "settings" variable to be used from other modules without having to
# import the "application" object
settings = application.config

# "bleach" module is used to sanitize the HTML output during CommmonMark->HTML
# conversion. The library has only a very restrictive list of white-listed
# tags, so we add some more here.
bleach.sanitizer.ALLOWED_TAGS += [
    'br', 'colgroup', 'col', 'div', 'dd', 'dl', 'dt', 'figure', 'figcaption',
    'h1', 'h2', 'h3', 'hr', 'img', 'link', 'object', 'p', 'pre', 'source', 'span',
    'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'var', 'video' ]
bleach.sanitizer.ALLOWED_ATTRIBUTES.update ({
    'a': [ 'class', 'href' ],
    'div': [ 'class' ],
    'img': [ 'alt', 'class', 'src', 'title' ],
    'link': [ 'href', 'rel' ],
    'object': [ 'data', 'type', 'typemustmatch' ],
    'source': [ 'src' ],
    'video': [ 'controls', 'height', 'width' ]
})

from dokk import graph, user
import dokk.sparql as sparql

def url(name, *anons, **parameters):
    """
    Return a URL string corresponding to a Bottle route, given its arguments.
    We use this function because it's shorter than calling application.router.build
    everywhere, especially in templates.
    """
    
    # Escape all hashes symbols (#) because they are not passed on by the browser
    for parameter in parameters.keys():
        if isinstance(parameters[parameter], str):
            parameters[parameter] = parameters[parameter].replace('#', '%23')
    
    return application.router.build(name, *anons, **parameters)

def clear_url(url):
    """
    Return only "host:port/path" of the given URL.
    This is mostly useful to display user-friendly URL names
    to the users.
    """
    
    url = urlparse(url)
    
    if url.hostname == None:
        return ''
    
    clean_url = url.hostname
    
    if url.port != None:
        clean_url += ':' + url.port
    
    # Do not write if it's a single /
    if url.path != '/':
        clean_url += url.path
    
    return clean_url

def dump(variable, indent=None):
    """
    Dump a variable as JSON.
    Mostly useful for debugging.
    """
    
    return json.dumps(variable, indent=indent)

def query(id, **parameters):
    """
    A function for templates for returning a query results.
    
    :param id: ID of the Query to execute.
    :param parameters: List of parameters for the query.
    """
    
    # Retrieve the Query
    query_string = graph.get_query(id)['content']
    
    # Substitute parameters
    query_string = Template(query_string).substitute(**parameters)
    
    try:
        return sparql.query_public(query_string)
    except Exception as e:
        return None

def read_archive(filename):
    """
    Read a file from the archive and return its content.
    """
    
    filepath = settings['dokk.archive'] + '/' + filename
    
    if not os.path.isfile(filepath):
        return None
    
    with open(filepath, mode='r') as f:
        return f.read()

# template() functions for rendering routes
template = functools.partial(
    bottle.jinja2_template,
    template_lookup = [ '/srv/dokk/dokk/templates' ],
    template_settings = {
        'filters': {
            'clear_url': clear_url,
            'commonmark2html': lambda text: bleach.clean(bleach.linkify(commonmark.commonmark(text or ''))),
            'simple_date': lambda date: dateutil.parser.parse(date).strftime('%H:%M, %d %b %Y')
        },
        'globals': {
            # Variables
            # ...
            
            # Functions
            'dump': dump,
            'query': query,
            'url': url,
            'user': lambda: user.get_from_session(),
            'signedin': lambda: user.is_signedin()
        },
        'autoescape': True
    })

class NodeTemplate(bottle.Jinja2Template):
    """
    This is an extension of Bottle's Jinja2Template class, that is used to
    load templates. What we're doing here is override the loader() function
    to load templates from the database instead of the filesystem.
    This class is used only for loading nodes' templates.
    
    See: https://github.com/bottlepy/bottle/blob/a454029f6e8a087e5cb570eb6ee36c2087d26e4d/bottle.py#L3914
         https://jinja.pocoo.org/docs/2.10/api/#jinja2.FunctionLoader
    """
    
    def loader(self, template_id):
        tpl = graph.get_template(template_id)
        
        return tpl['content'] if 'content' in tpl else ''

# This is used for rendering articles templates. We keep this as a separate
# function in order to avoid mixing articles templates with app templates.
article_template = functools.partial(
    bottle.template,
    template_adapter = NodeTemplate,
    template_lookup = [],
    template_settings = {
        'filters': {
            'clear_url': clear_url,
            'simple_date': lambda date: dateutil.parser.parse(date).strftime('%H:%M, %d %b %Y'),
            'template': lambda string, **parameters: article_template(string+'\n', **parameters)
        },
        'globals': {
            # deprecated
            'archive': read_archive,
            'blob': read_archive,
            'dump': dump,
            'query': query,
        },
        'autoescape': False
    })

def page_type_filter(config):
    """
    A type filter used for routes.
    Matches valid page types for the editor.
    
    Example. @get('/dokk/<type:page>')
    """
    
    # article   Free-text input for a node. Used for describing a node.
    # file      Structured data for describing a file.
    # query     A query template. Queries can be used inside articles.
    # template  A text template. Templates can be used inside articles.
    # topic     Structured data for a node. Complements "article".
    regexp = '(article|file|query|template|topic)'
    
    def to_python(match):
        return match

    def to_url(page):
        return page

    return regexp, to_python, to_url

# Bind the page-type filter
application.router.add_filter('page', page_type_filter)

# Load application routes
import dokk.routes

# Load custom error pages
import dokk.error
