# -*- coding: utf-8 -*-

##########################################################################
# OpenLP - Open Source Lyrics Projection                                 #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2019 OpenLP Developers                              #
# ---------------------------------------------------------------------- #
# This program is free software: you can redistribute it and/or modify   #
# it under the terms of the GNU 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 General Public License for more details.                           #
#                                                                        #
# You should have received a copy of the GNU General Public License      #
# along with this program.  If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
App stuff
"""
import json
import logging
import re

from webob import Request, Response
from webob.static import DirectoryApp

from openlp.core.api.http.errors import HttpError, NotFound, ServerError
from openlp.core.common.applocation import AppLocation


ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)

log = logging.getLogger(__name__)


def _route_to_regex(route):
    r"""
    Convert a route to a regular expression

    For example:

      'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)'

    and

      'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\d+)'

    """
    route_regex = ''
    last_pos = 0
    for match in ARGS_REGEX.finditer(route):
        route_regex += re.escape(route[last_pos:match.start()])
        arg_name = match.group(1)
        expr = match.group(2) or '[^/]+'
        expr = '(?P<%s>%s)' % (arg_name, expr)
        route_regex += expr
        last_pos = match.end()
    route_regex += re.escape(route[last_pos:])
    route_regex = '^%s$' % route_regex
    return route_regex


def _make_response(view_result):
    """
    Create a Response object from response
    """
    log.debug("in Make response")
    if isinstance(view_result, Response):
        return view_result
    elif isinstance(view_result, tuple):
        content_type = 'text/html'
        body = view_result[0]
        if isinstance(body, dict):
            content_type = 'application/json'
            body = json.dumps(body)
        response = Response(body=body, status=view_result[1],
                            content_type=content_type, charset='utf8')
        if len(view_result) >= 3:
            response.headers.update(view_result[2])
        return response
    elif isinstance(view_result, dict):
        return Response(body=json.dumps(view_result), status=200,
                        content_type='application/json', charset='utf8')
    elif isinstance(view_result, str):
        return Response(body=view_result, status=200,
                        content_type='text/html', charset='utf8')
    else:
        return Response(body=view_result, status=200,
                        content_type='text/plain', charset='utf8')


def _handle_exception(error):
    """
    Handle exceptions
    """
    log.exception(error)
    if isinstance(error, HttpError):
        return error.to_response()
    else:
        return ServerError().to_response()


class WSGIApplication(object):
    """
    This is the core of the API, the WSGI app
    """
    def __init__(self, name):
        """
        Create the app object
        """
        self.name = name
        self.static_routes = {}
        self.route_map = {}

    def initialise(self):
        """
        Set up generic roots for the whole application
        :return: None
        """
        self.add_static_route('/assets(.*)', '')
        self.add_static_route('/images(.*)', '')
        pass

    def add_route(self, route, view_func, method):
        """
        Add a route
        """
        route_regex = _route_to_regex(route)
        if route_regex not in self.route_map:
            self.route_map[route_regex] = {}
        self.route_map[route_regex][method.upper()] = view_func

    def add_static_route(self, route, static_dir):
        """
        Add a static directory as a route
        """
        if route not in self.static_routes:
            static_path = AppLocation.get_section_data_path('remotes') / static_dir
            if not static_path.exists():
                log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
                return
            self.static_routes[route] = DirectoryApp(str(static_path.resolve()))

    def dispatch(self, request):
        """
        Find the appropriate URL and run the view function
        """
        # If not a static route, try the views
        for route, views in self.route_map.items():
            match = re.match(route, request.path)
            if match and request.method.upper() in views:
                kwargs = match.groupdict()
                log.debug('Found {method} {url}'.format(method=request.method, url=request.path))
                view_func = views[request.method.upper()]
                return _make_response(view_func(request, **kwargs))
        # Look to see if this is a static file request
        for route, static_app in self.static_routes.items():
            if re.match(route, request.path):
                return request.get_response(static_app)
        log.error('URL {url} - Not found'.format(url=request.path))
        raise NotFound()

    def wsgi_app(self, environ, start_response):
        """
        The actual WSGI application.
        """
        request = Request(environ)
        try:
            response = self.dispatch(request)
        except Exception as e:
            response = _make_response(_handle_exception(e))
        response.headers.add("cache-control", "no-cache, no-store, must-revalidate")
        response.headers.add("pragma", "no-cache")
        response.headers.add("expires", "0")
        return response(environ, start_response)

    def __call__(self, environ, start_response):
        """
        Shortcut for wsgi_app.
        """
        return self.wsgi_app(environ, start_response)
