{ core, physical, ... }:
let
  inherit(core)     debug list number set string type;
  inherit(physical) formatValue;

  fullTable
  =   set.map (symbol: value: value // { inherit symbol; } )
      {
        # Period 1
        H   = { dalton =  1.0080;  };
        He  = { dalton =  4.0026;  };

        # Period 2
        Li  = { dalton =   6.94;    };
        Be  = { dalton =   9.0122;  };
        B   = { dalton =  10.81;    };
        C   = { dalton =  12.011;   };
        N   = { dalton =  14.007;   };
        O   = { dalton =  15.999;   };
        F   = { dalton =  18.998;   };
        Ne  = { dalton =  20.180;   };

        # Period 3
        Na  = { dalton =  22.990;   };
        Mg  = { dalton =  24.305;   };
        Al  = { dalton =  26.982;   };
        Si  = { dalton =  28.085;   };
        P   = { dalton =  30.974;   };
        S   = { dalton =  32.06;    };
        Cl  = { dalton =  35.45;    };
        Ar  = { dalton =  39.948;   };

        # Period 4
        K   = { dalton =  39.098;   };
        Ca  = { dalton =  40.078;   };
        Sc  = { dalton =  44.956;   };
        Ti  = { dalton =  47.867;   };
        V   = { dalton =  50.942;   };
        Cr  = { dalton =  51.996;   };
        Mn  = { dalton =  54.938;   };
        Fe  = { dalton =  55.845;   };
        Co  = { dalton =  58.933;   };
        Ni  = { dalton =  58.693;   };
        Cu  = { dalton =  63.546;   };
        Zn  = { dalton =  65.380;   };
        Ga  = { dalton =  69.723;   };
        Ge  = { dalton =  72.630;   };
        As  = { dalton =  74.922;   };
        Se  = { dalton =  78.971;   };
        Br  = { dalton =  79.904;   };
        Kr  = { dalton =  83.798;   };

        # Period 5
        Rb  = { dalton =  85.468;   };
        Sr  = { dalton =  87.620;   };
        Y   = { dalton =  88.906;   };
        Zr  = { dalton =  91.224;   };
        Nb  = { dalton =  92.906;   };
        Mo  = { dalton =  95.950;   };
        Tc  = { dalton =  97.4;     };
        Ru  = { dalton = 101.07;    };
        Rh  = { dalton = 102.91;    };
        Pd  = { dalton = 106.42;    };
        Ag  = { dalton = 107.87;    };
        Cd  = { dalton = 112.41;    };
        In  = { dalton = 114.82;    };
        Sn  = { dalton = 118.71;    };
        Sb  = { dalton = 121.76;    };
        Te  = { dalton = 127.60;    };
        I   = { dalton = 126.90;    };
        Xe  = { dalton = 131.29;    };

        # Period 6
        Cs  = { dalton = 132.91;    };
        Ba  = { dalton = 137.33;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        #  = { dalton = ;    };
        Pb  = { dalton = 207.20;    };
        Bi  = { dalton = 208.98;    };
        Po  = { dalton = 209.98;    };
        At  = { dalton = 210;       };
        Rn  = { dalton = 222;       };

        # Period 7
      };
  tableOfMasses                         =   set.mapValues ( value: value.dalton ) fullTable;

  normaliseMolecularFormula
  =   let
        addElements
        =   { ... } @ elements:
            previous:
            count:
              type.matchPrimitiveOrPanic previous
              {
                set
                =   set.fold
                    (
                      { ... } @ elements:
                      name:
                      previous:
                        elements
                        //  {
                              ${name}   =   previous * count + ( elements.${name} or 0 );
                            }
                    )
                    elements
                    previous;
                string
                =   elements
                //  {
                      ${previous}       =   ( elements.${previous} or 0 ) + count;
                    };
                null                    =   elements;
              };
        getSymbol                       =   foo: foo.symbol or foo;
        parse
        =   { elements, previous } @ this:
            token:
              type.matchPrimitiveOrPanic token
              {
                int
                =   if previous != null
                    then
                    {
                      elements          =   addElements elements previous token;
                      previous          =   null;
                    }
                    else
                      debug.panic [ "normaliseMolecularFormula" "parse" ] "Unexpected Number!";
                list
                =   {
                      elements          =   addElements elements previous 1;
                      previous          =   normalise token;
                    };
                set
                =   {
                      elements          =   addElements elements previous 1;
                      previous          =   getSymbol token;
                    };
                string
                =   {
                      elements          =   addElements elements previous 1;
                      previous          =   token;
                    };
              };
        defaultState
        =   {
              elements                  =   { };
              previous                  =   null;
            };
        finalise                        =   { elements, previous }: addElements elements previous 1;
        normalise
        =   this:
              finalise ( list.fold parse defaultState this );
      in
        this: finalise (parse defaultState this);

  calculateMassOfFormula
  =   formula:
        debug.debug "calculateMassOfFormula"
        {
          text                          =   "Molecular Formula";
          data                          =   formula;
        }
        (
          type.matchPrimitiveOrPanic formula
          {
            null                        =   null;
            set
            =   set.fold
                (
                  state:
                  symbol:
                  count:
                    ( state + count * ( tableOfMasses.${symbol} or (debug.panic "calculateMassOfFormula" "Unknown symbol: ${symbol}")) )
                )
                0
                formula;
          }
        );

  formatMolecularFormula
  =   { ... } @ formula:
        list.fold
        (
          result:
          symbol:
            let
              count                     =   formula.${symbol} or 0;
            in
              if count == 0
              then
                result
              else if count == 1
              then
                "${result}${symbol}"
              else
                "${result}${symbol}${string count}"
        )
        ""
        (
          if formula.C or 0 == 0
          then
            set.names fullTable
          else
            [
              "C" "H"
              "Ag" "Al" "Ar" "As" "At" "B" "Ba" "Be" "Bi" "Br" "Ca" "Cd" "Cl" "Co"
              "Cr" "Cs" "Cu" "F" "Fe" "Ga" "Ge" "He" "I" "In" "K" "Kr" "Li" "Mg"
              "Mn" "Mo" "N" "Na" "Nb" "Ne" "Ni" "O" "P" "Pb" "Pd" "Po" "Rb" "Rh"
              "Rn" "Ru" "S" "Sb" "Sc" "Se" "Si" "Sn" "Sr" "Tc" "Te" "Ti" "V" "Xe"
              "Y" "Zn" "Zr"
            ]
        );

  mapAnalysis
  =   { formula, ... } @ substance:
      { ... } @ gotElements:
        if gotElements != { }
        then
          let
            formula'                    =   normaliseMolecularFormula formula;
            format
            =   elements:
                  string.concatWith ", "
                  (
                    set.mapToList
                    (
                      symbol:
                      value:
                        "\\ch{${symbol}}:~${formatValue { inherit value; precision = 2; } "percent"}"
                    )
                    elements
                  );
            calculated
            =   let
                  mapped                =   set.map (symbol: value: tableOfMasses.${symbol} * value) formula';
                  sum                   =   number.sum (set.values mapped);
                  filtered              =   set.map (symbol: _: mapped.${symbol} or 0.0 ) gotElements;
                  calcElements          =   set.mapValues (value: value / sum * 100.0) filtered;
                in
                  format calcElements;
            found                       =   format gotElements;
          in
            [ "Elementar\\-analyse berechnet für \\ch{${formatMolecularFormula formula'}}: ${calculated}; gefunden: ${found}." ]
        else
          [ ];
in
{
  inherit normaliseMolecularFormula formatMolecularFormula calculateMassOfFormula;
  inherit mapAnalysis;
  inherit fullTable tableOfMasses;
}
