import json
import inspect

try:
    from itertools import imap as map  # PY2
except ImportError:
    pass

import tornado.web
from jsonschema import validate, ValidationError

from tornado_json.utils import is_method
from tornado_json.constants import HTTP_METHODS
from tornado_json.requesthandlers import APIHandler


def _validate_example(rh, method, example_type):
    """Validates example against schema

    :returns: Formatted example if example exists and validates, otherwise None
    :raises ValidationError: If example does not validate against the schema
    """
    example = getattr(method, example_type + "_example")
    schema = getattr(method, example_type + "_schema")

    if example is None:
        return None

    try:
        validate(example, schema)
    except ValidationError as e:
        raise ValidationError(
            "{}_example for {}.{} could not be validated.\n{}".format(
                example_type, rh.__name__, method.__name__, str(e)
            )
        )

    return json.dumps(example, indent=4, sort_keys=True)


def _get_rh_methods(rh):
    """Yield all HTTP methods in ``rh`` that are decorated
    with schema.validate"""
    for k, v in vars(rh).items():
        if all([
            k in HTTP_METHODS,
            is_method(v),
            hasattr(v, "input_schema")
        ]):
            yield (k, v)


def _get_tuple_from_route(route):
    """Return (pattern, handler_class) tuple from ``route``

    :type route: tuple|tornado.web.URLSpec
    :rtype: tuple
    :raises TypeError: If ``route`` is not a tuple or URLSpec
    """
    if isinstance(route, tuple):
        assert len(route) >= 2
        pattern, handler_class = route[:2]
    elif isinstance(route, tornado.web.URLSpec):
        pattern, handler_class = route.regex.pattern, route.handler_class
    else:
        raise TypeError("Unknown route type '{}'"
                        .format(type(route).__name__))
    return pattern, handler_class


def _escape_markdown_literals(string):
    """Escape any markdown literals in ``string`` by prepending with \\

    :type string: str
    :rtype: str
    """
    literals = list("\\`*_{}[]()<>#+-.!:|")
    escape = lambda c: '\\' + c if c in literals else c
    return "".join(map(escape, string))


def _cleandoc(doc):
    """Remove uniform indents from ``doc`` lines that are not empty

    :returns: Cleaned ``doc``
    """
    indent_length = lambda s: len(s) - len(s.lstrip(" "))
    not_empty = lambda s: s != ""

    lines = doc.split("\n")
    indent = min(map(indent_length, filter(not_empty, lines)))

    return "\n".join(s[indent:] for s in lines)


def _add_indent(string, indent):
    """Add indent of ``indent`` spaces to ``string.split("\n")[1:]``

    Useful for formatting in strings to already indented blocks
    """
    lines = string.split("\n")
    first, lines = lines[0], lines[1:]
    lines = ["{indent}{s}".format(indent=" " * indent, s=s)
             for s in lines]
    lines = [first] + lines