#!/usr/bin/env python3
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

from __future__ import print_function
import lintutils
from subprocess import PIPE, STDOUT
import argparse
import multiprocessing as mp
import sys
import platform
from functools import partial


# NOTE(wesm):
#
# * readability/casting is disabled as it aggressively warns about functions
#   with names like "int32", so "int32(x)", where int32 is a function name,
#   warns with
_filters = """
-whitespace/comments
-readability/casting
-readability/todo
-readability/alt_tokens
-build/header_guard
-build/c++11
-runtime/references
-build/include_order
""".split()


def _get_chunk_key(filenames):
    # lists are not hashable so key on the first filename in a chunk
    return filenames[0]


def _check_some_files(completed_processes, filenames):
    # cpplint outputs complaints in '/path:line_number: complaint' format,
    # so we can scan its output to get a list of files to fix
    result = completed_processes[_get_chunk_key(filenames)]
    return lintutils.stdout_pathcolonline(result, filenames)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Runs cpplint on all of the source files."
    )
    parser.add_argument(
        "--cpplint_binary", required=True, help="Path to the cpplint binary"
    )
    parser.add_argument(
        "--exclude_globs",
        help="Filename containing globs for files "
        "that should be excluded from the checks",
    )
    parser.add_argument(
        "--source_dir", required=True, help="Root directory of the source code"
    )
    parser.add_argument(
        "--quiet",
        default=False,
        action="store_true",
        help="If specified, only print errors",
    )
    arguments = parser.parse_args()

    exclude_globs = []
    if arguments.exclude_globs:
        for line in open(arguments.exclude_globs):
            exclude_globs.append(line.strip())

    linted_filenames = []
    for path in lintutils.get_sources(arguments.source_dir, exclude_globs):
        linted_filenames.append(str(path))

    cmd = [
        arguments.cpplint_binary,
        "--verbose=2",
        "--linelength=120",
        "--filter=" + ",".join(_filters),
    ]
    if arguments.cpplint_binary.endswith(".py") and platform.system() == "Windows":
        # Windows doesn't support executable scripts; execute with
        # sys.executable
        cmd.insert(0, sys.executable)
    if arguments.quiet:
        cmd.append("--quiet")
    else:
        print("\n".join(map(lambda x: "Linting {}".format(x), linted_filenames)))

    # lint files in chunks: each invocation of cpplint will process 16 files
    chunks = lintutils.chunk(linted_filenames, 16)
    cmds = [cmd + some for some in chunks]
    results = lintutils.run_parallel(cmds, stdout=PIPE, stderr=STDOUT)

    error = False
    # record completed processes (keyed by the first filename in the input
    # chunk) for lookup in _check_some_files
    completed_processes = {
        _get_chunk_key(filenames): result for filenames, result in zip(chunks, results)
    }
    checker = partial(_check_some_files, completed_processes)
    pool = mp.Pool()
    try:
        # scan the outputs of various cpplint invocations in parallel to
        # distill a list of problematic files
        for problem_files, stdout in pool.imap(checker, chunks):
            if problem_files:
                if isinstance(stdout, bytes):
                    stdout = stdout.decode("utf8")
                print(stdout, file=sys.stderr)
                error = True
    except Exception:
        error = True
        raise
    finally:
        pool.terminate()
        pool.join()

    sys.exit(1 if error else 0)
