    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE RankNTypes #-}

    {- |
       Copyright  : Copyright (C) 2006-2018 Bjorn Buckwalter
       License    : BSD3

       Maintainer : bjorn@buckwalter.se
       Stability  : Stable
       Portability: GHC only

    = Summary

    This module defines the SI prefixes, the SI base units and the SI
    derived units. It also defines the units outside of the SI that are
    accepted for use with the SI. Any chapters, sections or tables
    referenced are from <#note1 [1]> unless otherwise specified.

    = References

    1. #note1# http://physics.nist.gov/Pubs/SP811/
    2. #note2# http://en.wikipedia.org/wiki/Minute_of_arc
    3. #note3# http://en.wikipedia.org/wiki/Astronomical_unit

    -}

    module Numeric.Units.Dimensional.SIUnits
    (
      -- * SI Base Units
      -- $base-units
      metre, meter, gram, second, ampere, kelvin, mole, candela,
      -- * SI Derived Units
      -- $derived-units
      radian, steradian, hertz, newton, pascal, joule, watt, coulomb, volt, farad, ohm, siemens, weber, tesla, henry, lumen, lux,
      -- ** Celsius Temperature
      -- $celsius
      degreeCelsius, fromDegreeCelsiusAbsolute, toDegreeCelsiusAbsolute,
      -- ** Units Admitted for Reasons of Safeguarding Human Health
      -- $health
      becquerel, gray, sievert, katal,
      -- * Units Accepted for Use with the SI
      -- $accepted-units
      minute, hour, day,
      hectare, litre, liter, tonne, metricTon,
      -- ** Units of Plane Angle
      -- $arc-units
      degree, arcminute, arcsecond,
      -- $arc-units-alternate
      degreeOfArc, minuteOfArc, secondOfArc,
      -- ** Units Formerly Defined By Experiment
      -- $values-obtained-experimentally
      astronomicalUnit,
      -- * SI Prefixes
      -- $multiples
      deka, deca, hecto, kilo, mega, giga, tera, peta, exa, zetta, yotta,
      -- $submultiples
      deci, centi, milli, micro, nano, pico, femto, atto, zepto, yocto,
      -- $reified-prefixes
      Prefix, applyPrefix, siPrefixes
    )
    where

    import Data.Ratio
    import Numeric.Units.Dimensional
    import Numeric.Units.Dimensional.Quantities
    import Numeric.Units.Dimensional.UnitNames (Prefix, siPrefixes)
    import qualified Numeric.Units.Dimensional.UnitNames as N
    import Numeric.Units.Dimensional.UnitNames.Internal (ucum, ucumMetric)
    import qualified Numeric.Units.Dimensional.UnitNames.Internal as I
    import Numeric.NumType.DK.Integers ( pos3 )
    import Prelude ( Eq(..), ($), Num, Fractional, Floating, otherwise, error)
    import qualified Prelude

    {- $multiples
    Prefixes are used to form decimal multiples and submultiples of SI
    Units as described in section 4.4. We will define the SI prefixes
    in terms of the 'prefix' function which applies a scale factor to a
    unit.

    By defining SI prefixes as functions applied to a 'Unit' we satisfy
    section 6.2.6 "Unacceptability of stand-alone prefixes".

    We define all SI prefixes from Table 5. Multiples first.
    -}

    applyMultiple :: (Num a) => Prefix -> Unit 'Metric d a -> Unit 'NonMetric d a
    applyMultiple p u | denominator x == 1 = mkUnitZ n' (numerator x) u
                      | otherwise = error "Attempt to apply a submultiple prefix as a multiple."
      where
        n' = N.applyPrefix p (name u)
        x = N.scaleFactor p

    deka, deca, hecto, kilo, mega, giga, tera, peta, exa, zetta, yotta
      :: Num a => Unit 'Metric d a -> Unit 'NonMetric d a
    deka  = applyMultiple I.deka -- International English.
    deca  = deka      -- American English.
    hecto = applyMultiple I.hecto
    kilo  = applyMultiple I.kilo
    mega  = applyMultiple I.mega
    giga  = applyMultiple I.giga
    tera  = applyMultiple I.tera
    peta  = applyMultiple I.peta
    exa   = applyMultiple I.exa
    zetta = applyMultiple I.zetta
    yotta = applyMultiple I.yotta

    {- $submultiples
    Then the submultiples.
    -}

    applyPrefix :: (Fractional a) => Prefix -> Unit 'Metric d a -> Unit 'NonMetric d a
    applyPrefix p u = mkUnitQ n' x u
      where
        n' = N.applyPrefix p (name u)
        x = N.scaleFactor p

    deci, centi, milli, micro, nano, pico, femto, atto, zepto, yocto
      :: Fractional a => Unit 'Metric d a -> Unit 'NonMetric d a
    deci  = applyPrefix I.deci
    centi = applyPrefix I.centi
    milli = applyPrefix I.milli
    micro = applyPrefix I.micro
    nano  = applyPrefix I.nano
    pico  = applyPrefix I.pico
    femto = applyPrefix I.femto
    atto  = applyPrefix I.atto
    zepto = applyPrefix I.zepto
    yocto = applyPrefix I.yocto

    {- $reified-prefixes

    We supply an explicit representation of an SI prefix, along with a function to apply one and a
    list of all prefixes defined by the SI.

    -}

    {- $base-units
    These are the base units from section 4.1. To avoid a
    myriad of one-letter functions that would doubtlessly cause clashes
    and frustration in users' code we spell out all unit names in full,
    as we did for prefixes. We also elect to spell the unit names in
    singular form, as allowed by section 9.7 "Other spelling conventions".

    We define the SI base units in the order of table 1.
    -}

    metre, meter :: Num a => Unit 'Metric DLength a
    metre = mkUnitZ I.nMeter 1 siUnit -- International English.
    meter = metre         -- American English.

    {-

    For mass the SI base unit is kilogram. For sensible prefixes we
    define gram here (see section 6.2.7 "Prefixes and the kilogram").
    The drawback is that we are forced to use 'Fractional'.

    -}

    gram    :: Fractional a => Unit 'Metric DMass a
    gram    = mkUnitQ I.nGram 1e-3 siUnit
    second  :: Num a => Unit 'Metric DTime a
    second  = mkUnitZ I.nSecond 1 siUnit
    ampere  :: Num a => Unit 'Metric DElectricCurrent a
    ampere  = mkUnitZ I.nAmpere 1 siUnit
    kelvin  :: Num a => Unit 'Metric DThermodynamicTemperature a
    kelvin  = mkUnitZ I.nKelvin 1 siUnit
    mole    :: Num a => Unit 'Metric DAmountOfSubstance a
    mole    = mkUnitZ I.nMole 1 siUnit
    candela :: Num a => Unit 'Metric DLuminousIntensity a
    candela = mkUnitZ I.nCandela 1 siUnit

    {- $derived-units
    From Table 3, SI derived units with special names and symbols, including the
    radian and steradian.
    -}

    radian :: Num a => Unit 'Metric DPlaneAngle a
    radian = mkUnitZ (ucumMetric "rad" "rad" "radian") 1 siUnit -- meter * meter ^ neg1
    steradian :: Num a => Unit 'Metric DSolidAngle a
    steradian = mkUnitZ (ucumMetric "sr" "sr" "steradian") 1 siUnit -- meter ^ pos2 * meter ^ neg2
    hertz :: Num a => Unit 'Metric DFrequency a
    hertz = mkUnitZ (ucumMetric "Hz" "Hz" "Hertz") 1 $ siUnit
    newton :: Num a => Unit 'Metric DForce a
    newton = mkUnitZ (ucumMetric "N" "N" "Newton") 1 $ siUnit
    pascal :: Num a => Unit 'Metric DPressure a
    pascal = mkUnitZ (ucumMetric "Pa" "Pa" "Pascal") 1 $ siUnit
    joule :: Num a => Unit 'Metric DEnergy a
    joule = mkUnitZ (ucumMetric "J" "J" "Joule") 1 $ siUnit
    watt :: Num a => Unit 'Metric DPower a
    watt = mkUnitZ (ucumMetric "W" "W" "Watt") 1 $ siUnit
    coulomb :: Num a => Unit 'Metric DElectricCharge a
    coulomb = mkUnitZ (ucumMetric "C" "C" "Coulomb") 1 $ siUnit
    volt :: Num a => Unit 'Metric DElectricPotential a
    volt = mkUnitZ (ucumMetric "V" "V" "Volt") 1 $ siUnit
    farad :: Num a => Unit 'Metric DCapacitance a
    farad = mkUnitZ (ucumMetric "F" "F" "Farad") 1 $ siUnit
    ohm :: Num a => Unit 'Metric DElectricResistance a
    ohm = mkUnitZ (ucumMetric "Ohm" "Ω" "Ohm") 1 $ siUnit
    siemens :: Num a => Unit 'Metric DElectricConductance a
    siemens = mkUnitZ (ucumMetric "S" "S" "Siemens") 1 $ siUnit
    weber :: Num a => Unit 'Metric DMagneticFlux a
    weber = mkUnitZ (ucumMetric "Wb" "Wb" "Weber") 1 $ siUnit
    tesla :: Num a => Unit 'Metric DMagneticFluxDensity a
    tesla = mkUnitZ (ucumMetric "T" "T" "Tesla") 1 $ siUnit
    henry :: Num a => Unit 'Metric DInductance a
    henry = mkUnitZ (ucumMetric "H" "H" "Henry") 1 $ siUnit

    {-
    We defer the definition of Celcius temperature to another section (would
    appear here if we stricly followed table 3).
    -}

    lumen :: Num a => Unit 'Metric DLuminousFlux a
    lumen = mkUnitZ (ucumMetric "lm" "lm" "lumen") 1 $ siUnit
    lux :: Num a => Unit 'Metric DIlluminance a
    lux = mkUnitZ (ucumMetric "lx" "lx" "lux") 1 $ siUnit

    {- $celsius
    A problematic area is units which increase proportionally to the
    base SI units but cross zero at a different point. An example would
    be degrees Celsius (see section 4.2.1.1). The author feels that it
    is appropriate to define a unit for use with relative quantities
    (taking only into account the proportionality) and complement the
    unit with functions for converting absolute values.

    The function 'fromDegreeCelsiusAbsolute' should be used in lieu of
    "*~ degreeCelsius" when working with absolute temperatures. Similarily,
    'toDegreeCelsiusAbsolute' should be used in lieu of "/~ degreeCelsius"
    when working with absolute temperatures.
    -}

    degreeCelsius :: Num a => Unit 'Metric DCelsiusTemperature a
    degreeCelsius = kelvin

    fromDegreeCelsiusAbsolute :: Floating a => a -> ThermodynamicTemperature a
    fromDegreeCelsiusAbsolute x = x *~ degreeCelsius + 273.15 *~ degreeCelsius
    toDegreeCelsiusAbsolute :: Floating a => ThermodynamicTemperature a -> a
    toDegreeCelsiusAbsolute x = (x - 273.15 *~ degreeCelsius) /~ degreeCelsius

    {- $health

    The last units from Table 3 are SI derived units with special names and symbols admitted for reasons
    of safeguarding human health.
    -}

    becquerel :: Num a => Unit 'Metric DActivity a
    becquerel = mkUnitZ (ucumMetric "Bq" "Bq" "Becquerel") 1 $ siUnit
    gray :: Num a => Unit 'Metric DAbsorbedDose a
    gray = mkUnitZ (ucumMetric "Gy" "Gy" "Gray") 1 $ siUnit
    sievert :: Num a => Unit 'Metric DDoseEquivalent a
    sievert = mkUnitZ (ucumMetric "Sv" "Sv" "Sievert") 1 $ siUnit
    katal :: Num a => Unit 'Metric DCatalyticActivity a
    katal = mkUnitZ (ucumMetric "kat" "kat" "katal") 1 $ siUnit

    {- $accepted-units
    There are several units that are not strictly part of the SI but
    are either permanently or temporarily accepted for use with the SI.
    We define the permanently accepted ones in this module.

    From Table 6, Units accepted for use with the SI.

    We start with time which we grant exclusive rights to 'minute' and
    'second'.
    -}
    minute, hour, day :: Num a => Unit 'NonMetric DTime a
    minute = mkUnitZ (ucum "min" "min" "minute") 60 $ second
    hour   = mkUnitZ (ucum "h" "h" "hour")       60 $ minute
    day    = mkUnitZ (ucum "d" "d" "day")        24 $ hour -- Mean solar day.

    {- $arc-units

    Since 'minute' and 'second' are already in use for time we use
    'arcminute' and 'arcsecond' <#note2 [2]> for plane angle instead.
    -}

    degree, arcminute, arcsecond :: Floating a => Unit 'NonMetric DPlaneAngle a
    degree    = mkUnitR (ucum "deg" "°" "degree")    (Prelude.pi Prelude./ 180) $ radian
    arcminute = mkUnitR (ucum "'" "'" "arcminute")   (Prelude.recip 60)         $ degreeOfArc
    arcsecond = mkUnitR (ucum "''" "''" "arcsecond") (Prelude.recip 60)         $ minuteOfArc

    {- $arc-units-alternate
    Alternate (longer) forms of the above. In particular 'degreeOfArc'
    can be used if there is a percieved need to disambiguate from e.g.
    temperature.
    -}

    degreeOfArc, minuteOfArc, secondOfArc :: Floating a => Unit 'NonMetric DPlaneAngle a
    degreeOfArc = degree
    secondOfArc = arcsecond
    minuteOfArc = arcminute

    hectare :: Fractional a => Unit 'NonMetric DArea a
    hectare = square (hecto meter)

    litre, liter :: Fractional a => Unit 'Metric DVolume a
    litre = mkUnitQ (ucumMetric "L" "L" "litre") 1 $ deci meter ^ pos3 -- International English.
    liter = litre             -- American English.

    tonne, metricTon :: Num a => Unit 'Metric DMass a
    tonne     = mkUnitZ (ucumMetric "t" "t" "tonne") 1000 $ siUnit -- Name in original SI text.
    metricTon = tonne                   -- American name.

    {- $values-obtained-experimentally
    We decline to provide here those units - listed in Table 7 - which,
    while accepted for use with the SI, have values which are determined experimentally.
    For versioning purposes, those units can be found in "Numeric.Units.Dimensional.NonSI".

    However, in 2012 the IAU redefined the astronomical unit as a conventional
    unit of length directly tied to the meter, with a length of exactly
    149 597 870 700 m and the official abbreviation of au <#note3 [3]>. We therefore include it here.
    -}

    astronomicalUnit :: Num a => Unit 'NonMetric DLength a
    astronomicalUnit = mkUnitZ (ucum "AU" "AU" "astronomical unit") 149597870700 $ meter