lib:
let
  # takes in "ff0044" (no hash!) and returns { r = "ff", g = "00", b = "44" }
  slice-hex = hex: with builtins; { r = substring 0 2 hex; g = substring 2 2 hex; b = substring 4 2 hex; };

  # takes in "44" and returns 64
  drune-to-255 = drune: with builtins; (rune-to-num (substring 0 1 drune)) * 16 + (rune-to-num (substring 1 1 drune));
  num-to-drune = num: "${num-to-rune (num / 16)}${num-to-rune (num - ((num / 16) * 16))}";
  
  # takes in "D" and returns 13
  rune-to-num = rune: # inspiration from https://github.com/bertof/nix-rice
    let
      dict = {
        "0" = 0;
        "1" = 1;
        "2" = 2;
        "3" = 3;
        "4" = 4;
        "5" = 5;
        "6" = 6;
        "7" = 7;
        "8" = 8;
        "9" = 9;
        "A" = 10;
        "B" = 11;
        "C" = 12;
        "D" = 13;
        "E" = 14;
        "F" = 15;
      };
    in
    assert(builtins.hasAttr (lib.strings.toUpper rune) dict);
    builtins.getAttr (lib.strings.toUpper rune) dict;

  # takes in 15 and returns "F"
  num-to-rune = num:
    let
      num-string = builtins.toString num;
      dict = {
        "0" = "0";
        "1" = "1";
        "2" = "2";
        "3" = "3";
        "4" = "4";
        "5" = "5";
        "6" = "6";
        "7" = "7";
        "8" = "8";
        "9" = "9";
        "10" = "A";
        "11" = "B";
        "12" = "C";
        "13" = "D";
        "14" = "E"; 
        "15" = "F";
      };
    in
    assert(builtins.hasAttr num-string dict);
    builtins.getAttr num-string dict;

    # Keeps num between 0 and 255
    # Make sure to pass in an int not a float
    cap-255 = num: (if (num>255) then 255 else if (num<0) then 0 else num);
  
    nohash = hex: with builtins; assert((stringLength hex) == 7); substring 1 6 hex;
in
{

  ## USEFUL FUNCTIONS
  # --------------------------------------------------------------------------------
  # takes in a string like "#ff0044" and returns "ff0044" symbol
  inherit nohash;

  # --------------------------------------------------------------------------------
  # This takes in something like "#ff0044" and returns "255,0,64"
  hex-to-rgb-comma-string = hex: 
  with (slice-hex (nohash hex));
  with builtins;
  assert(isString hex);
  "${toString (drune-to-255 r)},${toString (drune-to-255 g)},${toString (drune-to-255 b)}";

  # --------------------------------------------------------------------------------
  # This is useful if you have a float (like a transparency value) and want a drune representation of it
  # So 0.0 -> "00" and 1.0 -> "FF" 
  float-to-drune = f: with builtins; assert(isFloat f); "${num-to-rune (floor((255*f) / 16))}${num-to-rune (floor(255*f) - (floor((255*f) / 16) * 16))}";

  # --------------------------------------------------------------------------------
  # Takes in hex and a float. 0.5 is +50% brightness and (-0.5) is -50% brightness.
  # So "#ff0044": 0.3 -> "#ff0055"
  alter-luminace-hex = hex: amount: 
  let 
    color-num = with (slice-hex (nohash hex)); { r = drune-to-255 r; g = drune-to-255 g; b = drune-to-255 b; };
    alter = num: (num-to-drune (cap-255 (builtins.floor ((125 * amount) + (num * (1+amount))) )));
  in
  with color-num; "#${alter r}${alter g}${alter b}";
}
