name: Build

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
    branches:
      - 'master'
      - '!ci_test_*'
    tags-ignore:
      - '*'
  pull_request:
    branches:
      - 'master'

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions-rust-lang/setup-rust-toolchain@v1
      - run: rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu i686-unknown-linux-gnu powerpc-unknown-linux-gnu riscv64gc-unknown-linux-gnu wasm32-unknown-unknown
      - run: RUSTFLAGS="-C target-feature=+neon,-fp16" cargo build --target aarch64-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+neon,+fp16" cargo +nightly build --target aarch64-unknown-linux-gnu --features nightly_f16,nightly_i8mm
      - run: RUSTFLAGS="-C target-feature=+sse4.1" cargo build --target i686-unknown-linux-gnu
      - run: cargo build --target powerpc-unknown-linux-gnu
      - run: cargo build --target riscv64gc-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+sse4.1" cargo build --target x86_64-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+sse4.1,+f16c" cargo +nightly build --features nightly_f16 --target x86_64-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+avx2,+f16c" cargo +nightly build --features nightly_f16 --target x86_64-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+avx2" cargo build --target x86_64-unknown-linux-gnu
      - run: RUSTFLAGS="-C target-feature=+simd128" cargo build --target wasm32-unknown-unknown
      - run: RUSTFLAGS="-C target-feature=+avx2" cargo build --target x86_64-unknown-linux-gnu --no-default-features

  clippy:
    name: Clippy
    strategy:
      matrix:
        os: [ ubuntu-latest, ubuntu-24.04-arm ]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - run: rustup component add clippy
      - run: cargo clippy -- -D warnings
      - run: cargo clippy --no-default-features -- -D warnings

  clippy_nightly:
    name: Clippy Nightly
    strategy:
      matrix:
        os: [ ubuntu-latest, ubuntu-24.04-arm ]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: rustup component add clippy
      - run: rustup target add wasm32-unknown-unknown
      - run: cargo clippy --all-features -- -D warnings
      - run: RUSTFLAGS="-C target-feature=+simd128" cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings

  tests_x86:
    name: Testing x86
    strategy:
      matrix:
        feature: [ avx, sse, "", nightly_f16 ]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: cargo +nightly test --features "${{ matrix.feature }}"

  tests_aarch64_neon_no_rdm_no_i8mm:
    name: Testing AArch64 NEON (no RDM, no I8MM)
    runs-on: ubuntu-24.04-arm
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user
      - run: rustup target add aarch64-unknown-linux-gnu
      - name: Run tests
        run: |
          failed=0
          tests=$(cargo +nightly test --features "neon,nightly_f16,nightly_i8mm,rdm" --lib -- --list 2>/dev/null | grep ': test' | sed 's/: test//')
          while read -r test; do
              rc=0
              echo "Running: $test"
              output=$(RUSTFLAGS="-C target-feature=+neon,-rdm" \
              CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="qemu-aarch64 -cpu cortex-a53" \
              cargo +nightly test --target aarch64-unknown-linux-gnu --lib \
              --features "neon,nightly_f16,nightly_i8mm,rdm" -- "$test" --exact 2>&1) || rc=$?
              echo "$output" | grep -E "FAILED|ok|signal 4"
              if [ "${rc:-0}" -ne 0 ]; then
                echo "❌ Test failed: $test"
                echo "$output"
                failed=1
              else
                echo "$output" | grep -E "ok|signal 4"
              fi
          done <<< "$tests"
          exit $failed

  tests_aarch64_neon_rdm_no_i8mm:
    name: Testing AArch64 NEON (RDM, no I8MM)
    runs-on: ubuntu-24.04-arm
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user
      - run: rustup target add aarch64-unknown-linux-gnu
      - name: Run tests
        run: |
          failed=0
          tests=$(cargo +nightly test --features "neon,nightly_f16,nightly_i8mm,rdm" --lib -- --list 2>/dev/null | grep ': test' | sed 's/: test//')
          while read -r test; do
              rc=0
              echo "Running: $test"
              output=$(RUSTFLAGS="-C target-feature=+neon,-rdm" \
              CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="qemu-aarch64 -cpu cortex-a72" \
              cargo +nightly test --target aarch64-unknown-linux-gnu --lib \
              --features "neon,nightly_f16,nightly_i8mm,rdm" -- "$test" --exact 2>&1) || rc=$?
              echo "$output" | grep -E "FAILED|ok|signal 4"
              if [ "${rc:-0}" -ne 0 ]; then
                echo "❌ Test failed: $test"
                echo "$output"
                failed=1
              else
                echo "$output" | grep -E "ok|signal 4"
              fi
          done <<< "$tests"
          exit $failed

  tests_x86_sse_isolated:
    name: Testing x86 SSE isolated
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user
      - run: |
          failed=0
          tests=$(cargo +nightly test --lib --features "avx,sse,nightly_f16" -- --list 2>/dev/null | grep ': test' | sed 's/: test//')
          while read -r test; do
              rc=0
              echo "Running: $test"
              output=$(RUSTFLAGS="-C target-feature=+sse4.1,-avx,-avx2,-fma" \
              CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="qemu-x86_64 -cpu Nehalem" \
              cargo +nightly test --lib --features "avx,sse,nightly_f16" -- "$test" --exact 2>&1) || rc=$?
              echo "$output" | grep -E "FAILED|ok|signal 4"
              if [ "${rc:-0}" -ne 0 ]; then
                echo "❌ Test failed: $test"
                echo "$output"
                failed=1
              else
                echo "$output" | grep -E "ok|signal 4"
              fi
          done <<< "$tests"
          exit $failed

  tests_x86_avx2_isolated:
    name: Testing x86 AVX2 isolated (no FMA)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: |
          sudo apt-get update
          sudo apt-get install -y qemu-user
      - run: |
          failed=0
          tests=$(cargo +nightly test --features "avx,nightly_f16" --lib -- --list 2>/dev/null | grep ': test' | sed 's/: test//')
          while read -r test; do
              rc=0
              echo "Running: $test"
              output=$(RUSTFLAGS="-C target-feature=+avx2,-fma" \
              CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="qemu-x86_64 -cpu Haswell-noTSX,avx2=on,fma=off" \
              cargo +nightly test --lib --features "avx,nightly_f16" --target x86_64-unknown-linux-gnu -- "$test" --exact 2>&1) || rc=$?
              echo "$output" | grep -E "FAILED|ok|signal 4"
              if [ "${rc:-0}" -ne 0 ]; then
                echo "❌ Test failed: $test"
                echo "$output"
                failed=1
              else
                echo "$output" | grep -E "ok|signal 4"
              fi
          done <<< "$tests"
          exit $failed

  tests_arm:
    name: Testing ARM
    runs-on: ubuntu-24.04-arm
    strategy:
      matrix:
        feature: [ "", "neon", "neon,rdm,nightly_i8mm,nightly_f16", "sve" ]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: cargo +nightly test --features "${{ matrix.feature }}"

  fuzz_rgba_8bit:
    name: Fuzzing 8bit
    runs-on: ubuntu-24.04-arm
    if: github.event_name == 'pull_request'
    strategy:
      matrix:
        feature: [ "rdm,nightly_f16", "neon,nightly_f16" ]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: cargo install cargo-fuzz
      - run: cargo fuzz run resize_rgba --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgb --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr8 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_plane --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_u16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_f16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_u16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_f16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr_f16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_plane_u16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_f32 -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_f32 -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr_f32 -- -max_total_time=10
      - run: cargo fuzz run resize_plane_f32 -- -max_total_time=10

  fuzz_nightly_arm:
    name: Fuzzing 8bit I8MM
    runs-on: ubuntu-24.04-arm
    if: github.event_name == 'pull_request'
    strategy:
      matrix:
        feature: [ nightly_i8mm ]
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: cargo install cargo-fuzz
      - run: cargo fuzz run resize_rgba --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgb --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr8 --features ${{ matrix.feature }} -- -max_total_time=10

  fuzz_nightly_arm_sve:
    name: Fuzzing SVE2
    runs-on: ubuntu-24.04-arm
    if: github.event_name == 'pull_request'
    strategy:
      matrix:
        feature: [ sve ]
    steps:
      - uses: actions/checkout@v6

      - name: Verify SVE2 available
        run: |
          python3 ./assets/sve_vl.py
          grep -m1 "sve2" /proc/cpuinfo || (echo "SVE2 not available on this runner" && exit 1)

      - uses: dtolnay/rust-toolchain@nightly

      - run: cargo install cargo-fuzz
      - run: cargo fuzz run resize_rgba  --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgb   --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr8 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_u16 --features ${{ matrix.feature }} -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_u16 --features ${{ matrix.feature }} -- -max_total_time=10

  fuzz_avx_sse:
    name: Fuzzing AVX,SSE
    if: github.event_name == 'pull_request'
    strategy:
      matrix:
        feature: [ "sse,nightly_f16", "avx,nightly_f16", "" ]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - run: cargo install cargo-fuzz
      - run: cargo fuzz run resize_rgba --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgb --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr8 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_plane --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run colorspaces --features colorspaces -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_u16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_f16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_u16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_f16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr_f16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_plane_u16 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgba_f32 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_rgb_f32 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_cbcr_f32 --features "${{ matrix.feature }}" -- -max_total_time=10
      - run: cargo fuzz run resize_plane_f32 --features "${{ matrix.feature }}" -- -max_total_time=10

  python_bindings:
    name: Python bindings
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Create venv and install dependencies
        run: |
          python -m venv pic-scale-py/.venv
          pic-scale-py/.venv/bin/pip install --upgrade pip
          pic-scale-py/.venv/bin/pip install maturin pytest numpy Pillow
      - name: Build and install zaft
        working-directory: pic-scale-py
        run: .venv/bin/maturin develop --release
      - name: Run Python tests
        working-directory: pic-scale-py
        run: .venv/bin/pytest tests/test_pic_scale.py -v