# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Script that is used by PRESUBMIT.py to run style checks on Java files."""

import collections
import os
import subprocess
import sys
import xml.dom.minidom


_SELF_DIR = os.path.dirname(__file__)
CHROMIUM_SRC = os.path.normpath(os.path.join(_SELF_DIR, '..', '..', '..'))
_CHECKSTYLE_ROOT = os.path.join(CHROMIUM_SRC, 'third_party', 'checkstyle',
                                'checkstyle-all.jar')
_JAVA_PATH = os.path.join(CHROMIUM_SRC, 'third_party', 'jdk', 'current', 'bin',
                          'java')
_STYLE_FILE = os.path.join(_SELF_DIR, 'chromium-style-5.0.xml')
_REMOVE_UNUSED_IMPORTS_PATH = os.path.join(_SELF_DIR,
                                           'remove_unused_imports.py')


class Violation(
        collections.namedtuple('Violation',
                               'file,line,column,message,severity')):
    def __str__(self):
        column = f'{self.column}:' if self.column else ''
        return f'{self.file}:{self.line}:{column} {self.message}'

    def is_warning(self):
        return self.severity == 'warning'

    def is_error(self):
        return self.severity == 'error'


def run_checkstyle(local_path, style_file, java_files):
    cmd = [
        _JAVA_PATH, '-cp', _CHECKSTYLE_ROOT,
        'com.puppycrawl.tools.checkstyle.Main', '-c', style_file, '-f', 'xml'
    ] + java_files
    check = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    stdout = check.communicate()[0].decode('utf-8')
    results = []

    lines = stdout.splitlines(keepends=True)
    if 'Checkstyle ends with' in lines[-1]:
        stdout = ''.join(lines[:-1])
    try:
        root = xml.dom.minidom.parseString(stdout)
    except Exception:
        print('Tried to parse:')
        print(stdout)
        raise

    for fileElement in root.getElementsByTagName('file'):
        filename = fileElement.attributes['name'].value
        if filename.startswith(local_path):
            filename = filename[len(local_path) + 1:]
        errors = fileElement.getElementsByTagName('error')
        for error in errors:
            severity = error.attributes['severity'].value
            if severity not in ('warning', 'error'):
                continue
            message = error.attributes['message'].value
            line = int(error.attributes['line'].value)
            column = None
            if error.hasAttribute('column'):
                column = int(error.attributes['column'].value)
            results.append(Violation(filename, line, column, message,
                                     severity))
    return results


def run_presubmit(input_api, output_api, files_to_skip=None):
    # Android toolchain is only available on Linux.
    if not sys.platform.startswith('linux'):
        return []

    # Filter out non-Java files and files that were deleted.
    java_files = [
        x.AbsoluteLocalPath() for x in
        input_api.AffectedSourceFiles(lambda f: input_api.FilterSourceFile(
            f, files_to_skip=files_to_skip)) if x.LocalPath().endswith('.java')
    ]
    if not java_files:
        return []

    local_path = input_api.PresubmitLocalPath()
    violations = run_checkstyle(local_path, _STYLE_FILE, java_files)
    warnings = ['  ' + str(v) for v in violations if v.is_warning()]
    errors = ['  ' + str(v) for v in violations if v.is_error()]

    ret = []
    if warnings:
        ret.append(output_api.PresubmitPromptWarning('\n'.join(warnings)))
    if errors:
        msg = '\n'.join(errors)
        if 'Unused import:' in msg or 'Duplicate import' in msg:
            msg += """

To remove unused imports: """ + input_api.os_path.relpath(
                _REMOVE_UNUSED_IMPORTS_PATH, local_path)
        ret.append(output_api.PresubmitError(msg))
    return ret