{ lib
, stdenv
, fetchpatch
, makeWrapper
, makeDesktopItem
, copyDesktopItems
, fetchFromGitHub
, gradle
, jdk17

# for arc
, SDL2
, pkg-config
, ant
, curl
, wget
, alsa-lib
, alsa-plugins
, glew
, glew-egl

# for soloud
, libpulseaudio ? null
, libjack2 ? null

, nixosTests


# Make the build version easily overridable.
# Server and client build versions must match, and an empty build version means
# any build is allowed, so this parameter acts as a simple whitelist.
# Takes the package version and returns the build version.
, makeBuildVersion ? (v: v)
, enableClient ? true
, enableServer ? true

, enableWayland ? false
}:

let
  pname = "mindustry";
  version = "146";
  buildVersion = makeBuildVersion version;

  jdk = jdk17;

  selectedGlew = if enableWayland then glew-egl else glew;

  Mindustry = fetchFromGitHub {
    owner = "Anuken";
    repo = "Mindustry";
    rev = "v${version}";
    hash = "sha256-pJAJjb8rgDL5q2hfuXH2Cyb1Szu4GixeXoLMdnIAlno=";
  };
  Arc = fetchFromGitHub {
    owner = "Anuken";
    repo = "Arc";
    rev = "v${version}";
    hash = "sha256-L+5fshI1oo1lVdTMTBuPzqtEeR2dq1NORP84rZ83rT0=";
  };
  soloud = fetchFromGitHub {
    owner = "Anuken";
    repo = "soloud";
    # This is pinned in Arc's arc-core/build.gradle
    rev = "v0.9";
    hash = "sha256-6KlqOtA19MxeqZttNyNrMU7pKqzlNiA4rBZKp9ekanc=";
  };

  desktopItem = makeDesktopItem {
    name = "Mindustry";
    desktopName = "Mindustry";
    exec = "mindustry";
    icon = "mindustry";
    categories = [ "Game" ];
  };

in
assert lib.assertMsg (enableClient || enableServer)
  "mindustry: at least one of 'enableClient' and 'enableServer' must be true";
stdenv.mkDerivation {
  inherit pname version;

  unpackPhase = ''
    cp -r ${Mindustry} Mindustry
    cp -r ${Arc} Arc
    chmod -R u+w -- Mindustry Arc
    cp -r ${soloud} Arc/arc-core/csrc/soloud
    chmod -R u+w -- Arc/arc-core/csrc/soloud
  '';

  patches = [
    ./0001-fix-include-path-for-SDL2-on-linux.patch
    # Fix build with gradle 8.8, remove on next Arc release
    (fetchpatch {
      url = "https://github.com/Anuken/Arc/commit/2a91c51bf45d700091e397fd0b62384763901ae6.patch";
      hash = "sha256-sSD78GmF14vBvNe+ajUJ4uIc4p857shTP/UkAK6Pyyg=";
      extraPrefix = "Arc/";
      stripLen = 1;
    })
    (fetchpatch {
      url = "https://github.com/Anuken/Arc/commit/d7f8ea858c425410dbd43374271a703d4443b432.patch";
      hash = "sha256-5LPgBOV0r/dUtpyxitTu3/9tMIqjeIKfGVJi3MEr7fQ=";
      extraPrefix = "Arc/";
      stripLen = 1;
    })
    (fetchpatch {
      url = "https://github.com/Anuken/Mindustry/commit/695dad201fb4c2b4252f2ee5abde32e968169ba5.patch";
      hash = "sha256-bbTjyfUl+XFG/dgD1XPddVKD/ImOP5ARAP3q0FPnt58=";
      name = "always-use-local-arc-1.patch";
      stripLen = 1; extraPrefix = "Mindustry/";
    })
    (fetchpatch {
      url = "https://github.com/Anuken/Mindustry/commit/f6082225e859c759c8d9c944250b6ecd490151ed.patch";
      hash = "sha256-xFHdAUTS1EiHNQqw6qfzYk2LMr/DjeHoEzQfcfOUcFs=";
      name = "always-use-local-arc-2.patch";
      stripLen = 1; extraPrefix = "Mindustry/";
    })
    (fetchpatch {
      url = "https://github.com/Anuken/Mindustry/commit/e4eadbbb7f35db3093a0a3d13272bdfbedfaead3.patch";
      hash = "sha256-L/XQAxh6UgKsTVTgQKDXNRIAdtVtaY4ameT/Yb/+1p8=";
      name = "always-use-local-arc-3.patch";
      stripLen = 1; extraPrefix = "Mindustry/";
    })
  ];

  postPatch = ''
    # Ensure the prebuilt shared objects don't accidentally get shipped
    rm -r Arc/natives/natives-*/libs/*
    rm -r Arc/backends/backend-*/libs/*

    cd Mindustry

    # Remove unbuildable iOS stuff
    sed -i '/^project(":ios"){/,/^}/d' build.gradle
    sed -i '/robo(vm|VM)/d' build.gradle
    rm ios/build.gradle
  '';

  mitmCache = gradle.fetchDeps {
    inherit pname;
    data = ./deps.json;
  };

  __darwinAllowLocalNetworking = true;

  buildInputs = lib.optionals enableClient [
    SDL2
    selectedGlew
    alsa-lib
  ];
  nativeBuildInputs = [
    pkg-config
    gradle
    makeWrapper
    jdk
  ] ++ lib.optionals enableClient [
    ant
    copyDesktopItems
    curl
    wget
  ];

  desktopItems = lib.optional enableClient desktopItem;

  gradleFlags = [ "-Pbuildversion=${buildVersion}" "-Dorg.gradle.java.home=${jdk}" ];

  buildPhase = with lib; optionalString enableServer ''
    gradle server:dist
  '' + optionalString enableClient ''
    pushd ../Arc
    gradle jnigenBuild
    gradle jnigenJarNativesDesktop
    glewlib=${lib.getLib selectedGlew}/lib/libGLEW.so
    sdllib=${lib.getLib SDL2}/lib/libSDL2.so
    patchelf backends/backend-sdl/libs/linux64/libsdl-arc*.so \
      --add-needed $glewlib \
      --add-needed $sdllib
    # Put the freshly-built libraries where the pre-built libraries used to be:
    cp arc-core/libs/*/* natives/natives-desktop/libs/
    cp extensions/freetype/libs/*/* natives/natives-freetype-desktop/libs/
    popd

    gradle desktop:dist
  '';

  installPhase = with lib; let
    installClient = ''
      install -Dm644 desktop/build/libs/Mindustry.jar $out/share/mindustry.jar
      mkdir -p $out/bin
      makeWrapper ${jdk}/bin/java $out/bin/mindustry \
        --add-flags "-jar $out/share/mindustry.jar" \
        --suffix LD_LIBRARY_PATH : ${lib.makeLibraryPath [libpulseaudio alsa-lib libjack2]} \
        --set ALSA_PLUGIN_DIR ${alsa-plugins}/lib/alsa-lib/'' + optionalString enableWayland '' \
        --set SDL_VIDEODRIVER wayland \
        --set SDL_VIDEO_WAYLAND_WMCLASS Mindustry
      '' + ''

      # Retain runtime depends to prevent them from being cleaned up.
      # Since a jar is a compressed archive, nix can't figure out that the dependency is actually in there,
      # and will assume that it's not actually needed.
      # This can cause issues.
      # See https://github.com/NixOS/nixpkgs/issues/109798.
      echo "# Retained runtime dependencies: " >> $out/bin/mindustry
      for dep in ${SDL2.out} ${alsa-lib.out} ${selectedGlew.out}; do
        echo "# $dep" >> $out/bin/mindustry
      done

      install -Dm644 core/assets/icons/icon_64.png $out/share/icons/hicolor/64x64/apps/mindustry.png
    '';
    installServer = ''
      install -Dm644 server/build/libs/server-release.jar $out/share/mindustry-server.jar
      mkdir -p $out/bin
      makeWrapper ${jdk}/bin/java $out/bin/mindustry-server \
        --add-flags "-jar $out/share/mindustry-server.jar"
    '';
  in ''
    runHook preInstall
  '' + optionalString enableClient installClient
     + optionalString enableServer installServer
     + ''
    runHook postInstall
  '';

  postGradleUpdate = ''
    # this fetches non-gradle dependencies
    cd ../Arc
    gradle preJni
  '';

  passthru.tests.nixosTest = nixosTests.mindustry;

  meta = with lib; {
    homepage = "https://mindustrygame.github.io/";
    downloadPage = "https://github.com/Anuken/Mindustry/releases";
    description = "Sandbox tower defense game";
    sourceProvenance = with sourceTypes; [
      fromSource
      binaryBytecode  # deps
    ];
    license = licenses.gpl3Plus;
    maintainers = with maintainers; [ chkno fgaz thekostins ];
    platforms = platforms.all;
    # TODO alsa-lib is linux-only, figure out what dependencies are required on Darwin
    broken = enableClient && stdenv.isDarwin;
  };
}
