# -*- coding: utf-8 -*-
# Copyright © 2011 Varyant, LLC

import re
from wsgiref.util import request_uri

from wack.http.errors import HttpMethodNotAllowed, HttpNotFound
from wack.http.request import Request
from wack.http.response import Response


class Controllers(object):
    """
    WSGI application that maps URLs and HTTP methods to functions wack that have
    been mounted on a path of the URL tree via a regular expression.

    At runtime the Controllers instance matches a given URL path with the
    regular expressions it has been given, in the order they were given, looking
    for a match. The Controllers instance will only match the entire PATH_INFO
    variable or not. If a match is found, the Controllers instance consumes the
    PATH_INFO variable, appending it to the SCRIPT_NAME variable. It then
    matches the request method with the methods supplied. If no match is found
    for either the URL path or the request method, an HttpNotFound exception is
    raised. Matched groups from the regular expression are passed into the
    controller's method handler as arguments.

    :ivar mappings: ordered list of (regex, list of methods, application) tuples
    :type mappings: list
    """

    def __init__(self):
        self.mappings = []

    def __call__(self, environ, start_response):
        path_info = environ['PATH_INFO']
        script_name = environ['SCRIPT_NAME']
        request_method = environ['REQUEST_METHOD']
        for regex, method_list, controller in self.mappings:
            match = regex.match(path_info)
            if not match:
                continue
            if request_method not in method_list:
                raise HttpMethodNotAllowed({'uri': request_uri(environ, 0),
                                            'method': request_method})
            environ['PATH_INFO'] = ''
            environ['SCRIPT_NAME'] = script_name + path_info
            request = Request(environ)
            response = Response()
            args = match.groups()
            body = controller(request, response, *args) or ''
            start_response(*response.start_response_args(body))
            return body
        raise HttpNotFound({'uri': request_uri(environ, 0)})

    def mount(self, regex_str, method_list, controller):
        """
        Mount the controller function on the URL tree at the path specified by
        the regular expression. The controller will only be invoked for the
        given http methods.
        :param regex_str: regular expression of URL path
        :param method_list: list of HTTP methods
        :param controller: python callable
        :raise: ValueError if the regular expression is invalid
        """
        if not regex_str.startswith('^/'):
            raise ValueError('regular expression must start with: ^/')
        if not regex_str.endswith('$'):
            raise ValueError('regular expression must end with: $')
        regex = re.compile(regex_str)
        # XXX introspection on controller to see if its interface matches?
        self.mappings.append((regex, method_list, controller))
