rec {
  description = "FAIL";

  inputs = {
    nixpkgs.url = "nixpkgs"; # Use nixpkgs from system registry
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = {
    self,
    nixpkgs,
    flake-utils,
  }:
  # Create a shell (and possibly package) for each possible system, not only x86_64-linux
    flake-utils.lib.eachDefaultSystem (
      system: let
        # =========================================================================================
        # Define pkgs/stdenvs
        # =========================================================================================
        pkgs = import nixpkgs {
          inherit system;
          config.allowUnfree = true;
          overlays = [];
        };

        i386_pkgs = import nixpkgs {
          inherit system;
          config.allowUnfree = true;
          overlays = [];

          # Build crosscompiler
          crossSystem = {
            config = "i386-elf";
            libc = "newlib";
          };
        };

        boost_pkgs =
          import
          (builtins.fetchTarball {
            url = "https://github.com/NixOS/nixpkgs/archive/824421b1796332ad1bcb35bc7855da832c43305f.tar.gz";
            sha256 = "sha256:1w6cjnakz1yi66rs8c6nmhymsr7bj82vs2hz200ipi1sfiq8dy4y";
          })
          {
            inherit system;
          };

        libdwarf_pkgs =
          import
          (builtins.fetchTarball {
            url = "https://github.com/NixOS/nixpkgs/archive/f597e7e9fcf37d8ed14a12835ede0a7d362314bd.tar.gz";
            sha256 = "sha256:1l79hh7jh7m8yc5mvc8dbg6s8rf30bgm994kf07xriqbzwfn158r";
          })
          {
            inherit system;
          };

        inherit (pkgs) lib stdenv;

        # =========================================================================================
        # Define shell environment
        # =========================================================================================

        # Setup the shell when entering the "nix develop" environment (bash script).
        shellHook = let
          # Add project-local fish abbrs here
          abbrs = {
            fail = "perl ./scripts/menu.pl";

            # C/C++:
            # cmake-debug = "${cmakeDebug}";
            # cmake-release = "${cmakeRelease}";
            # build-debug = "${buildDebug}";
            # build-release = "${buildRelease}";
          };

          eraseAbbr = name: value: "abbr --erase ${name} 2>/dev/null";
          createAbbr = name: value: ''abbr -a ${name} "${value}"'';

          # This will be sourced by the global fish config if INIT_PROJECT_SHELL gets unset
          unloadProjectShell = pkgs.writers.writeFish "unload-shell.fish" ''
            echo "Unloading \"${description}\" environment..."

            ${builtins.concatStringsSep "\n" (lib.mapAttrsToList eraseAbbr abbrs)}
          '';

          # This will be sourced by the global fish config if INIT_PROJECT_SHELL gets set
          initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
            # Unload just in case, to not have redefinition errors
            source ${unloadProjectShell}

            echo "Sourcing \"${description}\" environment..."

            ${builtins.concatStringsSep "\n" (lib.mapAttrsToList createAbbr abbrs)}
          '';
        in
          builtins.concatStringsSep "\n" [
            # Launch into pure fish shell
            ''
              # Can't do the "exec" with nix-direnv
              # - The "exec fish" would call direnv again => Infinite loop
              # - The shellHook is Bash/POSIX, so fish syntax doesn't work
              # exec "$(type -p fish)" -C "source ${initProjectShell} && abbr -a menu '${pkgs.bat}/bin/bat "${initProjectShell}"'"

              # Determine the project root, used e.g. in cmake scripts
              export FLAKE_PROJECT_ROOT="$(git rev-parse --show-toplevel)"
              export INIT_PROJECT_SHELL="${initProjectShell}"
              export UNLOAD_PROJECT_SHELL="${unloadProjectShell}"
            ''
          ];

        # ===========================================================================================
        # Define custom dependencies
        # ===========================================================================================

        python = pkgs.python314.withPackages (
          p:
            with p; [
              setuptools
              flask
              flask-mysqldb
              pyyaml
              tabulate
            ]
        );

        # perl = pkgs.perl.withPackages (p:
        #   with p; [
        #     # Those are already installed system-wide
        #     # PLS
        #     # PerlTidy
        #
        #     NetOpenSSH
        #   ]);

        libpcl = stdenv.mkDerivation rec {
          pname = "libpcl1";
          version = "1.12-2";

          src = pkgs.fetchurl {
            url = "http://launchpadlibrarian.net/521269537/${pname}_${version}_amd64.deb";
            hash = "sha256-GL3mjPAccAtRMAJPnDMCHiDf6xNvGi4oUWylOIqBjP0=";
          };

          dontBuild = true;
          nativeBuildInputs = with pkgs; [
            dpkg
            autoPatchelfHook
          ];

          installPhase = ''
            runHook preInstall

            mkdir -p $out/lib
            cp -rv usr/lib/x86_64-linux-gnu/* $out/lib/

            runHook postInstall
          '';
        };

        # Provides clang to compile C++ to Wasm
        wasi-sdk = stdenv.mkDerivation rec {
          pname = "wasi-sdk";
          version = "29";

          src = let
            baseurl = "https://github.com/WebAssembly/wasi-sdk/releases/download";
          in
            builtins.fetchTarball {
              url = "${baseurl}/${pname}-${version}/${pname}-${version}.0-x86_64-linux.tar.gz";
              sha256 = "sha256:16afis71iqfvwiny4dz0lk9f7wbary0wa67ybwyhywr8g57ss6hq";
            };

          nativeBuildInputs = with pkgs; [
            autoPatchelfHook
          ];

          buildInputs = with pkgs; [
            libgcc.lib
          ];

          dontBuild = true;
          installPhase = ''
            runHook preInstall

            mkdir -p $out
            cp -rv ./* $out/

            runHook postInstall
          '';
        };

        # Provides the iwasm interpreter
        iwasm = stdenv.mkDerivation rec {
          pname = "iwasm";
          version = "2.4.4";

          src = let
            baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
          in
            builtins.fetchTarball {
              url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
              sha256 = "sha256:05irihz3yf7hpc0a59qz9i62imhrsni9xy9nxwsn6b8s92c1yzrp";
            };

          nativeBuildInputs = with pkgs; [
            autoPatchelfHook
          ];

          buildInputs = with pkgs; [
            libz
            zstd
            libgcc.lib
          ];

          dontBuild = true;
          installPhase = ''
            runHook preInstall

            mkdir -p $out/bin
            cp -rv ./* $out/bin/

            runHook postInstall
          '';
        };

        # Provides the wamrc compiler
        wamrc = stdenv.mkDerivation rec {
          pname = "wamrc";
          version = "2.4.4";

          src = let
            baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
          in
            builtins.fetchTarball {
              url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
              sha256 = "sha256:0264arh03gc35z0zdvw07qdvqgfvsxr3qgl1aszghwicmdmh4sqj";
            };

          nativeBuildInputs = with pkgs; [
            autoPatchelfHook
          ];

          buildInputs = with pkgs; [
            libz
            zstd
            libgcc.lib
          ];

          dontBuild = true;
          installPhase = ''
            runHook preInstall

            mkdir -p $out/bin
            cp -rv ./* $out/bin/

            runHook postInstall
          '';
        };

        # ===========================================================================================
        # Specify dependencies
        # https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-dependencies-overview
        # Just for a "nix develop" shell, buildInputs can be used for everything.
        # ===========================================================================================

        # Add dependencies to nativeBuildInputs if they are executed during the build:
        # - Those which are needed on $PATH during the build, for example cmake and pkg-config
        # - Setup hooks, for example makeWrapper/autoPatchelfHook
        # - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
        nativeBuildInputs = with pkgs; [
          cmake
          gdb
          xxd
          wabt
          grub2
          xorriso
          mariadb.client
          # dbeaver-bin
          # beekeeper-studio
          # tableplus
          lazysql
          iwasm
          wamrc
          fail-bin
          php
          qemu_kvm

          # perl

          # Don't install to not pollute our PATH. Just export as environment variable.
          # wasi-sdk
          # libiwasm_debug
          # libiwasm_release
        ];

        # Add dependencies to buildInputs if they will end up copied or linked into the final output or otherwise used at runtime:
        # - Libraries used by compilers, for example zlib
        # - Interpreters needed by patchShebangs for scripts which are installed, which can be the case for e.g. perl
        buildInputs = with pkgs; [];

        # ===========================================================================================
        # Define buildable + installable packages
        # ===========================================================================================

        fail-bin = stdenv.mkDerivation {
          pname = "fail";
          version = "1.0.0";
          src = ./.;

          dontStrip = true;

          nativeBuildInputs = with pkgs; [
            autoPatchelfHook
          ];

          buildInputs = with pkgs; [
            # FAIL runtime dependencies
            python # bochs-experiment-runner.py, resultbrowser.py

            # For old VSS FAIL
            # alsa-lib # libasound.so.2
            # boost_pkgs.boost174 # libboost_coroutine.so.1.74.0, libboost_regex.so.1.74.0, libboost_thread.so.1.74.0
            # capstone_4 # libcapstone.so.4
            # libdwarf_pkgs.libdwarf # libdwarf.so.1
            # elfutils # libelf.so.1
            # mariadb # libmariadb.so.3
            # libpcl # libpcl.so.1
            # protobuf_21 # libprotobuf.so.32
            # SDL # libSDL-1.2.so.0
            # libx11 # libX11.so.6
            # libxrandr # libXrandr.so.2
            # libz # libz.so.1

            # For current FAIL
            boost183
            capstone # libcapstone.so.5
            libdwarf_pkgs.libdwarf # libdwarf.so.1
            elfutils # libelf.so.1
            mariadb # libmariadb.so.3
            protobuf_21 # libprotobuf.so.32
            libz # libz.so.1
          ];

          installPhase = ''
            runHook preInstall

            mkdir -p $out
            cp -rv ./fail/bin $out/bin
            cp -rv ./fail/share $out/share

            runHook postInstall
          '';
        };
      in {
        # Provide package for "nix build"
        packages = {
          default = fail-bin;
          fail = fail-bin;
          wasi-sdk = wasi-sdk;
          iwasm = iwasm;
          wamrc = wamrc;
        };
        apps = {
          default = flake-utils.lib.mkApp {drv = fail-bin;};
          fail = fail-bin;
          wasi-sdk = wasi-sdk;
          iwasm = iwasm;
          wamrc = wamrc;
        };

        devShells = {
          # Provide default environment for "nix develop".
          # Other environments can be added below.
          default = pkgs.mkShell {
            inherit nativeBuildInputs buildInputs shellHook;
            name = description;

            # =========================================================================================
            # Define environment variables
            # =========================================================================================

            # Dynamic libraries from buildinputs:
            # LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;

            # Those are read by compile.pl / build.pl
            FAIL_SHARE = "${fail-bin}/share";
            FAIL_PYTHON = "${python}/bin/python";
            WASI_ROOT = wasi-sdk;
            WAMR_ROOT = "/home/christoph/Notes/TU/MastersThesis/FailNix/wamr";
            CROSS_CC = "${i386_pkgs.stdenv.cc}/bin/i386-elf-gcc";
            CROSS_CXX = "${i386_pkgs.stdenv.cc}/bin/i386-elf-g++";
            LINUX_CC = "${pkgs.multiStdenv.cc}/bin/gcc";
            LINUX_CXX = "${pkgs.multiStdenv.cc}/bin/g++";
          };
        };
      }
    );
}
