"""
SI unit system.
Based on MKSA, which stands for "meter, kilogram, second, ampere".
Added kelvin, candela and mole.

"""

from typing import List

from sympy.physics.units import DimensionSystem, Dimension, dHg0

from sympy.physics.units.quantities import Quantity

from sympy.core.numbers import (Rational, pi)
from sympy.core.singleton import S
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.physics.units.definitions.dimension_definitions import (
    acceleration, action, current, impedance, length, mass, time, velocity,
    amount_of_substance, temperature, information, frequency, force, pressure,
    energy, power, charge, voltage, capacitance, conductance, magnetic_flux,
    magnetic_density, inductance, luminous_intensity
)
from sympy.physics.units.definitions import (
    kilogram, newton, second, meter, gram, cd, K, joule, watt, pascal, hertz,
    coulomb, volt, ohm, siemens, farad, henry, tesla, weber, dioptre, lux,
    katal, gray, becquerel, inch, liter, julian_year, gravitational_constant,
    speed_of_light, elementary_charge, planck, hbar, electronvolt,
    avogadro_number, avogadro_constant, boltzmann_constant,
    stefan_boltzmann_constant, Da, atomic_mass_constant, molar_gas_constant,
    faraday_constant, josephson_constant, von_klitzing_constant,
    acceleration_due_to_gravity, magnetic_constant, vacuum_permittivity,
    vacuum_impedance, coulomb_constant, atmosphere, bar, pound, psi, mmHg,
    milli_mass_unit, quart, lightyear, astronomical_unit, planck_mass,
    planck_time, planck_temperature, planck_length, planck_charge, planck_area,
    planck_volume, planck_momentum, planck_energy, planck_force, planck_power,
    planck_density, planck_energy_density, planck_intensity,
    planck_angular_frequency, planck_pressure, planck_current, planck_voltage,
    planck_impedance, planck_acceleration, bit, byte, kibibyte, mebibyte,
    gibibyte, tebibyte, pebibyte, exbibyte, curie, rutherford, radian, degree,
    steradian, angular_mil, atomic_mass_unit, gee, kPa, ampere, u0, c, kelvin,
    mol, mole, candela, m, kg, s, electric_constant, G, boltzmann
)
from sympy.physics.units.prefixes import PREFIXES, prefix_unit
from sympy.physics.units.systems.mksa import MKSA, dimsys_MKSA

derived_dims = (frequency, force, pressure, energy, power, charge, voltage,
                capacitance, conductance, magnetic_flux,
                magnetic_density, inductance, luminous_intensity)
base_dims = (amount_of_substance, luminous_intensity, temperature)

units = [mol, cd, K, lux, hertz, newton, pascal, joule, watt, coulomb, volt,
        farad, ohm, siemens, weber, tesla, henry, candela, lux, becquerel,
        gray, katal]

all_units = []  # type: List[Quantity]
for u in units:
    all_units.extend(prefix_unit(u, PREFIXES))

all_units.extend(units)
all_units.extend([mol, cd, K, lux])


dimsys_SI = dimsys_MKSA.extend(
    [
        # Dimensional dependencies for other base dimensions:
        temperature,
        amount_of_substance,
        luminous_intensity,
    ])

dimsys_default = dimsys_SI.extend(
    [information],
)

SI = MKSA.extend(base=(mol, cd, K), units=all_units, name='SI', dimension_system=dimsys_SI, derived_units={
    power: watt,
    magnetic_flux: weber,
    time: second,
    impedance: ohm,
    pressure: pascal,
    current: ampere,
    voltage: volt,
    length: meter,
    frequency: hertz,
    inductance: henry,
    temperature: kelvin,
    amount_of_substance: mole,
    luminous_intensity: candela,
    conductance: siemens,
    mass: kilogram,
    magnetic_density: tesla,
    charge: coulomb,
    force: newton,
    capacitance: farad,
    energy: joule,
    velocity: meter/second,
})

One = S.One

SI.set_quantity_dimension(radian, One)

SI.set_quantity_scale_factor(ampere, One)

SI.set_quantity_scale_factor(kelvin, One)

SI.set_quantity_scale_factor(mole, One)

SI.set_quantity_scale_factor(candela, One)

# MKSA extension to MKS: derived units

SI.set_quantity_scale_factor(coulomb, One)

SI.set_quantity_scale_factor(volt, joule/coulomb)

SI.set_quantity_scale_factor(ohm, volt/ampere)

SI.set_quantity_scale_factor(siemens, ampere/volt)

SI.set_quantity_scale_factor(farad, coulomb/volt)

SI.set_quantity_scale_factor(henry, volt*second/ampere)

SI.set_quantity_scale_factor(tesla, volt*second/meter**2)

SI.set_quantity_scale_factor(weber, joule/ampere)


SI.set_quantity_dimension(lux, luminous_intensity / length ** 2)
SI.set_quantity_scale_factor(lux, steradian*candela/meter**2)

# katal is the SI unit of catalytic activity

SI.set_quantity_dimension(katal, amount_of_substance / time)
SI.set_quantity_scale_factor(katal, mol/second)

# gray is the SI unit of absorbed dose

SI.set_quantity_dimension(gray, energy / mass)
SI.set_quantity_scale_factor(gray, meter**2/second**2)

# becquerel is the SI unit of radioactivity

SI.set_quantity_dimension(becquerel, 1 / time)
SI.set_quantity_scale_factor(becquerel, 1/second)

#### CONSTANTS ####

# elementary charge
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(elementary_charge, charge)
SI.set_quantity_scale_factor(elementary_charge, 1.602176634e-19*coulomb)

# Electronvolt
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(electronvolt, energy)
SI.set_quantity_scale_factor(electronvolt, 1.602176634e-19*joule)

# Avogadro number
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(avogadro_number, One)
SI.set_quantity_scale_factor(avogadro_number, 6.02214076e23)

# Avogadro constant

SI.set_quantity_dimension(avogadro_constant, amount_of_substance ** -1)
SI.set_quantity_scale_factor(avogadro_constant, avogadro_number / mol)

# Boltzmann constant
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(boltzmann_constant, energy / temperature)
SI.set_quantity_scale_factor(boltzmann_constant, 1.380649e-23*joule/kelvin)

# Stefan-Boltzmann constant
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(stefan_boltzmann_constant, energy * time ** -1 * length ** -2 * temperature ** -4)
SI.set_quantity_scale_factor(stefan_boltzmann_constant, pi**2 * boltzmann_constant**4 / (60 * hbar**3 * speed_of_light ** 2))

# Atomic mass
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(atomic_mass_constant, mass)
SI.set_quantity_scale_factor(atomic_mass_constant, 1.66053906660e-24*gram)

# Molar gas constant
# REF: NIST SP 959 (June 2019)

SI.set_quantity_dimension(molar_gas_constant, energy / (temperature * amount_of_substance))
SI.set_quantity_scale_factor(molar_gas_constant, boltzmann_constant * avogadro_constant)

# Faraday constant

SI.set_quantity_dimension(faraday_constant, charge / amount_of_substance)
SI.set_quantity_scale_factor(faraday_constant, elementary_charge * avogadro_constant)

# Josephson constant

SI.set_quantity_dimension(josephson_constant, frequency / voltage)
SI.set_quantity_scale_factor(josephson_constant, 0.5 * planck / elementary_charge)

# Von Klitzing constant

SI.set_quantity_dimension(von_klitzing_constant, voltage / current)
SI.set_quantity_scale_factor(von_klitzing_constant, hbar / elementary_charge ** 2)

# Acceleration due to gravity (on the Earth surface)

SI.set_quantity_dimension(acceleration_due_to_gravity, acceleration)
SI.set_quantity_scale_factor(acceleration_due_to_gravity, 9.80665*meter/second**2)

# magnetic constant:

SI.set_quantity_dimension(magnetic_constant, force / current ** 2)
SI.set_quantity_scale_factor(magnetic_constant, 4*pi/10**7 * newton/ampere**2)

# electric constant:

SI.set_quantity_dimension(vacuum_permittivity, capacitance / length)
SI.set_quantity_scale_factor(vacuum_permittivity, 1/(u0 * c**2))

# vacuum impedance:

SI.set_quantity_dimension(vacuum_impedance, impedance)
SI.set_quantity_scale_factor(vacuum_impedance, u0 * c)

# Coulomb's constant:
SI.set_quantity_dimension(coulomb_constant, force * length ** 2 / charge ** 2)
SI.set_quantity_scale_factor(coulomb_constant, 1/(4*pi*vacuum_permittivity))

SI.set_quantity_dimension(psi, pressure)
SI.set_quantity_scale_factor(psi, pound * gee / inch ** 2)

SI.set_quantity_dimension(mmHg, pressure)
SI.set_quantity_scale_factor(mmHg, dHg0 * acceleration_due_to_gravity * kilogram / meter**2)

SI.set_quantity_dimension(milli_mass_unit, mass)
SI.set_quantity_scale_factor(milli_mass_unit, atomic_mass_unit/1000)

SI.set_quantity_dimension(quart, length ** 3)
SI.set_quantity_scale_factor(quart, Rational(231, 4) * inch**3)

# Other convenient units and magnitudes

SI.set_quantity_dimension(lightyear, length)
SI.set_quantity_scale_factor(lightyear, speed_of_light*julian_year)

SI.set_quantity_dimension(astronomical_unit, length)
SI.set_quantity_scale_factor(astronomical_unit, 149597870691*meter)

# Fundamental Planck units:

SI.set_quantity_dimension(planck_mass, mass)
SI.set_quantity_scale_factor(planck_mass, sqrt(hbar*speed_of_light/G))

SI.set_quantity_dimension(planck_time, time)
SI.set_quantity_scale_factor(planck_time, sqrt(hbar*G/speed_of_light**5))

SI.set_quantity_dimension(planck_temperature, temperature)
SI.set_quantity_scale_factor(planck_temperature, sqrt(hbar*speed_of_light**5/G/boltzmann**2))

SI.set_quantity_dimension(planck_length, length)
SI.set_quantity_scale_factor(planck_length, sqrt(hbar*G/speed_of_light**3))

SI.set_quantity_dimension(planck_charge, charge)
SI.set_quantity_scale_factor(planck_charge, sqrt(4*pi*electric_constant*hbar*speed_of_light))

# Derived Planck units:

SI.set_quantity_dimension(planck_area, length ** 2)
SI.set_quantity_scale_factor(planck_area, planck_length**2)

SI.set_quantity_dimension(planck_volume, length ** 3)
SI.set_quantity_scale_factor(planck_volume, planck_length**3)

SI.set_quantity_dimension(planck_momentum, mass * velocity)
SI.set_quantity_scale_factor(planck_momentum, planck_mass * speed_of_light)

SI.set_quantity_dimension(planck_energy, energy)
SI.set_quantity_scale_factor(planck_energy, planck_mass * speed_of_light**2)

SI.set_quantity_dimension(planck_force, force)
SI.set_quantity_scale_factor(planck_force, planck_energy / planck_length)

SI.set_quantity_dimension(planck_power, power)
SI.set_quantity_scale_factor(planck_power, planck_energy / planck_time)

SI.set_quantity_dimension(planck_density, mass / length ** 3)
SI.set_quantity_scale_factor(planck_density, planck_mass / planck_length**3)

SI.set_quantity_dimension(planck_energy_density, energy / length ** 3)
SI.set_quantity_scale_factor(planck_energy_density, planck_energy / planck_length**3)

SI.set_quantity_dimension(planck_intensity, mass * time ** (-3))
SI.set_quantity_scale_factor(planck_intensity, planck_energy_density * speed_of_light)

SI.set_quantity_dimension(planck_angular_frequency, 1 / time)
SI.set_quantity_scale_factor(planck_angular_frequency, 1 / planck_time)

SI.set_quantity_dimension(planck_pressure, pressure)
SI.set_quantity_scale_factor(planck_pressure, planck_force / planck_length**2)

SI.set_quantity_dimension(planck_current, current)
SI.set_quantity_scale_factor(planck_current, planck_charge / planck_time)

SI.set_quantity_dimension(planck_voltage, voltage)
SI.set_quantity_scale_factor(planck_voltage, planck_energy / planck_charge)

SI.set_quantity_dimension(planck_impedance, impedance)
SI.set_quantity_scale_factor(planck_impedance, planck_voltage / planck_current)

SI.set_quantity_dimension(planck_acceleration, acceleration)
SI.set_quantity_scale_factor(planck_acceleration, speed_of_light / planck_time)

# Older units for radioactivity

SI.set_quantity_dimension(curie, 1 / time)
SI.set_quantity_scale_factor(curie, 37000000000*becquerel)

SI.set_quantity_dimension(rutherford, 1 / time)
SI.set_quantity_scale_factor(rutherford, 1000000*becquerel)


# check that scale factors are the right SI dimensions:
for _scale_factor, _dimension in zip(
    SI._quantity_scale_factors.values(),
    SI._quantity_dimension_map.values()
):
    dimex = SI.get_dimensional_expr(_scale_factor)
    if dimex != 1:
        # XXX: equivalent_dims is an instance method taking two arguments in
        # addition to self so this can not work:
        if not DimensionSystem.equivalent_dims(_dimension, Dimension(dimex)):  # type: ignore
            raise ValueError("quantity value and dimension mismatch")
del _scale_factor, _dimension

__all__ = [
    'mmHg', 'atmosphere', 'inductance', 'newton', 'meter',
    'vacuum_permittivity', 'pascal', 'magnetic_constant', 'voltage',
    'angular_mil', 'luminous_intensity', 'all_units',
    'julian_year', 'weber', 'exbibyte', 'liter',
    'molar_gas_constant', 'faraday_constant', 'avogadro_constant',
    'lightyear', 'planck_density', 'gee', 'mol', 'bit', 'gray',
    'planck_momentum', 'bar', 'magnetic_density', 'prefix_unit', 'PREFIXES',
    'planck_time', 'dimex', 'gram', 'candela', 'force', 'planck_intensity',
    'energy', 'becquerel', 'planck_acceleration', 'speed_of_light',
    'conductance', 'frequency', 'coulomb_constant', 'degree', 'lux', 'planck',
    'current', 'planck_current', 'tebibyte', 'planck_power', 'MKSA', 'power',
    'K', 'planck_volume', 'quart', 'pressure', 'amount_of_substance',
    'joule', 'boltzmann_constant', 'Dimension', 'c', 'planck_force', 'length',
    'watt', 'action', 'hbar', 'gibibyte', 'DimensionSystem', 'cd', 'volt',
    'planck_charge', 'dioptre', 'vacuum_impedance', 'dimsys_default', 'farad',
    'charge', 'gravitational_constant', 'temperature', 'u0', 'hertz',
    'capacitance', 'tesla', 'steradian', 'planck_mass', 'josephson_constant',
    'planck_area', 'stefan_boltzmann_constant', 'base_dims',
    'astronomical_unit', 'radian', 'planck_voltage', 'impedance',
    'planck_energy', 'Da', 'atomic_mass_constant', 'rutherford', 'second', 'inch',
    'elementary_charge', 'SI', 'electronvolt', 'dimsys_SI', 'henry',
    'planck_angular_frequency', 'ohm', 'pound', 'planck_pressure', 'G', 'psi',
    'dHg0', 'von_klitzing_constant', 'planck_length', 'avogadro_number',
    'mole', 'acceleration', 'information', 'planck_energy_density',
    'mebibyte', 's', 'acceleration_due_to_gravity',
    'planck_temperature', 'units', 'mass', 'dimsys_MKSA', 'kelvin', 'kPa',
    'boltzmann', 'milli_mass_unit', 'planck_impedance', 'electric_constant',
    'derived_dims', 'kg', 'coulomb', 'siemens', 'byte', 'magnetic_flux',
    'atomic_mass_unit', 'm', 'kibibyte', 'kilogram', 'One', 'curie', 'u',
    'time', 'pebibyte', 'velocity', 'ampere', 'katal',
]
