from __future__ import absolute_import

import os
import re

from pep8ext_naming import NamingChecker
from prospector.message import Location, Message
from prospector.tools.base import ToolBase
from pycodestyle import PROJECT_CONFIG, BaseReport, StyleGuide, register_check

try:
    # for pep8 <= 1.5.7
    from pycodestyle import DEFAULT_CONFIG as USER_CONFIG
except ImportError:
    # for pep8 >= 1.6.0
    from pycodestyle import USER_CONFIG


__all__ = ("Pep8Tool",)


class ProspectorReport(BaseReport):
    def __init__(self, *args, **kwargs):
        super(ProspectorReport, self).__init__(*args, **kwargs)
        self._prospector_messages = []

    def error(self, line_number, offset, text, check):
        code = super(ProspectorReport, self).error(
            line_number,
            offset,
            text,
            check,
        )
        if code is None:
            # The error pep8 found is being ignored, let's move on.
            return
        else:
            # Get a clean copy of the message text without the code embedded.
            text = text[5:]

        # mixed indentation (E101) is a file global message
        if code == "E101":
            line_number = None

        # Record the message using prospector's data structures.
        location = Location(
            path=self.filename,
            module=None,
            function=None,
            line=line_number,
            character=(offset + 1),
        )
        message = Message(
            source="pep8",
            code=code,
            location=location,
            message=text,
        )

        self._prospector_messages.append(message)

    def get_messages(self):
        return self._prospector_messages


class ProspectorStyleGuide(StyleGuide):
    def __init__(self, found_files, *args, **kwargs):
        self._files = found_files
        self._module_paths = set(self._files.iter_module_paths())

        # Override the default reporter with our custom one.
        kwargs["reporter"] = ProspectorReport

        super(ProspectorStyleGuide, self).__init__(*args, **kwargs)

    def excluded(self, filename, parent=None):
        if super(ProspectorStyleGuide, self).excluded(filename, parent):
            return True

        # If the file survived pep8's exclusion rules, check it against
        # prospector's patterns.
        fullpath = os.path.join(self._files.rootpath, parent or "", filename)
        if os.path.isdir(fullpath):
            return False

        fullpath = fullpath if parent else filename
        return fullpath not in self._module_paths


class Pep8Tool(ToolBase):
    def __init__(self, *args, **kwargs):
        super(Pep8Tool, self).__init__(*args, **kwargs)
        self.checker = None

    def configure(self, prospector_config, found_files):
        # figure out if we should use a pre-existing config file
        # such as setup.cfg or tox.ini
        external_config = None

        # 'none' means we ignore any external config, so just carry on
        use_config = False

        if prospector_config.use_external_config("pep8"):
            use_config = True

            paths = [os.path.join(found_files.rootpath, name) for name in PROJECT_CONFIG]
            paths.append(USER_CONFIG)
            ext_loc = prospector_config.external_config_location("pep8")
            if ext_loc is not None:
                paths = [ext_loc] + paths

            for conf_path in paths:
                if os.path.exists(conf_path) and os.path.isfile(conf_path):
                    # this file exists - but does it have pep8 or pycodestyle config in it?
                    header = re.compile(r"\[(pep8|pycodestyle)\]")
                    with open(conf_path) as conf_file:
                        if any([header.search(line) for line in conf_file.readlines()]):
                            external_config = conf_path
                            break

        # create a list of packages, but don't include packages which are
        # subpackages of others as checks will be duplicated
        packages = [os.path.split(p) for p in found_files.iter_package_paths(abspath=False)]
        packages.sort(key=len)
        check_paths = set()
        for package in packages:
            package_path = os.path.join(*package)
            if len(package) == 1:
                check_paths.add(package_path)
                continue
            if os.path.join(*package) in check_paths:
                continue
            check_paths.add(package_path)

        for filepath in found_files.iter_module_paths(abspath=False):
            package = os.path.dirname(filepath).split(os.path.sep)
            for i in range(0, len(package)):
                if os.path.join(*package[: i + 1]) in check_paths:
                    break
            else:
                check_paths.add(filepath)

        check_paths = [found_files.to_absolute_path(p) for p in check_paths]

        # Instantiate our custom pep8 checker.
        self.checker = ProspectorStyleGuide(paths=check_paths, found_files=found_files, config_file=external_config)
        if not use_config or external_config is None:
            configured_by = None
            # This means that we don't have existing config to use.
            # Make sure pep8's code ignores are fully reset to zero before
            # adding prospector-flavoured configuration.
            # pylint: disable=attribute-defined-outside-init
            self.checker.options.select = ()
            self.checker.options.ignore = tuple(prospector_config.get_disabled_messages("pep8"))

            if "max-line-length" in prospector_config.tool_options("pep8"):
                self.checker.options.max_line_length = prospector_config.tool_options("pep8")["max-line-length"]
        else:
            configured_by = "Configuration found at %s" % external_config

        # if we have a command line --max-line-length argument, that
        # overrules everything
        max_line_length = prospector_config.max_line_length
        if max_line_length is not None:
            self.checker.options.max_line_length = max_line_length

        return configured_by, []

    def run(self, _):
        report = self.checker.check_files()
        return report.get_messages()


# Load pep8ext_naming into pep8's configuration.
register_check(NamingChecker)
