#!/usr/bin/env bash
# Copyright (c) The mldsa-native project authors
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT

set -o errexit
set -o errtrace
set -o nounset
set -o pipefail

# consts
ROOT="$(realpath "$(dirname "$0")"/../)"
GITHUB_STEP_SUMMARY=${GITHUB_STEP_SUMMARY:-/dev/stdout}

# Check if we're in GitHub context
IN_GITHUB_CONTEXT=false
if [[ $GITHUB_STEP_SUMMARY != "/dev/stdout" ]]; then
  IN_GITHUB_CONTEXT=true
fi

# Standard color definitions
GREEN="\033[32m"
RED="\033[31m"
BLUE="\033[94m"
BOLD="\033[1m"
NORMAL="\033[0m"

info()
{
  printf "%b %b\n" "${GREEN}✓" "${NORMAL}${*}"
}

error()
{
  printf "%b %b\n" "${RED}✗" "${NORMAL}${*}"
}

checkerr()
{
  local code=$?
  local title="$1"
  local out="$2"
  local success=true
  if [[ $code == 127 ]]; then
    success=false
  fi

  if [[ ${#out} != 0 ]]; then
    if $IN_GITHUB_CONTEXT; then
      echo "$out" | while read -r file line; do
        echo "::error file=$file,line=${line:-1},title=Format error::$file require to be formatted"
      done
    fi
    success=false
  fi

  if $success; then
    info "$title"
    gh_summary_success "$title"
  else
    error "$title"
    SUCCESS=false
    gh_summary_failure "$title"
  fi
}

gh_group_start()
{
  if $IN_GITHUB_CONTEXT; then
    echo "::group::$1"
  fi
}

gh_group_end()
{
  if $IN_GITHUB_CONTEXT; then
    echo "::endgroup::"
  fi
}

gh_summary_success()
{
  if $IN_GITHUB_CONTEXT; then
    echo ":white_check_mark: $1" >>"$GITHUB_STEP_SUMMARY"
  fi
}

gh_summary_failure()
{
  if $IN_GITHUB_CONTEXT; then
    echo ":x: $1" >>"$GITHUB_STEP_SUMMARY"
  fi
}

gh_error()
{
  error "$4"
  if $IN_GITHUB_CONTEXT; then
    echo "::error file=$1,line=${2:-1},title=$3::$4"
  fi
}

gh_error_simple()
{
  if $IN_GITHUB_CONTEXT; then
    echo "::error title=$1::$2"
  fi
}

# Formatting
SUCCESS=true

gh_group_start "Linting nix files with nixpkgs-fmt"
checkerr "Lint nix" "$(nixpkgs-fmt --check "$ROOT")"
gh_group_end

gh_group_start "Linting shell scripts with shfmt"
checkerr "Lint shell" "$(shfmt -s -l -i 2 -ci -fn $(shfmt -f $(git grep -l '' :/)))"
gh_group_end

gh_group_start "Linting python scripts with black"
if ! diff=$(black --check --diff -q --include "(scripts/tests|scripts/simpasm|scripts/cfify|scripts/autogen|scripts/check-namespace|\.py$)" "$ROOT"); then
  gh_error_simple "Format error" "$diff"
  error "Lint python"
  SUCCESS=false
  gh_summary_failure "Lint python"
else
  info "Lint Python"
  gh_summary_success "Lint Python"
fi
gh_group_end

gh_group_start "Linting c files with clang-format"
checkerr "Lint C" "$(clang-format $(git ls-files ":/*.c" ":/*.h") --Werror --dry-run 2>&1 | grep "error:" | cut -d ':' -f 1,2 | tr ':' ' ')"
gh_group_end

check-eol-dry-run()
{
  for file in $(git ls-files -- ":/" ":/!:*.png"); do
    # Ignore symlinks
    if [[ ! -L $file && $(tail -c1 "$file" | wc -l) == 0 ]]; then
      l=$(wc -l <"$file")
      echo "$file $l"
    fi
  done
}
gh_group_start "Checking eol"
checkerr "Check eol" "$(check-eol-dry-run)"
gh_group_end

check-spdx()
{
  local success=true
  for file in $(git ls-files -- ":/" ":/!:*.json" ":/!:*.png" ":/!:*LICENSE*" ":/!:.git*" ":/!:flake.lock"); do
    # Ignore symlinks
    if [[ ! -L $file && $(grep "SPDX-License-Identifier:" $file | wc -l) == 0 ]]; then
      gh_error "$file" "${line:-1}" "Missing license header error" "$file is missing SPDX License header"
      success=false
    fi
  done
  for file in $(git ls-files -- "*.[chsS]" "*.py" "*.mk" "*.yml" "**/Makefile*" ":/!proofs/cbmc/*.py" ":/!examples/bring_your_own_fips202/custom_fips202/tiny_sha3/*" ":/!examples/custom_backend/mldsa_native/src/fips202/native/custom/src/*"); do
    # Ignore symlinks
    if [[ ! -L $file && $(grep "Copyright (c) The mldsa-native project authors" $file | wc -l) == 0 ]]; then
      gh_error "$file" "${line:-1}" "Missing copyright header error" "$file is missing copyright header"
      success=false
    fi
  done
  # For source files in dev/* and mldsa/*, we enforce `Apache-2.0 OR ISC OR MIT`
  for file in $(git ls-files -- "*.[chsSi]" | grep "^dev/\|^mldsa/"); do
    # Ignore symlinks
    if [[ ! -L $file && $(grep "SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT" $file | wc -l) == 0 ]]; then
      gh_error "$file" "${line:-1}" "Missing license header error" "$file is not licensed under 'Apache-2.0 OR ISC OR MIT'"
      success=false
    fi
  done

  if $success; then
    info "Check SPDX + Copyright"
    gh_summary_success "Check SPDX + Copyright"
  else
    error "Check SPDX + Copyright"
    SUCCESS=false
    gh_summary_failure "Check SPDX + Copyright"
  fi
}
gh_group_start "Checking SPDX + Copyright headers"
check-spdx
gh_group_end

check-autogenerated-files()
{
  if python3 $ROOT/scripts/autogen --dry-run; then
    info "Check native auto-generated files"
    gh_summary_success "Check native auto-generated files"
  else
    error "Check native auto-generated files"
    gh_summary_failure "Check native auto-generated files"
    SUCCESS=false
  fi
}

gh_group_start "Check native auto-generated files"
check-autogenerated-files
gh_group_end

check-magic()
{
  if python3 $ROOT/scripts/check-magic >/dev/null; then
    info "Check magic constants"
    gh_summary_success "Check magic constants"
  else
    error "Check magic constants"
    gh_summary_failure "Check magic constants"
    SUCCESS=false
  fi
}

gh_group_start "Check magic constants"
check-magic
gh_group_end

if ! $SUCCESS; then
  exit 1
fi

check-contracts()
{
  if python3 $ROOT/scripts/check-contracts >/dev/null; then
    info "Check contracts"
    gh_summary_success "Check contracts"
  else
    error "Check contracts"
    gh_summary_failure "Check contracts"
    SUCCESS=false
  fi
}

gh_group_start "Check contracts"
check-contracts
gh_group_end

if ! $SUCCESS; then
  exit 1
fi
