# -*- coding: utf-8 -*-
"""
Assembly: response
"""

import copy
import arrow
import inspect
import functools
from . import utils
import flask_caching
from jinja2 import Markup
from dicttoxml import dicttoxml
from werkzeug.wrappers import BaseResponse
from .assembly import (Assembly, app_context, apply_function_to_members)
from flask import (Response,
                   jsonify,
                   request,
                   current_app,
                   url_for,
                   make_response,
                   g)

# ----------------------------------------------------------------------------------------------------------------------
# ----  Monkey patch jsonify, to convert other data type: ie: Arrow
import flask.json
from flask.json import dumps as flask_dumps


class _JSONEnc(flask.json.JSONEncoder):
    def default(self, o):
        if isinstance(o, arrow.Arrow):
            return o.for_json()
        else:
            return super(self.__class__, self).default(o)


def dumps(o, **kw):
    kw["cls"] = _JSONEnc
    return flask_dumps(o, **kw)
flask.json.dumps = dumps

# ----------------------------------------------------------------------------------------------------------------------


__view_parsers = set()


def view_parser(f):
    """
    A simple decorator to to parse the data that will be rendered
    :param func:
    :return:
    """
    __view_parsers.add(f)
    return f

def _build_response(data, renderer=None):
    """
    Build a response using the renderer from the data
    :return:
    """
    if isinstance(data, Response) or isinstance(data, BaseResponse):
        return data
    if not renderer:
        raise AttributeError(" Renderer is required")

    data, status, headers = utils.prepare_view_response(data)
    for _ in __view_parsers:
        data = _(data) 
    return make_response(renderer(data), status, headers)

json_renderer = lambda i, data: _build_response(data, jsonify)
xml_renderer = lambda i, data: _build_response(data, dicttoxml)

def json(func):
    """
    Decorator to render as JSON
    :param func:
    :return:
    """
    if inspect.isclass(func):
        apply_function_to_members(func, json)
        return func
    else:
        @functools.wraps(func)
        def decorated_view(*args, **kwargs):
            data = func(*args, **kwargs)
            return _build_response(data, jsonify)
        return decorated_view

def xml(func):
    """
    Decorator to render as XML
    :param func:
    :return:
    """
    if inspect.isclass(func):
        apply_function_to_members(func, xml)
        return func
    else:
        @functools.wraps(func)
        def decorated_view(*args, **kwargs):
            data = func(*args, **kwargs)
            return _build_response(data, dicttoxml)
        return decorated_view

def jsonp(func):
    """Wraps JSONified output for JSONP requests.
    """

    @functools.wraps(func)
    def decorated_view(*args, **kwargs):
        callback = request.args.get('callback', None)
        if callback:
            data = str(func(*args, **kwargs))
            content = str(callback) + '(' + data + ')'
            mimetype = 'application/javascript'
            return current_app.response_class(content, mimetype=mimetype)
        else:
            return func(*args, **kwargs)
    return decorated_view

def template(page=None, **kwargs):
    """
    Decorator to change the view template.

    It works only on view methods

    ** on method that return a dict
        page or layout are optional

        :param page: The html page
        :param layout: The layout to use for that view

        :param kwargs:
            get pass to the view as k/V

    ** on other methods that return other type, it doesn't apply

    :return:
    """
    pkey = "_template_extends__"

    def decorator(f):
        if inspect.isclass(f):
            raise Error("@template can only be applied on Assembly methods")
            return f
        else:
            @functools.wraps(f)
            def wrap(*args2, **kwargs2):
                response = f(*args2, **kwargs2)
                if isinstance(response, dict) or response is None:
                    response = response or {}
                    if page:
                        response.setdefault("__template__", page)
                    for k, v in kwargs.items():
                        response.setdefault(k, v)
                return response
            return wrap
    return decorator

def headers(params={}):
    """This decorator adds the headers passed in to the response
    """
    def decorator(f):

        if inspect.isclass(f):
            h = headers(params)
            apply_function_to_members(f, h)
            return f

        @functools.wraps(f)
        def decorated_function(*args, **kwargs):
            resp = make_response(f(*args, **kwargs))
            h = resp.headers
            for header, value in params.items():
                h[header] = value
            return resp
        return decorated_function
    return decorator

def noindex(f):
    """This decorator passes X-Robots-Tag: noindex"""
    return headers({'X-Robots-Tag': 'noindex'})(f)

# ------------------------------------------------------------------------------
"""
Caching
Allow caching in the response
@cache

@reponse.cache(timeout=10)
def cached(self):
    ...

"""
caching = flask_caching.Cache()

@app_context
def _init_caching(app):
    utils.flatten_config_property("CACHE", app.config)
    caching.init_app(app)

cache = caching.cached