{-|
Module      : Isotope.Base
Description : Contains most of the data type declarations used in the Isotope
              library.
Copyright   : Michael Thomas
License     : GPL-3
Maintainer  : Michael Thomas <Michaelt293@gmail.com>
Stability   : Experimental

This module defines the majority of the types used in the Isotope library. A
large number of type synonyms are provided to improve readability. Of particular
importance are the 'Isotope', 'Element', 'ElementSymbol', `EmpiricalFormula`,
`MolecularFormula` and 'CondensedFormula' types. 'ElementSymbol' is an
enumeration type of all the element symbols used in the Isotopes library.
-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_HADDOCK hide #-}
module Isotope.Base (
    -- Type synonyms for masses
      IntegerMass
    , MonoisotopicMass
    , NominalMass
    , AverageMass
    , IsotopicMass
    -- Other type synonyms
    , ElementName
    , IsotopicAbundance
    , AtomicNumber
    , ProtonNumber
    , NeutronNumber
    , Nucleons
    , MassNumber
    -- 'Isotope' and 'Element' data types
    , Isotope(..)
    , Element(..)
    -- Element symbols
    , ElementSymbol(..)
    , elementSymbolList
    -- Functions taking an 'Element' as input
    , elementMostAbundantIsotope
    , elementIsotopicMasses
    , elementIntegerMasses
    , elementIsotopicAbundances
    , elementMonoisotopicMass
    , elementNominalMass
    , elementAverageMass
    , massNumber
    -- 'elements' - a map containing isotopic data for each element.
    , elements
    -- Functions taking an 'elementSymbol' as input
    , lookupElement
    , findElement
    , elementName
    , atomicNumber
    , isotopes
    , mostAbundantIsotope
    , selectIsotope
    , isotopicMasses
    , integerMasses
    , isotopicAbundances
    -- 'ChemicalMass' type class
    , ChemicalMass(..)
    -- Elemental composition
    , ElementalComposition(..)
    -- Molecular formulae
    , MolecularFormula(..)
    , (|+|)
    , (|-|)
    , (|*|)
    , mkMolecularFormula
    , Formula(..)
    , ToMolecularFormula(..)
    -- Condensed formulae
    , CondensedFormula(..)
    -- Empirical formula
    , EmpiricalFormula(..)
    , ToEmpiricalFormula(..)
    , mkEmpiricalFormula
    ) where

import Prelude hiding      (lookup,filter)
import Data.Map            ( Map
                           , fromList
                           , unionWith
                           , filter
                           , mapWithKey
                           , lookup
                           , (!)
                           , toList
                           )
import Data.Foldable hiding (toList)
import Data.Ord
import Data.List           (elemIndex, sortBy)
import Data.Maybe          (fromJust)

--------------------------------------------------------------------------------
-- Type synonyms for masses

-- | Integer mass for an isotope.
type IntegerMass       = MassNumber

-- | The exact mass of the most abundant isotope for an element or the sum of
-- the exact masses of the most abundant isotope of each element for a
-- molecular formula.
type MonoisotopicMass  = Double

-- | The integer mass of the most abundant isotope for an element or the sum of
-- integer mass of the most abundant isotope of each element for a chemical
-- formula.
type NominalMass       = Int

-- | The average mass of an element or molecular formula based on
-- naturally-occurring abundances.
type AverageMass       = Double

-- | The exact mass of an isotope.
type IsotopicMass      = Double

--------------------------------------------------------------------------------
-- Other type synonyms

-- | The name of an element.
type ElementName       = String

-- | The natural abundance of an isotope.
type IsotopicAbundance = Double

-- | Atomic number of an element.
type AtomicNumber      = Int

-- | Proton number (i.e., the number of protons) for an element/isotope.
type ProtonNumber      = AtomicNumber

-- | Neutron number (i.e., the number of neutrons) for an element.
type NeutronNumber     = Int

-- | Type synonym for a pair containing 'ProtonNumber' and 'NeutronNumber'.
type Nucleons          = (ProtonNumber, NeutronNumber)

-- | The number of protons plus the number of neutrons (i.e., proton number +
-- neutron number) for an isotope.
type MassNumber        = Int

--------------------------------------------------------------------------------
-- 'Isotope' and 'Element' data types

-- | An 'Isotope' has three parameters; 'Nucleons', 'IsotopeMass' and
-- 'IsotopicAbundance'.
data Isotope = Isotope { nucleons          :: Nucleons
                       , isotopicMass      :: IsotopicMass
                       , isotopicAbundance :: IsotopicAbundance
                       } deriving (Show, Eq, Ord)

-- | An 'Element' has three parameters; 'AtomicNumber', 'ElementName' and
-- list of 'Isotope'.
data Element = Element { atomicNumber' :: AtomicNumber
                       , elementName'  :: ElementName
                       , isotopes'     :: [Isotope]
                       } deriving (Show, Eq, Ord)

--------------------------------------------------------------------------------
-- Element symbols

-- | Element symbols as an enumeration type.
data ElementSymbol = H  | He | Li | Be | B  | C  | N  | O  | F  | Ne | Na | Mg |
                     Al | Si | P  | S  | Cl | Ar | K  | Ca | Sc | Ti | V  | Cr |
                     Mn | Fe | Co | Ni | Cu | Zn | Ga | Ge | As | Se | Br | Kr |
                     Rb | Sr | Y  | Zr | Nb | Mo | Ru | Rh | Pd | Ag | Cd | In |
                     Sn | Sb | Te | I  | Xe | Cs | Ba | La | Ce | Pr | Nd | Sm |
                     Eu | Gd | Tb | Dy | Ho | Er | Tm | Yb | Lu | Hf | Ta | W  |
                     Re | Os | Ir | Pt | Au | Hg | Tl | Pb | Bi | Th | Pa | U
                     deriving (Show, Read, Eq, Ord, Enum, Bounded)

-- | List containing all element symbols.
elementSymbolList :: [ElementSymbol]
elementSymbolList = [H .. U]

--------------------------------------------------------------------------------
-- Functions taking an 'Element' as input

-- | Returns the most abundant naturally-occurring isotope for an element.
elementMostAbundantIsotope :: Element -> Isotope
elementMostAbundantIsotope e = maximumBy (comparing isotopicAbundance) $ isotopes' e

-- | Exact masses for all naturally-occurring isotopes for an element.
elementIsotopicMasses :: Element -> [IsotopicMass]
elementIsotopicMasses e = isotopicMass <$> isotopes' e

-- | Integer masses for all naturally-occurring isotopes for an element.
elementIntegerMasses :: Element -> [IntegerMass]
elementIntegerMasses e = massNumber . nucleons <$> isotopes' e

-- | Isotope abundances for all naturally-occurring isotopes for an element.
elementIsotopicAbundances :: Element -> [IsotopicAbundance]
elementIsotopicAbundances e = isotopicAbundance <$> isotopes' e

-- | Monoistopic mass for an element.
elementMonoisotopicMass :: Element -> IsotopicMass
elementMonoisotopicMass = isotopicMass . elementMostAbundantIsotope

-- | Nominal mass for an element.
elementNominalMass :: Element -> MassNumber
elementNominalMass = massNumber . nucleons . elementMostAbundantIsotope

-- | Average mass of an element.
elementAverageMass :: Element -> IsotopicMass
elementAverageMass e = sum [isotopicMass x * isotopicAbundance x |
                            x <- isotopes' e]

-- Mass number for an isotope. Mass number is the number of protons plus the
-- number of neutrons.
massNumber :: Nucleons -> MassNumber
massNumber (protonNum, neutronNum) = protonNum + neutronNum
--------------------------------------------------------------------------------
-- 'elements' - a map containing isotopic data for each element.

-- | Map of the periodic table. All data on isotopic masses and abundances is
-- contained within this map.
elements :: Map ElementSymbol Element
elements = fromList
  [ (H,  Element 1  "hydrogen"     [ Isotope (1, 0)     1.00782503223  0.999885
                                   , Isotope (1, 1)     2.01410177812  0.000115 ])
  , (He, Element 2  "helium"       [ Isotope (2, 1)     3.0160293201   0.00000134
                                   , Isotope (2, 2)     4.00260325413  0.99999866 ])
  , (Li, Element 3  "lithium"      [ Isotope (3, 3)     6.0151228874   0.0759
                                   , Isotope (3, 4)     7.0160034366   0.9241 ])
  , (Be, Element 4  "beryllium"    [ Isotope (4, 5)     9.012183065    1 ])
  , (B,  Element 5  "boron"        [ Isotope (5, 5)     10.01293695    0.199
                                   , Isotope (5, 6)     11.00930536    0.801 ])
  , (C,  Element 6  "carbon"       [ Isotope (6, 6)     12.0000000     0.9893
                                   , Isotope (6, 7)     13.00335483507 0.0107 ])
  , (N,  Element 7  "nitrogen"     [ Isotope (7, 7)     14.00307400443 0.99636
                                   , Isotope (7, 8)     15.00010889888 0.00364 ])
  , (O,  Element 8  "oxygen"       [ Isotope (8, 8)     15.99491461957 0.99757
                                   , Isotope (8, 9)     16.99913175650 0.00038
                                   , Isotope (8, 10)    17.99915961286 0.00205 ])
  , (F,  Element 9  "fluorine"     [ Isotope (9, 10)    18.99840316273 1 ])
  , (Ne, Element 10 "neon"         [ Isotope (10, 10)   19.9924401762  0.9048
                                   , Isotope (10, 11)   20.993846685   0.0027
                                   , Isotope (10, 12)   21.991385114   0.0925 ])
  , (Na, Element 11 "sodium"       [ Isotope (11, 12)   22.9897692820  1 ])
  , (Mg, Element 12 "magnesium"    [ Isotope (12, 12)   23.985041697   0.7899
                                   , Isotope (12, 13)   24.985836976   0.1000
                                   , Isotope (12, 14)   25.982592968   0.1101 ])
  , (Al, Element 13 "aluminium"    [ Isotope (13, 14)   26.98153853    1 ])
  , (Si, Element 14 "silicon"      [ Isotope (14, 14)   27.97692653465 0.92223
                                   , Isotope (14, 15)   28.97649466490 0.04685
                                   , Isotope (14, 16)   29.973770136   0.03092 ])
  , (P,  Element 15 "phosphorous"  [ Isotope (15, 16)   30.97376199842 1 ])
  , (S,  Element 16 "sulfur"       [ Isotope (16, 16)   31.9720711744  0.9499
                                   , Isotope (16, 17)   32.9714589098  0.0075
                                   , Isotope (16, 18)   33.967867004   0.0425
                                   , Isotope (16, 20)   35.96708071    0.0001 ])
  , (Cl, Element 17 "chlorine"     [ Isotope (17, 18)   34.968852682   0.7576
                                   , Isotope (17, 20)   36.965902602   0.2424 ])
  , (Ar, Element 18 "argon"        [ Isotope (18, 18)   35.967545105   0.003336
                                   , Isotope (18, 20)   37.96273211    0.000629
                                   , Isotope (18, 22)   39.9623831237  0.996035 ])
  , (K,  Element 19 "potassium"    [ Isotope (19, 20)   38.9637064864  0.932581
                                   , Isotope (19, 21)   39.963998166   0.000117
                                   , Isotope (19, 22)   40.9618252579  0.067302 ])
  , (Ca, Element 20 "calcium"      [ Isotope (20, 20)   39.962590863   0.96941
                                   , Isotope (20, 22)   41.95861783    0.00647
                                   , Isotope (20, 23)   42.95876644    0.00135
                                   , Isotope (20, 24)   43.95548156    0.02086
                                   , Isotope (20, 26)   45.9536890     0.00004
                                   , Isotope (20, 28)   47.95252276    0.00187 ])
  , (Sc, Element 21 "scandium"     [ Isotope (21, 24)   44.95590828    1 ])
  , (Ti, Element 22 "titanium"     [ Isotope (22, 24)   45.95262772    0.0825
                                   , Isotope (22, 25)   46.95175879    0.0744
                                   , Isotope (22, 26)   47.94794198    0.7372
                                   , Isotope (22, 27)   48.94786568    0.0541
                                   , Isotope (22, 28)   49.94478689    0.0518 ])
  , (V,  Element 23 "vanadium"     [ Isotope (23, 27)   49.94715601    0.00250
                                   , Isotope (23, 28)   50.94395704    0.99750 ])
  , (Cr, Element 24 "chromium"     [ Isotope (24, 26)   49.94604183    0.04345
                                   , Isotope (24, 28)   51.94050623    0.83789
                                   , Isotope (24, 29)   52.94064815    0.09501
                                   , Isotope (24, 30)   53.93887916    0.02365 ])
  , (Mn, Element 25 "manganese"    [ Isotope (25, 30)   54.93804391    1 ])
  , (Fe, Element 26 "iron"         [ Isotope (26, 28)   53.93960899    0.05845
                                   , Isotope (26, 30)   55.93493633    0.91754
                                   , Isotope (26, 31)   56.93539284    0.02119
                                   , Isotope (26, 32)   57.93327443    0.00282 ])
  , (Co, Element 27 "cobalt"       [ Isotope (27, 32)   58.93319429    1 ])
  , (Ni, Element 28 "nickel"       [ Isotope (28, 30)   57.93534241    0.68077
                                   , Isotope (28, 32)   59.93078588    0.26223
                                   , Isotope (28, 33)   60.93105557    0.011399
                                   , Isotope (28, 34)   61.92834537    0.036346
                                   , Isotope (28, 36)   63.92796682    0.009255 ])
  , (Cu, Element 29 "copper"       [ Isotope (29, 34)   62.92959772    0.6915
                                   , Isotope (29, 36)   64.92778970    0.3085 ])
  , (Zn, Element 30 "zinc"         [ Isotope (30, 34)   63.92914201    0.4917
                                   , Isotope (30, 36)   65.92603381    0.2773
                                   , Isotope (30, 37)   66.92712775    0.0404
                                   , Isotope (30, 38)   67.92484455    0.1845
                                   , Isotope (30, 40)   69.9253192     0.0061 ])
  , (Ga, Element 31 "gallium"      [ Isotope (31, 38)   68.9255735     0.60108
                                   , Isotope (31, 40)   70.92470258    0.39892 ])
  , (Ge, Element 32 "germanium"    [ Isotope (32, 38)   69.92424875    0.2057
                                   , Isotope (32, 40)   71.922075826   0.2745
                                   , Isotope (32, 41)   72.923458956   0.0775
                                   , Isotope (32, 42)   73.921177761   0.3650
                                   , Isotope (32, 44)   75.921402726   0.0773 ])
  , (As, Element 33 "arsenic"      [ Isotope (33, 42)   74.92159457    1 ])
  , (Se, Element 34 "selenium"     [ Isotope (34, 40)   73.922475934   0.0089
                                   , Isotope (34, 42)   75.919213704   0.0937
                                   , Isotope (34, 43)   76.919914154   0.0763
                                   , Isotope (34, 44)   77.91730928    0.2377
                                   , Isotope (34, 46)   79.9165218     0.4961
                                   , Isotope (34, 48)   81.9166995     0.0873 ])
  , (Br, Element 35 "bromine"      [ Isotope (35, 44)   78.9183376     0.5069
                                   , Isotope (35, 46)   80.9162897     0.4931 ])
  , (Kr, Element 36 "krypton"      [ Isotope (36, 42)   77.92036494    0.00355
                                   , Isotope (36, 44)   79.91637808    0.02286
                                   , Isotope (36, 46)   81.91348273    0.11593
                                   , Isotope (36, 47)   82.91412716    0.11500
                                   , Isotope (36, 48)   83.9114977282  0.56987
                                   , Isotope (36, 50)   85.9106106269  0.17279 ])
  , (Rb, Element 37 "rubidium"     [ Isotope (37, 48)   84.9117897379  0.7217
                                   , Isotope (37, 50)   86.9091805310  0.2783 ])
  , (Sr, Element 38 "strontium"    [ Isotope (38, 46)   83.9134191     0.0056
                                   , Isotope (38, 48)   85.9092606     0.0986
                                   , Isotope (38, 49)   86.9088775     0.0700
                                   , Isotope (38, 50)   87.9056125     0.8258 ])
  , (Y,  Element 39 "yttrium"      [ Isotope (39, 50)   88.9058403     1 ])
  , (Zr, Element 40 "zirconium"    [ Isotope (40, 50)   89.9046977     0.5145
                                   , Isotope (40, 51)   90.9056396     0.1122
                                   , Isotope (40, 52)   91.9050347     0.1715
                                   , Isotope (40, 54)   93.9063108     0.1738
                                   , Isotope (40, 56)   95.9082714     0.0280 ])
  , (Nb, Element 41 "niobium"      [ Isotope (41, 52)   92.9063730     1 ])
  , (Mo, Element 42 "molybdenum"   [ Isotope (42, 50)   91.90680796    0.1453
                                   , Isotope (42, 52)   93.90508490    0.0915
                                   , Isotope (42, 53)   94.90583877    0.1584
                                   , Isotope (42, 54)   95.90467612    0.1667
                                   , Isotope (42, 55)   96.90601812    0.0960
                                   , Isotope (42, 56)   97.90540482    0.2439
                                   , Isotope (42, 58)   99.9074718     0.0982 ])
  , (Ru, Element 44 "ruthenium"    [ Isotope (44, 52)   95.90759025    0.0554
                                   , Isotope (44, 54)   97.9052868     0.0187
                                   , Isotope (44, 55)   98.9059341     0.1276
                                   , Isotope (44, 56)   99.9042143     0.1260
                                   , Isotope (44, 57)   100.9055769    0.1706
                                   , Isotope (44, 58)   101.9043441    0.3155
                                   , Isotope (44, 59)   103.9054275    0.1862 ])
  , (Rh, Element 45 "rhodium"      [ Isotope (45, 58)   102.9054980    1 ])
  , (Pd, Element 46 "palladium"    [ Isotope (46, 56)   101.9056022    0.0102
                                   , Isotope (46, 58)   103.9040305    0.1114
                                   , Isotope (46, 59)   104.9050796    0.2233
                                   , Isotope (46, 60)   105.9034804    0.2733
                                   , Isotope (46, 62)   107.9038916    0.2646
                                   , Isotope (46, 64)   109.90517220   0.1172 ])
  , (Ag, Element 47 "silver"       [ Isotope (47, 60)   106.9050916    0.51839
                                   , Isotope (47, 62)   108.9047553    0.48161 ])
  , (Cd, Element 48 "cadmium"      [ Isotope (48, 58)   105.9064599    0.0125
                                   , Isotope (48, 60)   107.9041834    0.0089
                                   , Isotope (48, 62)   109.90300661   0.1249
                                   , Isotope (48, 63)   110.90418287   0.1280
                                   , Isotope (48, 64)   111.90276287   0.2413
                                   , Isotope (48, 65)   112.90440813   0.1222
                                   , Isotope (48, 66)   113.90336509   0.2873
                                   , Isotope (48, 68)   115.90476315   0.0749 ])
  , (In, Element 49 "indium"       [ Isotope (49, 64)   112.90406184   0.0429
                                   , Isotope (49, 66)   114.903878776  0.9571 ])
  , (Sn, Element 50 "tin"          [ Isotope (50, 62)   111.90482387   0.0097
                                   , Isotope (50, 64)   113.9027827    0.0066
                                   , Isotope (50, 65)   114.903344699  0.0034
                                   , Isotope (50, 66)   115.90174280   0.1454
                                   , Isotope (50, 67)   116.90295398   0.0768
                                   , Isotope (50, 68)   117.90160657   0.2422
                                   , Isotope (50, 69)   118.90331117   0.0859
                                   , Isotope (50, 70)   119.90220163   0.3258
                                   , Isotope (50, 72)   121.9034438    0.0463
                                   , Isotope (50, 74)   123.9052766    0.0579 ])
  , (Sb, Element 51 "antimony"     [ Isotope (51, 70)   120.9038120    0.5721
                                   , Isotope (51, 72)   122.9042132    0.4279 ])
  , (Te, Element 52 "tellurium"    [ Isotope (52, 68)   119.9040593    0.0009
                                   , Isotope (52, 70)   121.9030435    0.0255
                                   , Isotope (52, 71)   122.9042698    0.0089
                                   , Isotope (52, 72)   123.9028171    0.0474
                                   , Isotope (52, 73)   124.9044299    0.0707
                                   , Isotope (52, 74)   125.9033109    0.1884
                                   , Isotope (52, 76)   127.90446128   0.3174
                                   , Isotope (52, 78)   129.906222748  0.3408 ])
  , (I,  Element 53 "iodine"       [ Isotope (53, 74)   126.9044719    1 ])
  , (Xe, Element 54 "xenon"        [ Isotope (54, 70)   123.9058920    0.000952
                                   , Isotope (54, 72)   125.9042983    0.000890
                                   , Isotope (54, 74)   127.9035310    0.019102
                                   , Isotope (54, 75)   128.9047808611 0.264006
                                   , Isotope (54, 76)   129.903509349  0.040710
                                   , Isotope (54, 77)   130.90508406   0.212324
                                   , Isotope (54, 78)   131.9041550856 0.269086
                                   , Isotope (54, 80)   133.90539466   0.104357
                                   , Isotope (54, 82)   135.907214484  0.088573 ])
  , (Cs, Element 55 "caesium"      [ Isotope (55, 78)   132.9054519610 1 ])
  , (Ba, Element 56 "barium"       [ Isotope (56, 74)   129.9063207    0.00106
                                   , Isotope (56, 76)   131.9050611    0.00101
                                   , Isotope (56, 78)   133.90450818   0.02417
                                   , Isotope (56, 79)   134.90568838   0.06592
                                   , Isotope (56, 80)   135.90457573   0.07854
                                   , Isotope (56, 81)   136.90582714   0.11232
                                   , Isotope (56, 82)   137.90524700   0.71698 ])
  , (La, Element 57 "lanthanum"    [ Isotope (57, 81)   137.9071149    0.0008881
                                   , Isotope (57, 83)   138.9063563    0.9991119 ])
  , (Ce, Element 58 "cerium"       [ Isotope (58, 78)   135.90712921   0.00185
                                   , Isotope (58, 80)   137.905991     0.00251
                                   , Isotope (58, 82)   139.9054431    0.88450
                                   , Isotope (58, 84)   141.9092504    0.11114 ])
  , (Pr, Element 59 "praseodymium" [ Isotope (59, 82)   140.9076576    1 ])
  , (Nd, Element 60 "neodymium"    [ Isotope (60, 82)   141.9077290    0.27152
                                   , Isotope (60, 83)   142.9098200    0.12174
                                   , Isotope (60, 84)   143.9100930    0.23798
                                   , Isotope (60, 85)   144.9125793    0.08293
                                   , Isotope (60, 86)   145.9131226    0.17189
                                   , Isotope (60, 88)   147.9168993    0.05756
                                   , Isotope (60, 90)   149.9209022    0.05638 ])
  , (Sm, Element 62 "samarium"     [ Isotope (62, 82)   143.9120065    0.0307
                                   , Isotope (62, 83)   146.9149044    0.1499
                                   , Isotope (62, 84)   147.9148292    0.1124
                                   , Isotope (62, 85)   148.9171921    0.1382
                                   , Isotope (62, 86)   149.9172829    0.0738
                                   , Isotope (62, 88)   151.9197397    0.2675
                                   , Isotope (62, 90)   153.9222169    0.2275 ])
  , (Eu, Element 63 "europium"     [ Isotope (63, 88)   150.9198578    0.4781
                                   , Isotope (63, 90)   152.9212380    0.5219 ])
  , (Gd, Element 64 "gadolinium"   [ Isotope (64, 88)   151.9197995    0.0020
                                   , Isotope (64, 90)   153.9208741    0.0218
                                   , Isotope (64, 91)   154.9226305    0.1480
                                   , Isotope (64, 92)   155.9221312    0.2047
                                   , Isotope (64, 93)   156.9239686    0.1565
                                   , Isotope (64, 94)   157.9241123    0.2484
                                   , Isotope (64, 96)   159.9270624    0.2186 ])
  , (Tb, Element 65 "terbium"      [ Isotope (65, 94)   158.9253547    1 ])
  , (Dy, Element 66 "dysprosium"   [ Isotope (66, 90)   155.9242847    0.00056
                                   , Isotope (66, 92)   157.9244159    0.00095
                                   , Isotope (66, 94)   159.9252046    0.02329
                                   , Isotope (66, 95)   160.9269405    0.18889
                                   , Isotope (66, 96)   161.9268056    0.25475
                                   , Isotope (66, 97)   162.9287383    0.24896
                                   , Isotope (66, 98)   163.9291819    0.28260 ])
  , (Ho, Element 67 "holmium"      [ Isotope (67, 98)   164.9303288    1 ])
  , (Er, Element 68 "erbium"       [ Isotope (68, 94)   161.9287884    0.00139
                                   , Isotope (68, 96)   163.9292088    0.01601
                                   , Isotope (68, 98)   165.9302995    0.33503
                                   , Isotope (68, 99)   166.9320546    0.22869
                                   , Isotope (68, 100)  167.9323767    0.26978
                                   , Isotope (68, 102)  169.9354702    0.14910 ])
  , (Tm, Element 69 "thulium"      [ Isotope (69, 100)  168.9342179    1 ])
  , (Yb, Element 70 "ytterbium"    [ Isotope (70, 98)   167.9338896    0.00123
                                   , Isotope (70, 100)  169.9347664    0.02982
                                   , Isotope (70, 101)  170.9363302    0.1409
                                   , Isotope (70, 102)  171.9363859    0.2168
                                   , Isotope (70, 103)  172.9382151    0.16103
                                   , Isotope (70, 104)  173.9388664    0.32026
                                   , Isotope (70, 106)  175.9425764    0.12996 ])
  , (Lu, Element 71 "lutetium"     [ Isotope (71, 104)  174.9407752    0.97401
                                   , Isotope (71, 105)  175.9426897    0.02599 ])
  , (Hf, Element 72 "hafnium"      [ Isotope (72, 102)  173.9400461    0.0016
                                   , Isotope (72, 104)  175.9414076    0.0526
                                   , Isotope (72, 105)  176.9432277    0.1860
                                   , Isotope (72, 106)  177.9437058    0.2728
                                   , Isotope (72, 107)  178.9458232    0.1362
                                   , Isotope (72, 108)  179.9465570    0.3508 ])
  , (Ta, Element 73 "tantalum"     [ Isotope (73, 107)  179.9474648    0.0001201
                                   , Isotope (73, 108)  180.9479958    0.9998799 ])
  , (W,  Element 74 "tungsten"     [ Isotope (74, 106)  179.9467108    0.0012
                                   , Isotope (74, 108)  181.94820394   0.2650
                                   , Isotope (74, 109)  182.95022275   0.1431
                                   , Isotope (74, 110)  183.95093092   0.3064
                                   , Isotope (74, 112)  185.9543628    0.2843 ])
  , (Re, Element 75 "rhenium"      [ Isotope (75, 110)  184.9529545    0.3740
                                   , Isotope (75, 112)  186.9557501    0.6260 ])
  , (Os, Element 76 "osmium"       [ Isotope (76, 108)  183.9524885    0.0002
                                   , Isotope (76, 110)  185.9538350    0.0159
                                   , Isotope (76, 111)  186.9557474    0.0196
                                   , Isotope (76, 112)  187.9558352    0.1324
                                   , Isotope (76, 113)  188.9581442    0.1615
                                   , Isotope (76, 114)  189.9584437    0.2626
                                   , Isotope (76, 116)  191.9614770    0.4078 ])
  , (Ir, Element 77 "iridium"      [ Isotope (77, 114)  190.9605893    0.373
                                   , Isotope (77, 116)  192.9629216    0.627 ])
  , (Pt, Element 78 "platinum"     [ Isotope (78, 112)  189.9599297    0.00012
                                   , Isotope (78, 114)  191.9610387    0.00782
                                   , Isotope (78, 116)  193.9626809    0.3286
                                   , Isotope (78, 117)  194.9647917    0.3378
                                   , Isotope (78, 118)  195.96495209   0.2521
                                   , Isotope (78, 120)  197.9678949    0.07356 ])
  , (Au, Element 79 "gold"         [ Isotope (79, 118)  196.96656879   1 ])
  , (Hg, Element 80 "mercury"      [ Isotope (80, 116)  195.9658326    0.0015
                                   , Isotope (80, 118)  197.96676860   0.0997
                                   , Isotope (80, 119)  198.96828064   0.1687
                                   , Isotope (80, 120)  199.96832659   0.2310
                                   , Isotope (80, 121)  200.97030284   0.1318
                                   , Isotope (80, 122)  201.97064340   0.2986
                                   , Isotope (80, 124)  203.97349398   0.0687 ])
  , (Tl, Element 81 "thallium"     [ Isotope (81, 122)  202.9723446    0.2952
                                   , Isotope (81, 124)  204.9744278    0.7048 ])
  , (Pb, Element 82 "lead"         [ Isotope (82, 122)  203.9730440    0.014
                                   , Isotope (82, 124)  205.9744657    0.241
                                   , Isotope (82, 125)  206.9758973    0.221
                                   , Isotope (82, 126)  207.9766525    0.524 ])
  , (Bi, Element 83 "bismuth"      [ Isotope (83, 126)  208.9803991    1 ])
  , (Th, Element 90 "thorium"      [ Isotope (90, 142)  232.0380558    1 ])
  , (Pa, Element 91 "protactinium" [ Isotope (91, 140)  231.0358842    1 ])
  , (U,  Element 92 "uranium"      [ Isotope (92, 142)  234.0409523    0.000054
                                   , Isotope (92, 143)  235.0439301    0.007204
                                   , Isotope (92, 146)  238.0507884    0.992742 ])
  ]

instance ChemicalMass ElementSymbol where
    toElementalComposition x = ElementalComposition . fromList $ [(x, 1)]

--------------------------------------------------------------------------------
-- Functions taking an 'elementSymbol' as input

-- | Searches elements (a map) with an 'ElementSymbol' key and returns
-- information for the element (wrapped in 'Maybe').
lookupElement :: ElementSymbol -> Maybe Element
lookupElement = flip lookup elements

-- | Searches 'elements' (a map) with an 'ElementSymbol' key and returns
-- information for the element.
findElement :: ElementSymbol -> Element
findElement = (!) elements

-- | Returns the name for an element symbol.
elementName :: ElementSymbol -> ElementName
elementName = elementName' . findElement

-- | Returns the atomic number for an element.
atomicNumber :: ElementSymbol -> AtomicNumber
atomicNumber = atomicNumber' . findElement

-- | Returns all the naturally-occurring isotopes for an element.
isotopes :: ElementSymbol -> [Isotope]
isotopes = isotopes' . findElement

-- | Returns the most abundant naturally-occurring isotope for an element.
mostAbundantIsotope :: ElementSymbol -> Isotope
mostAbundantIsotope = elementMostAbundantIsotope . findElement

-- | Selects an isotope of element based on the isotope's mass number
-- ('IntegerMass'). Note: This is a partial function.
selectIsotope :: ElementSymbol -> MassNumber -> Isotope
selectIsotope sym mass = isotopeList !! indexOfIsotope
    where isotopeList = isotopes sym
          indexOfIsotope = fromJust $ elemIndex mass (integerMasses sym)

-- | Exact masses for all naturally-occurring isotopes for an element.
isotopicMasses :: ElementSymbol -> [IsotopicMass]
isotopicMasses = elementIsotopicMasses . findElement

-- | Integer masses for all naturally-occurring isotopes for an element.
integerMasses :: ElementSymbol -> [IntegerMass]
integerMasses = elementIntegerMasses . findElement

-- | Isotope abundances for all naturally-occurring isotopes for an element.
isotopicAbundances :: ElementSymbol -> [IsotopicAbundance]
isotopicAbundances = elementIsotopicAbundances . findElement

--------------------------------------------------------------------------------
-- 'ElementalComposition' type class

-- | Class containing four methods; 'toElementalComposition',
-- 'monoisotopicMass', 'nominalMass' and 'averageMass'.
class ChemicalMass a where
     toElementalComposition :: a -> ElementalComposition
     monoisotopicMass       :: a -> MonoisotopicMass
     nominalMass            :: a -> NominalMass
     averageMass            :: a -> AverageMass
     monoisotopicMass = getFormulaSum elementMonoisotopicMass
     nominalMass      = getFormulaSum elementNominalMass
     averageMass      = getFormulaSum elementAverageMass
     {-# MINIMAL (toElementalComposition) #-}

-- Helper function for the calculating monoistopic masses, average mass and
-- nominal masses for molecular formulae.
getFormulaSum :: (Num a, ChemicalMass b) => (Element -> a) -> b -> a
getFormulaSum f m = sum $
    mapWithKey mapFunc (getComposition (toElementalComposition m))
  where mapFunc k v = (f . findElement) k * fromIntegral v

--------------------------------------------------------------------------------
-- Molecular formulae

-- | 'MolecularFormula' is a newtype to represent a molecular formula.
newtype MolecularFormula = MolecularFormula {
    getMolecularFormula :: Map ElementSymbol Int }
        deriving (Show, Read, Eq, Ord)

-- | Provided since 'EmpiricalFormula' can be thought of as a chemical
-- composition but is not a molecular formula.
newtype ElementalComposition = ElementalComposition  {
    getComposition :: Map ElementSymbol Int }
        deriving (Show, Read, Eq, Ord)

instance Monoid MolecularFormula where
   mempty = emptyFormula
   mappend = (|+|)

-- | Infix operator for the addition of molecular formulae. (|+|) is mappend in
-- the monoid instance and the same fixity as (+).
(|+|) :: MolecularFormula ->  MolecularFormula ->  MolecularFormula
(|+|) =  combineMolecularFormulae (+)

-- | Infix operator for the subtraction of molecular formulae. Has the same
-- fixity as (-).
(|-|) :: MolecularFormula ->  MolecularFormula ->  MolecularFormula
(|-|) = combineMolecularFormulae (-)

-- | Infix operator for the multiplication of molecular formulae. Has the same
-- fixity as (*).
(|*|) :: Int -> MolecularFormula ->  MolecularFormula
n |*| m = MolecularFormula . filterZero $
              (fromIntegral n *) <$> getMolecularFormula m

infixl 6 |+|
infixl 7 |*|
infixl 6 |-|

-- The function unionWith adapted to work with 'MolecularFormula'.
combineMolecularFormulae :: (Int -> Int -> Int)
    -> MolecularFormula -> MolecularFormula -> MolecularFormula
combineMolecularFormulae f m1 m2 = MolecularFormula $
                                       filterZero $ unionWith f
                                                    (getMolecularFormula m1)
                                                    (getMolecularFormula m2)

-- Helper function to remove (k, v) pairs where v == 0.
filterZero :: Map k Int -> Map k Int
filterZero = filter (/= 0)

instance ChemicalMass MolecularFormula where
    toElementalComposition (MolecularFormula m) = ElementalComposition m

class (ChemicalMass a) => ToMolecularFormula a where
    toMolecularFormula :: a -> MolecularFormula

-- | Type class with two methods, 'renderFormula' and 'emptyFormula'. The
-- 'renderFormula' method converts a formula to its shorthand notation.
class Formula a where
    renderFormula :: a -> String
    emptyFormula :: a

-- | Smart constructor to make values of type 'MolecularFormula'.
mkMolecularFormula :: [(ElementSymbol, Int)] -> MolecularFormula
mkMolecularFormula = MolecularFormula . filterZero . fromList

instance Formula MolecularFormula where
   renderFormula f = foldMap renderFoldfunc ((sortElementSymbolMap . getMolecularFormula) f)
   emptyFormula = mkMolecularFormula []

-- Helper function for 'renderFormula'.
renderFoldfunc :: (ElementSymbol, Int) -> String
renderFoldfunc (sym, num) = show sym ++ if num == 1
                                           then ""
                                           else show num

-- Use the Hill system for writing molecular formulas. C then H followed by
-- elements in alphabetical order.
sortElementSymbolMap :: Map ElementSymbol Int -> [(ElementSymbol, Int)]
sortElementSymbolMap m = sortBy (hillSystem fst) elementSymbolIntList
    where
      elementSymbolIntList = toList m
      elementSymbols = fst <$> elementSymbolIntList
      containsC = C `elem` elementSymbols
      hillSystem f a b = case (f a, f b) of
        (C, _)   -> LT
        (_, C)   -> GT
        (H, b')  -> if containsC then LT else (show . elementName) H `compare` show b'
        (a', H)  -> if containsC then GT else show a' `compare` (show . elementName) H
        (a', b') -> show a' `compare` show b'

--------------------------------------------------------------------------------
-- Condensed formulae

-- | 'CondensedFormula' is a newtype to represent a condensed formula.
newtype CondensedFormula = CondensedFormula {
    getCondensedFormula :: [Either MolecularFormula ([MolecularFormula], Int)] }
        deriving (Show, Read, Eq, Ord)

instance ChemicalMass CondensedFormula where
    toElementalComposition = toElementalComposition

instance ToMolecularFormula CondensedFormula where
    toMolecularFormula c = foldMap foldFunc (getCondensedFormula c)
       where foldFunc = \case
                         Left chemForm -> chemForm
                         Right (molForm, n) -> n |*| mconcat molForm

instance Formula CondensedFormula where
    renderFormula c = foldMap foldFunc (getCondensedFormula c)
        where foldFunc = \case
                          Left chemForm -> renderFormula chemForm
                          Right (chemFormList, n) ->
                              "(" ++ foldMap renderFormula chemFormList ++ ")" ++ formatNum n
                                  where formatNum n' = if n' == 1 then "" else show n'
    emptyFormula = CondensedFormula []

--------------------------------------------------------------------------------
-- | 'EmpiricalFormula' is a newtype to represent a empirical formula.
newtype EmpiricalFormula = EmpiricalFormula {
    getEmpiricalFormula :: Map ElementSymbol Int }
        deriving (Show, Read, Eq, Ord)

instance ChemicalMass EmpiricalFormula where
    toElementalComposition (EmpiricalFormula a) = ElementalComposition a

instance Formula EmpiricalFormula where
   renderFormula f = foldMap renderFoldfunc ((sortElementSymbolMap . getEmpiricalFormula) f)
   emptyFormula = mkEmpiricalFormula []

-- | Smart constructor to make values of type 'EmpiricalFormula'.
mkEmpiricalFormula :: [(ElementSymbol, Int)] -> EmpiricalFormula
mkEmpiricalFormula = EmpiricalFormula . filterZero . fromList

-- | Type class with a single method, 'toEmpiricalFormula', which converts a
-- chemical data type to `EmpiricalFormula`.
class ChemicalMass a => ToEmpiricalFormula a where
  toEmpiricalFormula :: a -> EmpiricalFormula

instance ToEmpiricalFormula MolecularFormula where
  toEmpiricalFormula (MolecularFormula m)
    | null m   = EmpiricalFormula m
    | otherwise = EmpiricalFormula $ (`div` greatestCommonDenom m) <$> m

instance ToEmpiricalFormula CondensedFormula where
  toEmpiricalFormula = toEmpiricalFormula . toMolecularFormula

-- Helper function to find the greatest common denominator in a map.
greatestCommonDenom :: (Integral v) => Map k v -> v
greatestCommonDenom = foldr gcd 0