# :coding: utf-8

import re
import functools

from .helper import filter_comments
from .helper import collapse_all
from .helper import get_docstring


#: Regular Expression pattern for classes
_CLASS_PATTERN = re.compile(
    r"(?P<start_regex>(\n|^)) *(?P<export>export +)?(?P<default>default +)?"
    r"(class +(?P<class_name>\w+)|(const|let|var) +(?P<data_name>\w+) "
    r"*= *class +\w+)"
    r"( +extends +(?P<mother_class>[\w._-]+))? *{"
)

#: Regular Expression pattern for class methods
_CLASS_METHOD_PATTERN = re.compile(
    r"(?P<start_regex>(\n|^)) *(?P<prefix>(static|get|set) +)?"
    r"(?P<method_name>[\w._-]+) *\([\n ]*(?P<arguments>.*?)[\n ]*\) *{",
    re.DOTALL
)

#: Regular Expression pattern for class arrow methods
_CLASS_METHOD_ARROW_PATTERN = re.compile(
    r"(?P<start_regex>(\n|^)) *(?P<prefix>static +)?(?P<method_name>\w+) *= *"
    r"(\([\n ]*(?P<arguments>.*?)[\n ]*\)|(?P<single_argument>[\w._-]+)) *"
    r"=> *",
    re.DOTALL
)

#: Regular Expression pattern for class attribute
_CLASS_ATTRIBUTE_PATTERN = re.compile(
    r"(?P<start_regex>(\n|^)) *(?P<prefix>static +)?"
    r"(?P<name>[\w._-]+) *= *(?P<value>.+?;)",
    re.DOTALL
)


def fetch_environment(content, module_id):
    """Return class environment dictionary from *content*.

    *module_id* represent the identifier of the module.

    The environment is in the form of::

        {
            "moduleName.AwesomeClass": {
                "id": "module.AwesomeClass",
                "name": "AwesomeClass",
                "parent": "MotherClass",
                "line": 42,
                "description": "Class doc.\\n\\nDetailed description."

                "id": "moduleName.AwesomeClass",
                "module_id": "moduleName",
                "exported": False,
                "default": False,
                "name": "AwesomeClass",
                "parent": None,
                "line_number": 2,
                "description": "Class doc.\\n\\nDetailed description."
                "method": {
                    "moduleName.AwesomeClass.awesomeMethod": {
                        ....
                    }
                },
                "attribute": {
                    "moduleName.AwesomeClass.DATA": {
                        ....
                    }
                }
            },
            ...
        }

    """
    environment = {}

    lines = content.split("\n")

    # The comment filter is made during the collapse content process to
    # preserve the class content with all comments (and docstrings!)
    content, collapsed_content = collapse_all(content, filter_comment=True)

    for match in _CLASS_PATTERN.finditer(content):
        class_name = match.group("class_name")
        if class_name is None:
            class_name = match.group("data_name")

        class_id = ".".join([module_id, class_name])

        line_number = (
            content[:match.start()].count("\n") +
            match.group("start_regex").count("\n") + 1
        )

        method_environment = {}
        attribute_environment = {}

        if line_number in collapsed_content.keys():
            class_content = collapsed_content[line_number][1:-1]

            method_environment = fetch_methods_environment(
                class_content, class_id, line_number=line_number-1
            )
            attribute_environment = fetch_attribute_environment(
                class_content, class_id, line_number=line_number-1
            )

        class_environment = {
            "id": class_id,
            "module_id": module_id,
            "exported": match.group("export") is not None,
            "default": match.group("default") is not None,
            "name": class_name,
            "parent": match.group("mother_class"),
            "line_number": line_number,
            "description": get_docstring(line_number, lines),
            "method": method_environment,
            "attribute": attribute_environment
        }
        environment[class_id] = class_environment

    return environment


def fetch_methods_environment(content, class_id, line_number=0):
    """Return function environment dictionary from *content*.

    *class_id* represent the identifier of the method class.

    *line_number* is the first line number of content.

    The environment is in the form of::

        {
            "moduleName.AwesomeClass.awesomeMethod": {
                "id": "moduleName.AwesomeClass.awesomeMethod",
                "class_id": "moduleName.AwesomeClass",
                "module_id": "moduleName",
                "name": "awesomeMethod",
                "prefix": "get",
                "arguments": ["argument1", "argument2"],
                "line_number": 5,
                "description": "Method doc.\\n\\nDetailed description."
            }
        }

    """
    environment = {}

    lines = content.split("\n")
    content = filter_comments(content)
    content = collapse_all(content)[0]

    for match_iter in (
        _CLASS_METHOD_ARROW_PATTERN.finditer(content),
        _CLASS_METHOD_PATTERN.finditer(content)
    ):
        for match in match_iter:
            method_id = ".".join([class_id, match.group("method_name")])
            prefix = match.group("prefix")
            if prefix is not None:
                prefix = prefix.strip()

                # Add the prefix to the method ID if the method if a getter or
                # a setter as several method could have the same name.
                if prefix in ["get", "set"]:
                    method_id += "." + prefix

            _line_number = (
                content[:match.start()].count("\n") +
                match.group("start_regex").count("\n") + 1
            )

            arguments_matched = match.group("arguments")
            if arguments_matched is None:
                arguments_matched = match.group("single_argument")

            arguments = list(filter(lambda x: len(x), [
                arg.strip() for arg in arguments_matched.split(",")
            ]))

            method_environment = {
                "id": method_id,
                "class_id": class_id,
                "module_id": class_id.rsplit(".", 1)[0],
                "name": match.group("method_name"),
                "prefix": prefix,
                "arguments": arguments,
                "line_number": _line_number + line_number,
                "description": get_docstring(_line_number, lines)
            }
            environment[method_id] = method_environment

    return environment


def fetch_attribute_environment(content, class_id, line_number=0):
    """Return attribute environment dictionary from *content*.

    *class_id* represent the identifier of the attribute class.

    *line_number* is the first line number of content.

    The environment is in the form of::

        {
            "moduleName.AwesomeClass.DATA": {
                "id": "moduleName.AwesomeClass.DATA",
                "class_id": "moduleName.AwesomeClass",
                "module_id": "moduleName",
                "name": "DATA",
                "prefix": "static",
                "value": "42",
                "line_number": 8,
                "description": "Attribute doc.\\n\\nDetailed description."
            }
        }

    """
    environment = {}

    lines = content.split("\n")

    # The comment filter is made during the collapse content process to
    # preserve the entire value (with semi-colons and docstrings!)
    content, collapsed_content = collapse_all(content, filter_comment=True)

    for match in _CLASS_ATTRIBUTE_PATTERN.finditer(content):
        attribute_id = ".".join([class_id, match.group("name")])
        prefix = match.group("prefix")
        if prefix is not None:
            prefix = prefix.strip()

        value = match.group("value")

        _line_number = (
            content[:match.start()].count("\n") +
            match.group("start_regex").count("\n") + 1
        )

        for _value_line_number in range(
            _line_number, _line_number + value.count("\n") + 1
        ):
            if "{}" in value and _value_line_number in collapsed_content.keys():
                value = value.replace(
                    "{}", collapsed_content[_value_line_number]
                )

        # Do not keep semi-colon in value
        if value.endswith(";"):
            value = value[:-1]

        attribute_environment = {
            "id": attribute_id,
            "class_id": class_id,
            "module_id": class_id.rsplit(".", 1)[0],
            "name": match.group("name"),
            "prefix": prefix,
            "value": functools.reduce(_clean_value, value.split('\n')).strip(),
            "line_number": _line_number + line_number,
            "description": get_docstring(_line_number, lines)
        }
        environment[attribute_id] = attribute_environment

    return environment


def _clean_value(line1, line2):
    """Clean up attribute value for display."""
    _line1 = line1.strip()
    _line2 = line2.strip()

    # Let trailing space to make the code easier to read
    if _line1[-1:] in ["{", "}", "(", ")", "[", "]", ";", ","]:
        _line1 += " "

    return _line1 + _line2
