/*
  NixOS support 2 fontconfig versions, "support" and "latest".
  - "latest" refers to default fontconfig package (pkgs.fontconfig).
  configuration files are linked to /etc/fonts/VERSION/conf.d/
  - "support" refers to supportPkg (pkgs."fontconfig_${supportVersion}").
  configuration files are linked to /etc/fonts/conf.d/
  This module generates a package containing configuration files and link it in /etc/fonts.
  Fontconfig reads files in folder name / file name order, so the number prepended to the configuration file name decide the order of parsing.
  Low number means high priority.
*/
{ config, pkgs, lib, ... }:
with lib;
let
  cfg = config.fonts.fontconfig;
  fcBool = x: "" + (boolToString x) + "";
  # back-supported fontconfig version and package
  # version is used for font cache generation
  supportVersion = "210";
  supportPkg = pkgs."fontconfig_${supportVersion}";
  # latest fontconfig version and package
  # version is used for configuration folder name, /etc/fonts/VERSION/
  # note: format differs from supportVersion and can not be used with makeCacheConf
  latestVersion = pkgs.fontconfig.configVersion;
  latestPkg = pkgs.fontconfig;
  # supported version fonts.conf
  supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
  # configuration file to read fontconfig cache
  # version dependent
  # priority 0
  cacheConfSupport = makeCacheConf { version = supportVersion; };
  cacheConfLatest = makeCacheConf { };
  # generate the font cache setting file for a fontconfig version
  # use latest when no version is passed
  # When cross-compiling, we can’t generate the cache, so we skip the
  #  part. fontconfig still works but is a little slower in
  # looking things up.
  makeCacheConf = { version ? null }:
    let
      fcPackage =
        if version == null
        then "fontconfig"
        else "fontconfig_${version}";
      makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
      cache = makeCache pkgs.${fcPackage};
      cache32 = makeCache pkgs.pkgsi686Linux.${fcPackage};
    in
    pkgs.writeText "fc-00-nixos-cache.conf" ''
      
      
      
        
        ${concatStringsSep "\n" (map (font: "${font}") config.fonts.fonts)}
        ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
        
        ${cache}
        ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
          ${cache32}
        ''}
        ''}
      
    '';
  # rendering settings configuration file
  # priority 10
  renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
    
    
    
      
      
        
          ${fcBool cfg.hinting.enable}
        
        
          ${fcBool cfg.hinting.autohint}
        
        
          hintslight
        
        
          ${fcBool cfg.antialias}
        
        
          ${cfg.subpixel.rgba}
        
        
          lcd${cfg.subpixel.lcdfilter}
        
      
      ${optionalString (cfg.dpi != 0) ''
      
        
          ${toString cfg.dpi}
        
      
      ''}
    
  '';
  # local configuration file
  localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
  # default fonts configuration file
  # priority 52
  defaultFontsConf =
    let
      genDefault = fonts: name:
        optionalString (fonts != [ ]) ''
          
            ${name}
            
            ${concatStringsSep ""
            (map (font: ''
              ${font}
            '') fonts)}
            
          
        '';
    in
    pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
      
      
      
        
        ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
        ${genDefault cfg.defaultFonts.serif     "serif"}
        ${genDefault cfg.defaultFonts.monospace "monospace"}
        ${genDefault cfg.defaultFonts.emoji "emoji"}
      
    '';
  # bitmap font options
  # priority 53
  rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" ''
    
    
    
    ${optionalString (!cfg.allowBitmaps) ''
    
    
      
        
          false
        
      
    
    ''}
    
    
      
        ${fcBool cfg.useEmbeddedBitmaps}
      
    
    
  '';
  # reject Type 1 fonts
  # priority 53
  rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
    
    
    
    
    
      
        
          Type 1
        
      
    
    
  '';
  # fontconfig configuration package
  confPkg = pkgs.runCommand "fontconfig-conf"
    {
      preferLocalBuild = true;
    } ''
    support_folder=$out/etc/fonts/conf.d
    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
    mkdir -p $support_folder
    mkdir -p $latest_folder
    # fonts.conf
    ln -s ${supportFontsConf} $support_folder/../fonts.conf
    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
          $latest_folder/../fonts.conf
    # fontconfig default config files
    ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
          $support_folder/
    ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
          $latest_folder/
    # update latest 51-local.conf path to look at the latest local.conf
    rm    $latest_folder/51-local.conf
    substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
               $latest_folder/51-local.conf \
               --replace local.conf /etc/fonts/${latestVersion}/local.conf
    # 00-nixos-cache.conf
    ln -s ${cacheConfSupport} \
          $support_folder/00-nixos-cache.conf
    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
    # 10-nixos-rendering.conf
    ln -s ${renderConf}       $support_folder/10-nixos-rendering.conf
    ln -s ${renderConf}       $latest_folder/10-nixos-rendering.conf
    # 50-user.conf
    ${optionalString (!cfg.includeUserConf) ''
    rm $support_folder/50-user.conf
    rm $latest_folder/50-user.conf
    ''}
    # local.conf (indirect priority 51)
    ${optionalString (cfg.localConf != "") ''
    ln -s ${localConf}        $support_folder/../local.conf
    ln -s ${localConf}        $latest_folder/../local.conf
    ''}
    # 52-nixos-default-fonts.conf
    ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
    ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
    # 53-no-bitmaps.conf
    ln -s ${rejectBitmaps} $support_folder/53-no-bitmaps.conf
    ln -s ${rejectBitmaps} $latest_folder/53-no-bitmaps.conf
    ${optionalString (!cfg.allowType1) ''
    # 53-nixos-reject-type1.conf
    ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
    ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
    ''}
  '';
  # Package with configuration files
  # this merge all the packages in the fonts.fontconfig.confPackages list
  fontconfigEtc = pkgs.buildEnv {
    name = "fontconfig-etc";
    paths = cfg.confPackages;
    ignoreCollisions = true;
  };
in
{
  imports = [
    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ])
    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ])
    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
    (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
    (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
    (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
  ] ++ lib.forEach [ "enable" "substitutions" "preset" ]
    (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
      The fonts.fontconfig.ultimate module and configuration is obsolete.
      The repository has since been archived and activity has ceased.
      https://github.com/bohoomil/fontconfig-ultimate/issues/171.
      No action should be needed for font configuration, as the fonts.fontconfig
      module is already used by default.
    '');
  options = {
    fonts = {
      fontconfig = {
        enable = mkOption {
          type = types.bool;
          default = true;
          description = ''
            If enabled, a Fontconfig configuration file will be built
            pointing to a set of default fonts.  If you don't care about
            running X11 applications or any other program that uses
            Fontconfig, you can turn this option off and prevent a
            dependency on all those fonts.
          '';
        };
        confPackages = mkOption {
          internal = true;
          type = with types; listOf path;
          default = [ ];
          description = ''
            Fontconfig configuration packages.
          '';
        };
        antialias = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Enable font antialiasing. At high resolution (> 200 DPI),
            antialiasing has no visible effect; users of such displays may want
            to disable this option.
          '';
        };
        dpi = mkOption {
          type = types.int;
          default = 0;
          description = ''
            Force DPI setting. Setting to 0 disables DPI
            forcing; the DPI detected for the display will be used.
          '';
        };
        localConf = mkOption {
          type = types.lines;
          default = "";
          description = ''
            System-wide customization file contents, has higher priority than
            defaultFonts settings.
          '';
        };
        defaultFonts = {
          monospace = mkOption {
            type = types.listOf types.str;
            default = [ "DejaVu Sans Mono" ];
            description = ''
              System-wide default monospace font(s). Multiple fonts may be
              listed in case multiple languages must be supported.
            '';
          };
          sansSerif = mkOption {
            type = types.listOf types.str;
            default = [ "DejaVu Sans" ];
            description = ''
              System-wide default sans serif font(s). Multiple fonts may be
              listed in case multiple languages must be supported.
            '';
          };
          serif = mkOption {
            type = types.listOf types.str;
            default = [ "DejaVu Serif" ];
            description = ''
              System-wide default serif font(s). Multiple fonts may be listed
              in case multiple languages must be supported.
            '';
          };
          emoji = mkOption {
            type = types.listOf types.str;
            default = [ "Noto Color Emoji" ];
            description = ''
              System-wide default emoji font(s). Multiple fonts may be listed
              in case a font does not support all emoji.
              Note that fontconfig matches color emoji fonts preferentially,
              so if you want to use a black and white font while having
              a color font installed (eg. Noto Color Emoji installed alongside
              Noto Emoji), fontconfig will still choose the color font even
              when it is later in the list.
            '';
          };
        };
        hinting = {
          enable = mkOption {
            type = types.bool;
            default = true;
            description = ''
              Enable font hinting. Hinting aligns glyphs to pixel boundaries to
              improve rendering sharpness at low resolution. At high resolution
              (> 200 dpi) hinting will do nothing (at best); users of such
              displays may want to disable this option.
            '';
          };
          autohint = mkOption {
            type = types.bool;
            default = false;
            description = ''
              Enable the autohinter in place of the default interpreter.
              The results are usually lower quality than correctly-hinted
              fonts, but better than unhinted fonts.
            '';
          };
        };
        includeUserConf = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Include the user configuration from
            ~/.config/fontconfig/fonts.conf or
            ~/.config/fontconfig/conf.d.
          '';
        };
        subpixel = {
          rgba = mkOption {
            default = "rgb";
            type = types.enum [ "rgb" "bgr" "vrgb" "vbgr" "none" ];
            description = ''
              Subpixel order. The overwhelming majority of displays are
              rgb in their normal orientation. Select
              vrgb for mounting such a display 90 degrees
              clockwise from its normal orientation or vbgr
              for mounting 90 degrees counter-clockwise. Select
              bgr in the unlikely event of mounting 180
              degrees from the normal orientation. Reverse these directions in
              the improbable event that the display's native subpixel order is
              bgr.
            '';
          };
          lcdfilter = mkOption {
            default = "default";
            type = types.enum [ "none" "default" "light" "legacy" ];
            description = ''
              FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering
              has no visible effect; users of such displays may want to select
              none.
            '';
          };
        };
        cache32Bit = mkOption {
          default = false;
          type = types.bool;
          description = ''
            Generate system fonts cache for 32-bit applications.
          '';
        };
        allowBitmaps = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Allow bitmap fonts. Set to false to ban all
            bitmap fonts.
          '';
        };
        allowType1 = mkOption {
          type = types.bool;
          default = false;
          description = ''
            Allow Type-1 fonts. Default is false because of
            poor rendering.
          '';
        };
        useEmbeddedBitmaps = mkOption {
          type = types.bool;
          default = false;
          description = ''Use embedded bitmaps in fonts like Calibri.'';
        };
      };
    };
  };
  config = mkMerge [
    (mkIf cfg.enable {
      environment.systemPackages = [ pkgs.fontconfig ];
      environment.etc.fonts.source = "${fontconfigEtc}/etc/fonts/";
      security.apparmor.includes."abstractions/fonts" = ''
        # fonts.conf
        r ${supportFontsConf}
        r ${latestPkg.out}/etc/fonts/fonts.conf
        # fontconfig default config files
        r ${supportPkg.out}/etc/fonts/conf.d/*.conf,
        r ${latestPkg.out}/etc/fonts/conf.d/*.conf,
        substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
                   $latest_folder/51-local.conf \
                   --replace local.conf /etc/fonts/${latestVersion}/local.conf
        # 00-nixos-cache.conf
        r ${cacheConfSupport},
        r ${cacheConfLatest},
        # 10-nixos-rendering.conf
        r ${renderConf},
        # local.conf (indirect priority 51)
        ${optionalString (cfg.localConf != "") ''
        r ${localConf},
        ''}
        # 52-nixos-default-fonts.conf
        r ${defaultFontsConf},
        # 53-no-bitmaps.conf
        r ${rejectBitmaps},
        ${optionalString (!cfg.allowType1) ''
        # 53-nixos-reject-type1.conf
        r ${rejectType1},
        ''}
      '';
    })
    (mkIf (cfg.enable && !cfg.penultimate.enable) {
      fonts.fontconfig.confPackages = [ confPkg ];
    })
  ];
}