#!/usr/bin/env bash

write_command_file() {
  local file="$1"
  shift
  printf '%q\n' "$@" > "$file"
}

command_matches() {
  local file="$1"
  shift

  local tmp
  tmp="$(mktemp)"
  write_command_file "$tmp" "$@"
  if [[ -f "$file" ]] && cmp -s "$tmp" "$file"; then
    rm -f "$tmp"
    return 0
  fi
  rm -f "$tmp"
  return 1
}

depfile_newer_than() {
  local depfile="$1"
  local output="$2"

  [[ -f "$depfile" ]] || return 0

  local deps
  deps="$(tr '\\\n' '  ' < "$depfile" | sed -e 's/^[^:]*://')"
  local dep
  for dep in $deps; do
    if [[ -f "$dep" && "$dep" -nt "$output" ]]; then
      return 0
    fi
  done
  return 1
}

object_needs_rebuild() {
  local obj="$1"
  local depfile="$2"
  local cmdfile="$3"
  shift 3

  [[ -f "$obj" ]] || return 0
  command_matches "$cmdfile" "$@" || return 0
  depfile_newer_than "$depfile" "$obj" && return 0
  return 1
}

link_needs_rebuild() {
  local output="$1"
  local cmdfile="$2"
  shift 2

  [[ -f "$output" ]] || return 0
  command_matches "$cmdfile" "$@" || return 0

  local arg
  for arg in "$@"; do
    if [[ -f "$arg" && "$arg" -nt "$output" ]]; then
      return 0
    fi
  done
  return 1
}

source_object_path() {
  local source="$1"
  local root_dir="$2"
  local obj_dir="$3"
  local relative="${source#"$root_dir"/}"

  if [[ "$relative" == "$source" ]]; then
    relative="$(basename "$source")"
  fi

  relative="${relative//\//__}"
  printf '%s/%s.o' "$obj_dir" "${relative%.*}"
}

compile_wasm_object() {
  local target="$1"
  local source="$2"
  local obj="$3"
  local build_dir="$4"
  shift 4

  local stdout_log="$build_dir/$target.stdout.log"
  local stderr_log="$build_dir/$target.stderr.log"
  local depfile="$obj.d"
  local cmdfile="$obj.cmd"
  local cmd=(emcc "$@" -MMD -MP -MF "$depfile" -c "$source" -o "$obj")

  mkdir -p "$(dirname "$obj")"
  if ! object_needs_rebuild "$obj" "$depfile" "$cmdfile" "${cmd[@]}"; then
    printf 'up to date: %s\n' "$source" >> "$stdout_log"
    return 0
  fi

  printf 'compile: %s\n' "$source" >> "$stdout_log"
  if "${cmd[@]}" >> "$stdout_log" 2>> "$stderr_log"; then
    write_command_file "$cmdfile" "${cmd[@]}"
    return 0
  fi
  return 1
}

link_wasm_module() {
  local target="$1"
  local output="$2"
  local build_dir="$3"
  shift 3

  local stdout_log="$build_dir/$target.stdout.log"
  local stderr_log="$build_dir/$target.stderr.log"
  local cmdfile="$build_dir/$(basename "$output").cmd"
  local wasm_output="${output%.js}.wasm"
  local cmd=(emcc "$@" -o "$output")

  if [[ -f "$output" && ! -f "$wasm_output" ]]; then
    printf 'missing paired wasm output: %s\n' "$wasm_output" >> "$stdout_log"
    rm -f "$output"
  fi

  if ! link_needs_rebuild "$output" "$cmdfile" "${cmd[@]}"; then
    printf 'up to date: %s\n' "$output" >> "$stdout_log"
    return 0
  fi

  printf 'link: %s\n' "$output" >> "$stdout_log"
  if "${cmd[@]}" >> "$stdout_log" 2>> "$stderr_log"; then
    write_command_file "$cmdfile" "${cmd[@]}"
    return 0
  fi
  return 1
}
