{ stdenv
, lib
, buildDotnetModule
, dotnetCorePackages
, callPackage
, fetchFromGitHub
, ffmpeg
, glfw
, libglvnd
, libogg
, libvorbis
, openal
, portaudio
, rtmidi
}:

let
  csprojName = if stdenv.hostPlatform.isLinux then
    "FamiStudio.Linux"
  else if stdenv.hostPlatform.isDarwin then
    "FamiStudio.Mac"
  else throw "Don't know how to build FamiStudio for ${stdenv.hostPlatform.system}";
in
buildDotnetModule rec {
  pname = "famistudio";
  version = "4.2.1";

  src = fetchFromGitHub {
    owner = "BleuBleu";
    repo = "FamiStudio";
    rev = "refs/tags/${version}";
    hash = "sha256-WYy/6cWQg3Ayok/eAdnvlWAvdcuhy/sdlWOVvaYcPkc=";
  };

  postPatch = let
    libname = library: "${library}${stdenv.hostPlatform.extensions.sharedLibrary}";
    buildNativeWrapper = args: callPackage ./build-native-wrapper.nix (args // {
      inherit version src;
      sourceRoot = "${src.name}/ThirdParty/${args.depname}";
    });
    nativeWrapperToReplaceFormat = args: let
      libPrefix = lib.optionalString stdenv.hostPlatform.isLinux "lib";
    in {
      package = buildNativeWrapper args;
      expectedName = "${libPrefix}${args.depname}";
      ourName = "${libPrefix}${args.depname}";
    };
    librariesToReplace = [
      # Unmodified native libraries that we can fully substitute
      { package = glfw; expectedName = "libglfw"; ourName = "libglfw"; }
      { package = rtmidi; expectedName = "librtmidi"; ourName = "librtmidi"; }
    ] ++ lib.optionals stdenv.hostPlatform.isLinux [
      { package = openal; expectedName = "libopenal32"; ourName = "libopenal"; }
    ] ++ lib.optionals stdenv.hostPlatform.isDarwin [
      { package = portaudio; expectedName = "libportaudio.2"; ourName = "libportaudio.2"; }
    ] ++ [
      # Native libraries, with extra code for the C# wrapping
      (nativeWrapperToReplaceFormat { depname = "GifDec"; })
      (nativeWrapperToReplaceFormat { depname = "NesSndEmu"; })
      (nativeWrapperToReplaceFormat { depname = "NotSoFatso"; extraPostPatch = ''
        # C++17 does not allow register storage class specifier
        substituteInPlace build.sh \
          --replace-fail "$CXX" "$CXX -std=c++14"
      ''; })
      (nativeWrapperToReplaceFormat { depname = "ShineMp3"; })
      (nativeWrapperToReplaceFormat { depname = "Stb"; })
      (nativeWrapperToReplaceFormat { depname = "Vorbis"; buildInputs = [ libogg libvorbis ]; })
    ];
    libraryReplaceArgs = lib.strings.concatMapStringsSep " "
      (library: "--replace-fail '${libname library.expectedName}' '${lib.getLib library.package}/lib/${libname library.ourName}'")
      librariesToReplace;
  in ''
    # Don't use any prebuilt libraries
    rm FamiStudio/*.{dll,dylib,so*}

    # Replace copying of vendored prebuilt native libraries with copying of our native libraries
    substituteInPlace ${projectFile} ${libraryReplaceArgs}

    # Un-hardcode target platform if set
    sed -i -e '/PlatformTarget/d' ${projectFile}

    # Don't require a special name to be preserved, our OpenAL isn't 32-bit
    substituteInPlace FamiStudio/Source/AudioStreams/OpenALStream.cs \
      --replace-fail 'libopenal32' 'libopenal'
  '';

  projectFile = "FamiStudio/${csprojName}.csproj";
  nugetDeps = ./deps.nix;
  dotnet-sdk = dotnetCorePackages.sdk_7_0;
  dotnet-runtime = dotnetCorePackages.runtime_7_0;

  runtimeDeps = lib.optionals stdenv.hostPlatform.isLinux [
    libglvnd
  ];

  executables = [ "FamiStudio" ];

  postInstall = ''
    mkdir -p $out/share/famistudio
    for datdir in Setup/Demo\ {Instruments,Songs}; do
      cp -R "$datdir" $out/share/famistudio/
    done
  '';

  postFixup = ''
    # FFMpeg looked up from PATH
    wrapProgram $out/bin/FamiStudio \
      --prefix PATH : ${lib.makeBinPath [ ffmpeg ]}
  '';

  passthru.updateScript = ./update.sh;

  meta = with lib; {
    homepage = "https://famistudio.org/";
    description = "NES Music Editor";
    longDescription = ''
      FamiStudio is very simple music editor for the Nintendo Entertainment System
      or Famicom. It is targeted at both chiptune artists and NES homebrewers.
    '';
    license = licenses.mit;
    maintainers = with maintainers; [ OPNA2608 ];
    platforms = platforms.unix;
    mainProgram = "FamiStudio";
  };
}
