# Copyright (c) 2021, Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#
#   * Neither the name of Google nor the names of its contributors may
#     be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Top-level presubmit script for libwebp.

See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
details on the presubmit API built into depot_tools.
"""

import re
import subprocess2

USE_PYTHON3 = True
_BASH_INDENTATION = "2"
_GIT_COMMIT_SUBJECT_LENGTH = 65
_INCLUDE_BASH_FILES_ONLY = [r".*\.sh$"]
_INCLUDE_MAN_FILES_ONLY = [r"man/.+\.1$"]
_INCLUDE_SOURCE_FILES_ONLY = [r".*\.[ch]$"]
_LIBWEBP_MAX_LINE_LENGTH = 80


def _CheckCommitSubjectLength(input_api, output_api):
  """Ensures commit's subject length is no longer than 65 chars."""
  name = "git-commit subject"
  cmd = ["git", "log", "-1", "--pretty=%s"]
  start = input_api.time.time()
  proc = subprocess2.Popen(
      cmd,
      stderr=subprocess2.PIPE,
      stdout=subprocess2.PIPE,
      universal_newlines=True)

  stdout, _ = proc.communicate()
  duration = input_api.time.time() - start

  if not re.match(r"^Revert",
                  stdout) and (len(stdout) - 1) > _GIT_COMMIT_SUBJECT_LENGTH:
    failure_msg = (
        "The commit subject: %s is too long (%d chars)\n"
        "Try to keep this to 50 or less (up to 65 is permitted for "
        "non-reverts).\n"
        "https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-"
        "Project#_commit_guidelines") % (stdout, len(stdout) - 1)
    return output_api.PresubmitError("%s\n (%4.2fs) failed\n%s" %
                                     (name, duration, failure_msg))

  return output_api.PresubmitResult("%s\n (%4.2fs) success" % (name, duration))


def _CheckDuplicateFiles(input_api, output_api):
  """Ensures there are not repeated filenames."""
  all_files = []
  for f in input_api.change.AllFiles():
    for include_file in _INCLUDE_SOURCE_FILES_ONLY:
      if re.match(include_file, f):
        all_files.append(f)
        break

  basename_to_path = {}
  for f in all_files:
    basename_file = input_api.basename(f)
    if basename_file in basename_to_path:
      basename_to_path[basename_file].append(f)
    else:
      basename_to_path[basename_file] = [f]

  dupes = []
  for files in basename_to_path.values():
    if len(files) > 1:
      dupes.extend(files)

  if dupes:
    return output_api.PresubmitError(
        "Duplicate source files, rebase or rename some to make them unique:\n%s"
        % dupes)
  return output_api.PresubmitResult("No duplicates, success\n")


def _GetFilesToSkip(input_api):
  return list(input_api.DEFAULT_FILES_TO_SKIP) + [
      r"swig/.*\.py$",
      r"\.pylintrc$",
  ]


def _RunManCmd(input_api, output_api, man_file):
  """man command wrapper."""
  cmd = ["man", "--warnings", "-EUTF-8", "-l", "-Tutf8", "-Z", man_file]
  name = "Check %s file." % man_file
  start = input_api.time.time()
  output, _ = subprocess2.communicate(
      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  duration = input_api.time.time() - start
  if output[1]:
    return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
                                     (name, " ".join(cmd), duration, output[1]))
  return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
                                    (name, " ".join(cmd), duration))


def _RunShellCheckCmd(input_api, output_api, bash_file):
  """shellcheck command wrapper."""
  cmd = ["shellcheck", "-x", "-oall", "-sbash", bash_file]
  name = "Check %s file." % bash_file
  start = input_api.time.time()
  output, rc = subprocess2.communicate(
      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  duration = input_api.time.time() - start
  if rc == 0:
    return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
                                      (name, " ".join(cmd), duration))
  return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
                                   (name, " ".join(cmd), duration, output[1]))


def _RunShfmtCheckCmd(input_api, output_api, bash_file):
  """shfmt command wrapper."""
  cmd = [
      "shfmt", "-i", _BASH_INDENTATION, "-bn", "-ci", "-sr", "-kp", "-d",
      bash_file
  ]
  name = "Check %s file." % bash_file
  start = input_api.time.time()
  output, rc = subprocess2.communicate(
      cmd, stdout=None, stderr=subprocess2.PIPE, universal_newlines=True)
  duration = input_api.time.time() - start
  if rc == 0:
    return output_api.PresubmitResult("%s\n%s (%4.2fs)\n" %
                                      (name, " ".join(cmd), duration))
  return output_api.PresubmitError("%s\n%s (%4.2fs) failed\n%s" %
                                   (name, " ".join(cmd), duration, output[1]))


def _RunCmdOnCheckedFiles(input_api, output_api, run_cmd, files_to_check):
  """Ensure that libwebp/ files are clean."""
  file_filter = lambda x: input_api.FilterSourceFile(
      x, files_to_check=files_to_check, files_to_skip=None)

  affected_files = input_api.change.AffectedFiles(file_filter=file_filter)
  results = [
      run_cmd(input_api, output_api, f.AbsoluteLocalPath())
      for f in affected_files
  ]
  return results


def _CommonChecks(input_api, output_api):
  """Ensures this patch does not have trailing spaces, extra EOLs,
     or long lines.
  """
  results = []
  results.extend(
      input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol(
          input_api, output_api))
  results.extend(
      input_api.canned_checks.CheckChangeHasNoTabs(input_api, output_api))
  results.extend(
      input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
          input_api, output_api))
  results.append(_CheckCommitSubjectLength(input_api, output_api))
  results.append(_CheckDuplicateFiles(input_api, output_api))

  source_file_filter = lambda x: input_api.FilterSourceFile(
      x, files_to_skip=_GetFilesToSkip(input_api))
  results.extend(
      input_api.canned_checks.CheckLongLines(
          input_api,
          output_api,
          maxlen=_LIBWEBP_MAX_LINE_LENGTH,
          source_file_filter=source_file_filter))

  results.extend(
      input_api.canned_checks.CheckPatchFormatted(
          input_api,
          output_api,
          check_clang_format=False,
          check_python=True,
          result_factory=output_api.PresubmitError))
  results.extend(
      _RunCmdOnCheckedFiles(input_api, output_api, _RunManCmd,
                            _INCLUDE_MAN_FILES_ONLY))
  # Run pylint.
  results.extend(
      input_api.canned_checks.RunPylint(
          input_api,
          output_api,
          files_to_skip=_GetFilesToSkip(input_api),
          pylintrc=".pylintrc",
          version="2.7"))

  # Binaries shellcheck and shfmt are not installed in depot_tools.
  # Installation is needed
  try:
    subprocess2.communicate(["shellcheck", "--version"])
    results.extend(
        _RunCmdOnCheckedFiles(input_api, output_api, _RunShellCheckCmd,
                              _INCLUDE_BASH_FILES_ONLY))
    print("shfmt")
    subprocess2.communicate(["shfmt", "-version"])
    results.extend(
        _RunCmdOnCheckedFiles(input_api, output_api, _RunShfmtCheckCmd,
                              _INCLUDE_BASH_FILES_ONLY))
  except OSError as os_error:
    results.append(
        output_api.PresubmitPromptWarning(
            "%s\nPlease install missing binaries locally." % os_error.args[0]))
  return results


def CheckChangeOnUpload(input_api, output_api):
  results = []
  results.extend(_CommonChecks(input_api, output_api))
  return results


def CheckChangeOnCommit(input_api, output_api):
  results = []
  results.extend(_CommonChecks(input_api, output_api))
  return results
