# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © 2016 Continuum Analytics, Inc.
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
# -----------------------------------------------------------------------------
"""Generic and custom code linters."""

# Standard library imports
import json
import os
import re

# Local imports
from ciocheck.tools import Tool
from ciocheck.utils import run_command


class Linter(Tool):
    """Generic linter with json and regex output support."""

    # Regex matching
    pattern = None

    # Json matching
    json_keys = []  # ((old_key, new_key), ...)
    output_on_stderr = False

    def __init__(self, cmd_root):
        """Generic linter with json and regex output support."""
        super(Linter, self).__init__(cmd_root)
        self.paths = None
        self.regex = None

    def _parse_regex(self, string):
        """Parse output with grouped regex."""
        results = []
        self.regex = re.compile(self.pattern, re.VERBOSE)
        for matches in self.regex.finditer(string):
            results.append(matches.groupdict())
        return results

    def _parse_json(self, string):
        """Parse output with json keys."""
        data = json.loads(string)
        results = []
        for item in data:
            new_item = {}
            for (old_key, new_key) in self.json_keys:
                new_item[new_key] = item.pop(old_key)
            new_item.update(item)
            results.append(new_item)
        return results

    def _parse(self, string):
        """Parse linter output."""
        if self.json_keys:
            results = self._parse_json(string)
        elif self.pattern:
            results = self._parse_regex(string)
        else:
            raise Exception('Either a pattern or a json key mapping has to '
                            'be defined.')
        return results

    def extra_processing(self, results):
        """Override in case extra processing on results is needed."""
        return results

    def run(self, paths):
        """Run linter and return a list of dicts."""
        self.paths = list(paths.keys()) if isinstance(paths, dict) else paths
        if self.paths:
            args = list(self.command)
            args += self.paths
            out, err = run_command(args)
            if self.output_on_stderr:
                string = err
            else:
                string = out
            results = self._parse(string)
            results = self.extra_processing(results)
        else:
            results = []

        return results


class Flake8Linter(Linter):
    """Flake8 python tool runner."""

    language = 'python'
    name = 'flake8'
    extensions = ('py', )
    command = ('flake8', )
    config_file = '.flake8'
    config_sections = [('flake8', 'flake8')]

    # Match lines of the form:
    # path/to/file.py:328: undefined name '_thing'
    pattern = r'''
        (?P<path>.*?):(?P<line>\d{1,1000}):
        (?P<column>\d{1,1000}):\s
        (?P<type>[EWFCNTIBDSQ]\d{3})\s
        (?P<message>.*)
        '''


class Pep8Linter(Linter):
    """Pep8 python tool runner."""

    language = 'python'
    name = 'pep8'
    extensions = ('py', )
    command = ('pep8', )
    config_file = '.pep8'
    config_sections = [('pep8', 'pep8')]

    # Match lines of the form:
    pattern = r'''
        (?P<path>.*?):(?P<line>\d{1,1000}):
        (?P<column>\d{1,1000}):\s
        (?P<type>[EWFCNTIBDSQ]\d{3})\s
        (?P<message>.*)
        '''


class PydocstyleLinter(Linter):
    """Pydocstyle python tool runner."""

    language = 'python'
    name = 'pydocstyle'
    extensions = ('py', )
    command = ('pydocstyle', )
    config_file = '.pydocstyle'
    config_sections = [('pydocstyle', 'pydocstyle')]
    output_on_stderr = True

    # Match lines of the form:
    # ./bootstrap.py:1 at module level:
    #    D400: First line should end with a period (not 't')
    pattern = r'''
        (?P<path>.*?):
        (?P<line>\d{1,1000000})\  # 1 million lines of code :-p ?
        (?P<symbol>.*):\n.*?
        (?P<type>D\d{3}):\s
        (?P<message>.*)
        '''


class PylintLinter(Linter):
    """Pylint python tool runner."""

    language = 'python'
    name = 'pylint'
    extensions = ('py', )
    command = ('pylint', '--output-format', 'json', '-j', '0')
    config_file = '.pydocstyle'
    config_sections = [('pydocstyle', 'pydocstyle')]
    json_keys = (
        ('message', 'message'),
        ('line', 'line'),
        ('column', 'column'),
        ('type', 'type'),
        ('path', 'path'), )

    def extra_processing(self, results):
        """Make path an absolute path."""
        for item in results:
            item['path'] = os.path.join(self.cmd_root, item['path'])
        return results


LINTERS = [
    Pep8Linter,
    PydocstyleLinter,
    Flake8Linter,
    PylintLinter,
]


def test():
    """Main local test."""
    here = os.path.dirname(os.path.realpath(__file__))
    paths = [here]
    linter = PylintLinter(here)
    results = linter.run(paths)
    for result in results:
        print(result)


if __name__ == '__main__':
    test()
