{ chemistry, core, document, physical, ... }:
let
  inherit(core)       debug error indentation list number set string type;
  inherit(chemistry)  compound elements ir ms nmr values;
  inherit(elements)   calculateMassOfFormula normaliseMolecularFormula tableOfMasses;
  inherit(document)   Heading Heading' LaTeX Paragraph Paragraph';
  inherit(tableOfMasses) Cu I;

  default                               =   a: b: if a != null then a else b;

  # null | [ Any ] | T
  # -> [ ] | [ Any ] | [ T ]
  toList
  =   value:
        type.matchPrimitiveOrDefault value
        {
          null                          =   [ ];
          list                          =   value;
        }
        [ value ];

  # string | [ ? ] -> [ Document::Chunk.Paragraph ]
  toParagraphs
  =   listOrString:
        type.matchPrimitiveOrPanic listOrString
        {
          string                        =   [ ( Paragraph listOrString ) ];
          list                          =   list.map (paragraph: Paragraph paragraph) listOrString;
        };

  # bool -> Synthesis -> [ Document::Chunk.Paragraph ]
  mapProcedure
  =   _newline:
      { ... } @ synthesis:
        let
          procedure                     =   string.trim' synthesis.procedure;
          result
          =   type.matchPrimitiveOrPanic synthesis.procedure
              {
                lambda                  =   list.map (paragraph: Paragraph' paragraph { endParagraph = "\\par"; }) ( synthesis.procedure synthesis );
                string                  =   [ ( Paragraph procedure ) ];
                list                    =   list.map (paragraph: Paragraph' paragraph { endParagraph = "\\par"; }) ( synthesis.procedure );
              };
        in
          result;

  # Synthesis -> Product -> [ Document::Chunk ]
  formatAnalysis
  =   { ... } @ resources:
      { ... } @ synthesis:
      { analysisOnly ? false, hack ? false, ... } @ product:
        let
          substance                     =   product.substance or synthesis.substance or { };
          yield                         =   product.yield or {};
          yield'
          =   if isLambda yield
              then
                yield product
              else
                yield;
          mass                          =   yield'.mass;
          amount                        =   yield'.amount or ( mass / ( substance.dalton or 1 ) );
          amount'                       =   normaliseValue ( amount ) null;

          equivalent                    =   yield'.equivalent or 1;
          relativeEquivalents
          =   if equivalent != 1
              then
                " (${physical.formatValue equivalent "equivalent"})"
              else
                "";
          # float
          relative
          =   let
                relative                =   ( yield'.relative or ( amount / ( reactant'.amount or amount ) * 100 ) ) * 1.0 * equivalent;
              in
                if relative > 100
                then
                  debug.warn "formatAnalysis"
                  {
                    text                =   "Yield is over 100 %: ${string relative}!";
                    data                =   yield';
                  }
                  relative
                else
                  relative;

          # string | { title: string }
          reactant                      =   yield'.reactant or "???";

          # string
          title
          =   type.matchPrimitiveOrPanic reactant
              {
                set
                =   if reactant.title != null
                    then
                      reactant.title
                    else
                      reactant.substance.ID;
                string                  =   reactant;
              };

          # set
          reactant'
          =   if set.isInstanceOf reactant
              then
                reactant
              else
                { };

          # string
          novel                         =   product.novel or substance.novel or false;
          novelText                     =   "Diese Verbindung ist bisher literatur\\-unbekannt.";

          # float
          purity                        =   ( yield'.purity or 1 ) * 1.0;

          # string
          purity'
          =   if purity != 1.0
              then
                "etwa ${physical.formatValue { value = 100*purity; precision = 1; } "percent"}-iger Reinheit und "
              else
                "";

          # string
          mass'
          =   if purity != 1.0
              then
                "rein: ${physical.formatValue { value = mass * purity; precision = 2; } "gram"}, "
              else
                "";

          warn
          =   name:
                debug.warn "formatAnalysis"
                {
                  when                  =   product.novel or substance.novel or false;
                  text                  =   "${name}-data missing for novel compound ${substance.name}";
                };

          otherData#: T = int|float|{ from: int|float, till: int|float }|null @ {
          #   melting:        T,
          #   boiling:        T,
          #   sublimation:    T,
          #   decomposition:  T,
          #   density:        int|float|null,
          # }?
          =   product.physical  or ( warn "Physical" null );
          eaData                        =   substance.elements  or  product.elements  or  ( warn "EA"   { } );
          irData                        =   substance.ir        or  product.ir        or  ( warn "IR"   [ ] );
          msData                        =   substance.ms        or  product.ms        or  ( warn "MS"   { } );
          nmrData                       =   substance.nmr       or  product.nmr       or  ( warn "NMR"  { } );
          reports
          =   [] # Elementaranalyse
          ++  ( values.report   resources           otherData )
        # ++  ( elements.report resources substance Elements  )
          ++  ( nmr.report      resources           nmrData   )
          ++  ( ir.report       resources           irData    )
          ++  ( ms.report       resources           msData    );
          failure                       =   synthesis.failure or false || product.failure or false;
        in
          [
            (
              Paragraph'
                (
                  if analysisOnly
                  then
                    if novel
                    then
                      [ novelText ]
                    else
                      []
                  else if failure
                  then
                    if yield' == {}
                    then
                      [ "Es konnte kein Produkt erhalten werden." ]
                    else
                      [
                        "Es konnte kein Produkt erhalten werden und"
                        "  ${physical.formatValue { value = mass; precision = 2; } "gram"}"
                        "    (${physical.formatValue { value = amount'.value; precision = 2; } "${amount'.prefix}mol"})"
                        "  des eingesetzten ${title} wurden zurückgewonnen."
                      ]
                  else if yield' == {}
                  then
                    [ "Erhalten wurde eine unbestimmte Menge ${product.description or "Produkt"}." ]
                  else
                    [
                      "Erhalten wurden ${physical.formatValue { value = mass; precision = 2; } "gram"}"
                      "  (${mass'}${physical.formatValue { value = amount'.value*purity; precision = 2; } "${amount'.prefix}mol"})"
                      "  ${product.description or "Produkt"} mit \\mbox{${purity'}${physical.formatValue { value = relative*purity; precision = 1; } "percent"}-iger} Ausbeute"
                      "    bezogen auf eingesetztes ${title}${relativeEquivalents}.${if novel then " ${novelText}" else ""}"
                    ]
                )
                {
                  endParagraph
                  =   if !hack
                      then
                        "\\par"
                      else
                        "\\par";
                  /*endParagraph
                  =   if  !failure
                      &&  any (x: x != [] && x != {} && x != null) [ eaData irData msData nmrData otherData ]
                      then
                        ""
                      else
                        "\\newline";*/
                }
            )
          ]
          ++  (
                if failure
                then
                  []
                else
                  reports
              );

  # Synthesis -> Product -> Document::Chunk.Paragraph | null
  formatProductChemicals
  =   { ... } @ resources:
      { ... } @ syn:
      { ... } @ product:
        if product ? "chemicals"
        then
          let
            chemicals
            =   type.matchPrimitiveOrPanic product.chemicals
                {
                  null                  =   [ ];
                  set                   =   set.values product.chemicals;
                  list                  =   product.chemicals;
                };
            len                         =   list.length chemicals;
            endParagraph                =   "\\par";
          in
            if len > 1
            then
              Paragraph'
                (
                  [ "Eingesetzt wurden:" ]
                  ++  ( generate (x: "  ${formatChemical (list.get chemicals x)},") ( len - 2 ) )
                  ++  [
                        "  ${formatChemical (list.get chemicals ( len - 2 ))} und"
                        "  ${formatChemical (list.get chemicals ( len - 1 ))}."
                      ]
                )
                { inherit endParagraph; }
            else if len == 1
            then
              Paragraph'
                [ "Eingesetzt wurden ${formatChemical ( list.head chemicals)}." ]
                { inherit endParagraph; }
            else
              null
        else
          null;

  # Synthesis -> [ Document::Chunk.Heading ]
  mapProducts
  =   { ... } @ resources:
      { ... } @ synthesis:
        map
        (
          {
            chemicals ? {},
            clearPage ? false,
            note      ? null,
            substance ? null,
            title     ? null,
            name      ? null,
            noHeading ? false,
            noMolecule ? false,
            postAnalysis  ? [],
            lines ? null,
            ...
          } @ product:
            let
              product'
              =   product
              //  {
                    inherit clearPage name substance title;
                    chemicals           =   fixChemicals resources chemicals;
                  };
              body
              =   ( toList ( formatProductChemicals resources synthesis product' ) )
              ++  (
                    if note != null
                    then
                      if lambda.isInstanceOf note
                      then
                        [ (note product') ]
                      else
                        [ note ]
                    else
                      []
                  )
              ++  ( formatAnalysis resources synthesis product' )
              ++  postAnalysis;

              lines'
              =   if lines != null
                  then
                    "[${string lines}]"
                  else
                    "";

              body'
              =   LaTeX
                  (
                    if substance != null
                    && !noMolecule
                    then
                      [
                        "\\renewcommand{\\NumAtom}[2]{}%"
                        #"\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%"
                        "\\Wrapchem${lines'}{${substance { mass = true; structure = true; }}}"
                        "{" indentation.more
                      ]
                      ++  body
                      ++  [ indentation.less "}"  ]
                    else
                      body
                  );

              body''
              =   if noHeading
                  then
                    body'
                  else if substance != null
                  then
                    Heading'
                    {
                      bookmark          =   "${substance.Name}";
                      caption           =   "${substance.NameID}";
                    }
                    [ body' ]
                    {
                      inherit clearPage;
                      label             =   "substance:${substance.name or name}";
                      clearPageOnLastQuarter = true;
                    }
                  else if title != null
                  then
                    Heading'
                    {
                      bookmark          =   "${compound title}";
                      caption           =   "${compound title}";
                    }
                    [ body' ]
                    {
                      inherit clearPage;
                      label             =   "substance:${name}";
                      clearPageOnLastQuarter = true;
                    }
                  else
                    body';
            in
              body''
        );

  getFullTitle
  =   { acronyms, ... }:
      { casus, degased, dry, molar, solvent, title }:
        if      solvent != null
        then
          let
            # ToDo: Export to fluent-module
            specialSolvents
            =   {
                  "H2O"                 =   { deu = "wässrige";                             eng = "aqueous";                           };
                  "Et2O"                =   { deu = "${acronyms.Et2O.as "etherische"}";     eng = "${acronyms.Et2O.as "etheral"}";     };
                  "EtOH"                =   { deu = "${acronyms.EtOH.as "ethanolische"}";   eng = "${acronyms.EtOH.as "ethanolic"}";   };
                  "MeOH"                =   { deu = "${acronyms.MeOH.as "methanolische"}";  eng = "${acronyms.MeOH.as "methanolic"}";  };
                  "NH3"                 =   { deu = "ammoniakalische";                      eng = null;                                };
                  "HCl"                 =   { deu = "salz\\-saure";                         eng = null;                                };
                  "HNO3"                =   { deu = "salpeter\\-saure";                     eng = null;                                };
                  "H2SO4"               =   { deu = "schwefel\\-saure";                     eng = null;                                };
                };
            special                     =   specialSolvents.${solvent}.deu or null;
            acronym
            =   {
                  THF                   =   acronyms."tetrahydrofuran";
                  tetrahydrofuran       =   acronyms."tetrahydrofuran";
                }.${solvent} or null;
          in
            if isString solvent
            then
              if      special != null then  "${special}${default casus "r"} ${molar}${title}\\-lösung"
              else if acronym != null then  "${molar}${title}-Lösung in ${acronym.long}"
              else                          "${molar}${title}-Lösung in ${compound solvent}"
            else                            "${molar}${title}-Lösung in ${getTitle solvent}"
        else if dry && degased        then  "trockene${default casus "m"} und entgaste${default casus "m"} ${title}"
        else if dry                   then  "trockene${default casus "m"} ${title}"
        else if degased               then  "entgaste${default casus "m"} ${title}"
        else                                "${molar}${title}";

  fixChemical
  =   { acronyms, ... } @ resources:
      { ... } @ chemicals:
      name:
      { acronym, casus, concentration, mixture, purity, relative, substance, title, ... } @ chemical:
        let
          molar
          =   if      purity != 1.0
              then
                "${physical.formatValue ( purity * 100 ) "percent"}-ige${default casus "r"}~"
              else
                type.matchPrimitiveOrPanic concentration
                {
                  null                  =   "";
                  int                   =   "${formatConcentration concentration}~";
                  float                 =   "${formatConcentration concentration}~";
                  list                  =   "${concatWith ":" ( list.map formatConcentration concentration)}~";
                  string
                  =   {
                        "conc"          =   "${acronyms.concentrated.short} ";
                        "sat"           =   "${acronyms.saturated.short} ";
                        "dil"           =   "${acronyms.diluted.short} ";
                      }.${concentration}
                  or  (
                        debug.panic "fixChemical" "Invalid Concentration »${concentration}«"
                      );
                };

          chemical'
          =   chemical
          //  {
                title
                =   getFullTitle  resources
                    {
                      inherit molar;
                      title             =   getTitle { inherit acronym mixture substance title; };
                      inherit(chemical) casus dry degased solvent;
                    };
              };

          relative'
          =   chemicals.${relative}
          or  (
                debug.panic
                  "fixChemical"
                  "While calculating equivalent for ${name}: There is no chemical »${relative}«!"
              );
        in
          if relative != null
          then
            chemical'
            //  {
                  equivalent
                  =   debug.warn "fixChemical"
                      {
                        text            =   "relative";
                        data
                        =   {
                              chemical  =   { inherit(chemical) amount; };
                              relative  =   { inherit(relative') amount equivalent; };
                              ratio     =   chemical.amount / relative'.amount;
                            };
                      }
                      ( chemical.amount / relative'.amount * relative'.equivalent);
                }
          else
            chemical';

  # ( string -> Chemical ) -> ( string -> Chemical )
  fixChemicals
  =   { ... } @ resources:
      { ... } @ chemicals:
        set.map
        (
          name:
          { ... } @ chemical:
            type.matchPrimitiveOrPanic chemical
            {
              list                      =   list.map (fixChemical resources chemicals name) chemical;
              set                       =   fixChemical resources chemicals name chemical;
            }
        )
        chemicals;

  # F -> G -> [ T ] -> [ ( F T ) ]
  filterMap                             =   m: f: self: filter f ( list.map m self );

  # [ Synthesis ] -> [ Document::Chunk.Heading ] | !
  mapSyntheses
  =   { ... } @ resources:
      syntheses:
        filterMap
        (
          { ... } @ syn:
            let
              syn'
              =   syn
              //  {
                    chemicals           =   fixChemicals resources syn.chemicals;
                  };
              mapSubstances
              =   string.concatMappedWith
                  (
                    { ... } @ substance:
                      "${substance { mass = true; structure = true; }}"
                  )
                  "\\arrow{0}[-90,0.3]";
              product
              =   if syn'.product or null != null
                  then
                    type.type.matchPrimitiveOrDefault syn'.product
                    {
                      lambda            =   syn'.product syn';
                      list
                      =   list.map
                          (
                            product:
                              type.callLambda product syn'
                          )
                          syn'.product;
                    }
                    syn'.product
                  else
                    [];
              citeLiterature
              =   { literature ? null, ... }:
                    type.matchPrimitiveOrPanic literature
                    {
                      null              =   "";
                      list              =   "\\cite{${string.concatMappedWith ({ name, ... }: name) "," literature}}";
                      set               =   "\\cite{${literature.name}}";
                    };
              formattedAnalysis
              =   type.matchPrimitiveOrPanic product
                  {
                    null                =   [ ];
                    list
                    =   if product != []
                        then
                          mapProducts resources syn' product
                        else
                          [
                            (
                              Paragraph'
                              [ "Es konnte kein Produkt erhalten werden." ]
                              { endParagraph = ""; }
                            )
                          ];
                    set
                    =   formatAnalysis resources syn'
                        (
                          product // { chemicals = fixChemicals resources product.chemicals; }
                        );
                  };

              lines'
              =   if syn'.lines or null != null
                  then
                    "[${string syn'.lines}]"
                  else
                    "";
            in
              if syn'.ignore or false
              then
                null
              else
                (
                  if syn'.title or null != null
                  then
                    Heading'
                    (
                      if type.isSet syn'.title
                      then
                        syn'.title
                      else
                      {
                        bookmark          =   syn'.title;
                        caption           =   "${syn'.title}${citeLiterature syn'}";
                      }
                    )
                    (
                      if syn'.substances or null != null
                      then
                        LaTeX
                        (
                          [
                            "\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%"
                            "\\Wrapchem${lines'}{${mapSubstances syn'.substances}}"
                            "{" indentation.more
                          ]
                          ++  ( mapProcedure true syn' )
                          ++  formattedAnalysis
                          ++  [ indentation.less "}"  ]
                        )
                      else
                        ( mapProcedure false syn' )
                        ++  formattedAnalysis
                    )
                    {
                      clearPageOnLastQuarter = true;
                    }
                  else if syn'.substance or null != null
                  then
                    Heading'
                    {
                      bookmark          =   "${syn'.substance.Name}";
                      caption           =   "${syn'.substance.NameID}${citeLiterature syn'}";
                      LaTeX             =   true;
                    }
                    (
                      LaTeX
                      (
                        [
                          "\\renewcommand{\\NumAtom}[2]{-[#1,,,draw=none]{\\scriptstyle#2}}%"
                          "\\Wrapchem${lines'}{${syn'.substance { mass = true; structure = true; }}}"
                          "{" indentation.more
                        ]
                        ++  ( mapProcedure true syn' )
                        ++  formattedAnalysis
                        ++  [ indentation.less "}"  ]
                      )
                    )
                    {
                      label             =   "substance:${syn'.substance.name}";
                      clearPage         =   syn'.clearPage or false;
                      clearPageOnLastQuarter = true;
                    }
                  else
                    debug.panic "mapSyntheses" "Either substance or title must be set!"
                )
        )
        (x: x != null)
        syntheses;

  # Chemical -> string
  formatDetails
  =   { ... } @ chemical:
        if list.isInstanceOf chemical  then  concatWith "; " ( list.map formatDetails' chemical )
        else                      formatDetails' chemical;

  # Chemical -> string
  formatDetails'
  =   { ... } @ chemical:
        let
          details
          =   (
                if chemical.substance != null
                then
                  [ chemical.substance.ID ]
                else
                  [ ]
              )
          ++  (
                if chemical.volume != null
                then
                  let
                    precision
                    =   if chemical.kind != "solvent" then  null
                        else if chemical.volume < 20  then  1
                        else                                0;

                    volume              =   normaliseValue ( chemical.volume / 1000.0 )                   null;
                    volume'             =   physical.formatValue { value = volume.value; inherit precision; }     "${volume.prefix}litre";

                    fullVolume          =   normaliseValue ( chemical.times * chemical.volume / 1000.0 )  null;
                    fullVolume'         =   physical.formatValue { value = fullVolume.value; inherit precision; } "${fullVolume.prefix}litre";
                  in
                    if chemical.times != null then  [ "${string chemical.times}×${volume'}=${fullVolume'}" ]
                    else
                      [ volume' ]
                else
                  [ ]
              )
          ++  (
                if chemical.mass != null
                then
                  let
                    mass                =   normaliseValue ( chemical.mass ) null;
                    volume              =   normaliseValue ( chemical.solvent.volume / 1000.0 ) (-3);
                    precision
                    =   if chemical.solvent.volume < 20 then  1
                        else                                  0;
                    solvent
                    =   if chemical.solvent != null
                        && chemical.solvent ? volume
                        then
                          " in ${physical.formatValue { value = volume.value; inherit precision; } "${volume.prefix}litre"}"
                        else
                          "";
                  in
                    [ "${( physical.formatValue { value = mass.value; precision = 2; } "${mass.prefix}gram" )}" ]
                else
                  [ ]
              )
          ++  (
                if chemical.amount != null
                then let
                  amount                 =   normaliseValue ( chemical.amount ) ( chemical.factor or null );
                in
                  [ ( physical.formatValue { value = amount.value; precision = 2; } "${amount.prefix}mol" ) ]
                else
                  [ ]
              )
          ++  (
                if chemical.equivalent != null
                then
                  debug.info "formatDetails'" { text = "Equivalent"; data = chemical.equivalent; }
                  (
                    if chemical.kind == "catalyst"  then  [ ( physical.formatValue { value = chemical.equivalent; precision = null; } "equivalent" ) ]
                    else                                  [ ( physical.formatValue { value = chemical.equivalent; precision = 1; } "equivalent" ) ]
                  )
                else
                  [ ]
              )
          ++  (
                if chemical.details != null then  [ chemical.details ]
                else                              [ ]
              );
        in
          string.concatWith ", " details;

  getUnitPrefix#: integer -> string | !
  =   factor:
        if      factor == -24 then  "yokto"
        else if factor == -21 then  "zepto"
        else if factor == -18 then  "atto"
        else if factor == -15 then  "femto"
        else if factor == -12 then  "pico"
        else if factor == -9  then  "nano"
        else if factor == -6  then  "micro"
        else if factor == -3  then  "milli"
        else if factor == 0   then  ""
        else if factor == 3   then  "kilo"
        else if factor == 6   then  "mega"
        else if factor == 9   then  "giga"
        else if factor == 12  then  "tera"
        else if factor == 15  then  "peta"
        else if factor == 18  then  "exa"
        else if factor == 21  then  "zetta"
        else if factor == 24  then  "yotta"
        else error.throw "getUnitPrefix: Invalid Factor: ${string factor}";

  # Number -> null | Number -> { value: Number; prefix = string; }
  normaliseValue
  =   input:
      factor:
        let
          absInput                      =   number.abs input;
          factor'
          =   if factor != null                                     then  factor
              else if absInput >= 1000000000000000.0                then   15
              else if absInput >=    1000000000000.0                then   12
              else if absInput >=       1000000000.0                then    9
              else if absInput >=          1000000.0                then    6
              else if absInput >=             1000.0                then    3
              else if absInput >=                1.0                then    0
              else if absInput >=                0.001              then   -3
              else if absInput >=                0.000001           then   -6
              else if absInput >=                0.000000001        then   -9
              else if absInput >=                0.000000000001     then  -12
              else if absInput >=                0.000000000000001  then  -15
              else                                                          0;
          prefix                        =   getUnitPrefix factor';
          value                         =   input * ( number.pow 10 ( 0 - factor' ) );
        in
          { inherit value prefix; };

  # { acronym, mixture, substance, title, ... } -> string
  getTitle
  =   { acronym ? null, mixture ? null, substance ? null, title ? null, ... }:
        if      title     !=  null  then  compound title
        else if acronym   !=  null  then  acronym.long
        else if substance !=  null  then  substance.Name
        else if mixture   !=  null  then  string.concatMappedWith getTitle "-" mixture
        else                              "???";

  # Chemical -> string
  formatName
  =   { ... } @ chemical:
        if list.isInstanceOf chemical
        then
          "${string.concatMappedWith ({ title, ... }: title) "-" chemical}-Mischung"
        else
          if chemical.the or null != null
          then
            "${chemical.the} ${chemical.title}"
          else
            chemical.title;

  # Chemical -> string
  formatChemical
  =   { ... } @ chemical:
        let
          name                          =   formatName chemical;
          details                       =   formatDetails chemical;
        in
          if details != ""
          then
            "${name} (${details})"
          else
            name;

  # Number -> string | !
  formatConcentration
  =   concentration:
        let
          concentration'                =   normaliseValue concentration null;
        in
          if concentration != null
          then
            "${( physical.formatValue { value = concentration'.value; precision = 1; } "${concentration'.prefix}molar" )}"
          else
            debug.panic "formatConcentration" "Concentration is null!";

  Chemical
  =   kind:
      {
        acronym       ? null,
        amount        ? null,
        concentration ? null,
        dalton        ? null,
        degased       ? false,
        density       ? null,
        details       ? null,
        dry           ? false,
        equivalent    ? null,
        factor        ? null,
        formula       ? null,
        mass          ? null,
        mixture       ? null,
        purity        ? 1.0,
        relative      ? null,
        simple        ? null,
        solvent       ? null,
        substance     ? null,
        the           ? null,
        times         ? null,
        title         ? null,
        volume        ? null
      } @ chemical:
      casus:
        let
          formula'                      =   normaliseMolecularFormula ( default formula ( substance.formula or [ ] ) );
          dalton'                       =   calculateMassOfFormula formula';

          chemical'
          =   {
                __type__                =   "Chemical";
                concentration
                =   if      concentration != null then  concentration
                    else if mixture       != null then  list.map ({ concentration ? null, ... }: concentration) mixture
                    else                                null;

                dalton                  =   default dalton  ( substance.dalton  or dalton'  );
                density                 =   default density ( substance.density or null     );
                formula                 =   formula';
                inherit acronym amount casus degased details dry equivalent factor kind mass mixture purity relative simple
                        solvent substance the times title volume;
              };

          chemical''
          =   chemical'
          //  {
                amount
                =   debug.info "chemical"
                    {
                      data              =   chemical;
                    }
                    (
                      if      chemical'.amount != null          then  chemical'.amount
                      else if chemical'.volume != null
                      then
                        if      chemical'.mass != null          then  debug.panic "Chemical" "Volume and mass are mutualy exclusive!"
                        else if chemical'.concentration != null
                        then
                          if isFloat chemical'.concentration    then  chemical'.volume * chemical'.concentration / 1000
                          else                                        null
                        else if chemical'.density != null
                        &&      chemical'.dalton  != null       then  chemical'.purity * chemical'.volume * chemical'.density / chemical'.dalton
                        else                                          null
                      else if chemical'.mass != null
                      then
                        if chemical'.dalton != null             then  chemical'.mass / chemical'.dalton
                        else                                          null
                      else                                            null
                    );
              };
        in
          if  kind == "reagent"
          ||  kind == "catalyst"
          then
            if chemical''.amount == null
            then
              debug.panic "Chemical"
              {
                text                    =   "Catalysts and Reagents need to have amount of substance (`amount`) given. Perhaps you want Material.";
                data                    =   chemical'';
              }
            else if chemical''.equivalent == null
            &&      chemical''.relative == null
            then
              debug.panic "Chemical" "Catalysts and Reagents need to have `equivalent` given. Perhaps you want Material."
            else
              chemical''
          else
            chemical'';

  Catalyst'                             =   { ... } @ attrs: Chemical   "catalyst"  attrs;
  Material'                             =   { ... } @ attrs: Chemical   "material"  attrs;
  Product'                              =   { ... } @ attrs: Chemical   "product"   attrs;
  Reagent'                              =   { ... } @ attrs: Chemical   "reagent"   attrs;
  Solvent'                              =   { ... } @ attrs: Chemical   "solvent"   attrs;
  Catalyst                              =   { ... } @ attrs: Catalyst'              attrs null;
  Material                              =   { ... } @ attrs: Material'              attrs null;
  Product                               =   { ... } @ attrs: Product'               attrs null;
  Reagent                               =   { ... } @ attrs: Reagent'               attrs null;
  Solvent                               =   { ... } @ attrs: Solvent'               attrs null;
in
{
  inherit mapSyntheses;
  inherit formatChemical formatDetails formatName;
  inherit Catalyst  Material  Product   Reagent   Solvent;
  inherit Catalyst' Material' Product'  Reagent'  Solvent';
}
