"""Classes and methods for TurboGears controllers."""
import view
import cherrypy
import logging
import re
import types
import validators as turbogearsvalid
import kid
import formencode

log = logging.getLogger("turbogears.controllers")

unicodechars = re.compile(r"([^\x00-\x9F])")

defaultfmt = cherrypy.config.get("kid.outputformat", "html")
if defaultfmt not in kid.output_methods.keys():
    defaultfmt = "html"

def _process_output(tg_format, output, template):
    """Produces final output form from the data returned from a
    controller method.
    @param tg_format: format of desired output (html or json)
    @param output: the output returned by the controller
    @param template: HTML template to use
    """
    if isinstance(output, dict):
        if output.has_key("tg_template"):
            template=output.pop("tg_template")
        if not tg_format:
            if template:
                tg_format = defaultfmt
            else:
                tg_format = "json"
        if tg_format == "json":
            log.debug("Returning JSON")
            cherrypy.response.headerMap["Content-Type"] = "text/javascript"
        output["tg_flash"] = get_flash()
        output = view.render(output, tg_format, template=template)
        
    # fix the Safari XMLHttpRequest encoding problem
    try:
        contentType = cherrypy.response.headerMap["Content-Type"]
        ua = cherrypy.request.headerMap["User-Agent"]
    except KeyError:
        return output
    if not contentType.startswith("text/"):
        return output
    ua = view.UserAgent(ua)
    if ua.browser == "safari":
        if isinstance(output, str):
            output = output.decode("utf8")
        output = unicodechars.sub(
            lambda m: "&#x%x;" % ord(m.group(1)), 
            output).encode("ascii")
    if isinstance(output, unicode):
        output = output.encode("utf8")
    # Fix for opera browser
    if "Opera" in ua.browser and tg_format == "json":
        cherrypy.response.headerMap["Content-Type"] = "text/plain"
    return output
        
def expose(html=None, validators=None, 
    template=None, format=None, content_type="text/html"):
    """TurboGears verson of CherryPy's expose.
    
    In addition to exposing the method to the web, this expose function will
    also perform validation of input arguments,
    process output through a template, fix Safari's problem with 
    XMLHttpRequest encodings and provide JSON output on request.
    
    If validators are supplied, incoming arguments will be processed by the
    validator assigned to the argument. If validation fails, an attempt
    will be made to call validation_error on the object that was
    being called. validation_error is given the name of the method that was
    called, the arguments that were provided, and a list of Invalid
    exceptions.
    
    @param html: reference along the Python path for the HTML template
    @type html: string
    @param template: reference along the Python path for the template.
                     for use in place of "html"
    @type template: string
    @param validators: maps argument names to validator applied to that arg
    @type validators: dict or instance of formencode.Schema
    @param format: format to output by default
    @type format: string
    @param content_type: sets the content-type http header
    @type content_type: string
    """
    import controllers
    if template:
        html = template
    
    def decorator(func):
        def newfunc(self, *args, **kw):
            tg_format = kw.pop("tg_format", format)
            underscore = kw.pop("_", None)
            cherrypy.response.headerMap['Content-Type']= content_type
            
            errors = {}
            if validators:
                if isinstance(validators, dict):
                    for field, validator in validators.items():
                        if field not in kw:
                            continue
                        try:
                            kw[field] = validator.to_python(kw[field])
                        except turbogearsvalid.Invalid, error:
                            errors[field] = error
                else:
                    try:
                        kw = validators.to_python(kw)
                    except turbogearsvalid.Invalid, error:
                        if not error.error_dict:
                            errors["tg_general"] = error
                        else:
                            errors.update(error.error_dict)
            if errors:
                if hasattr(self, "validation_error"):
                    output = self.validation_error(func.func_name, kw, errors)
                else:
                    raise turbogearsvalid.Invalid(str(errors), kw, None)
            else:
                output = func(self, *args, **kw)
            return controllers._process_output(tg_format, output, html)
        newfunc.func_name = func.func_name
        newfunc.exposed = True
        return newfunc
        
    return decorator

def flash(message):
    """Set a message to be displayed in the browser on next page display"""
    cherrypy.response.simpleCookie['tg_flash'] = message
    cherrypy.response.simpleCookie['tg_flash']['path'] = '/'
    
def get_flash():
    """Retrieve the flash message, if one is set. The message is cleared
    after retrieval."""
    try:
        message = cherrypy.request.simpleCookie["tg_flash"].value
        cherrypy.response.simpleCookie["tg_flash"] = ""
        cherrypy.response.simpleCookie["tg_flash"]['expires'] = 0
        cherrypy.response.simpleCookie['tg_flash']['path'] = '/'
    except KeyError:
        message = None
    return message
    
class Controller(object):
    """Base class for a web application's controller.
    
    As of 0.6, this has no functionality and does not need to be
    used directly. This may be a source of future functionality,
    but it's mostly here as a base class for Root."""
    pass
    
class Root(Controller):
    """Base class for the root of a web application.
    
    Your web application should have one of these. The root of
    your application is used to compute URLs used by your app."""
    pass

__all__ = ["expose", "flash", "get_flash", "Root", "Controller"]
