##################################################################
##  (c) Copyright 2015-  by Jaron T. Krogel                     ##
##################################################################


#====================================================================#
#  unit_converter.py                                                 #
#    Support for physical unit conversion of scalars and arrays.     #
#    Several distance, time, mass, energy, charge, pressure, and     #
#    force units are supported.                                      #
#                                                                    #
#  Content summary:                                                  #
#    convert                                                         #
#      User-facing function to convert a scalar or array from one    #
#      unit system to another.                                       #
#                                                                    #
#    UnitConverter                                                   #
#      Class performs unit conversion.  Wrapped by convert function. #
#                                                                    #
#====================================================================#


from generic import obj
from developer import DevBase,error


class Unit(DevBase):

    unit_dicts = obj(all=obj())

    def __init__(self,type,name,symbol,value,shift=0):
        self.type   = type
        self.name   = name
        self.symbol = symbol
        self.value  = value
        self.shift  = shift

        ud = Unit.unit_dicts
        if type not in ud:
            ud[type] = obj()
        #end if
        td = ud[type]
        td[symbol] = self
        ud.all[symbol] = self
    #end def __init__
#end class Unit


class UnitConverter(DevBase):

    unassigned = None

    kb = 1.3806503e-23 #J/K

    # count
    mol = 6.0221415e23

    # distance
    m  = 1.e0
    A  = 1.e-10*m
    B  = .52917720859e-10*m
    nm = 1.e-9*m
    pm = 1.e-12*m
    fm = 1.e-15*m

    # time
    s = 1.e0
    ms = 1.e-3*s
    ns = 1.e-9*s
    ps = 1.e-12*s
    fs = 1.e-15*s

    # mass
    kg  = 1.e0
    me  = 9.10938291e-31*kg
    mp  = 1.672621777e-27*kg
    amu = 1.660538921e-27*kg
    Da  = amu

    # energy
    J      = 1.e0
    eV     = 1.60217646e-19*J
    Ry     = 13.6056923*eV
    Ha     = 2*Ry
    kJ_mol = 1000.*J/mol
    kcal_mol = 0.04336411531*eV
    K      = kb
    degC   = K
    degF   = 5./9.*K

    degC_shift = -273.15
    degF_shift = 9./5*degC_shift+32

    # charge
    C = 1.e0
    e = 1.60217646e-19*C

    # pressure
    Pa   = 1.e0
    bar  = 1.e5*Pa
    Mbar = 1.e6*bar
    GPa  = 1.e9*Pa
    atm  = 1.01325e5*Pa

    # force
    N  = 1.e0
    pN = 1e-12*N

    # thermal conductivity
    W_mK = 1.0


    meter            = Unit('distance'  ,'meter'           ,'m'       ,m              )
    angstrom         = Unit('distance'  ,'Angstrom'        ,'A'       ,A              )
    bohr             = Unit('distance'  ,'Bohr'            ,'B'       ,B              )
    nanometer        = Unit('distance'  ,'nanometer'       ,'nm'      ,nm             )
    picometer        = Unit('distance'  ,'picometer'       ,'pm'      ,pm             )
    femtometer       = Unit('distance'  ,'femtometer'      ,'pm'      ,fm             )
                                                                      
    second           = Unit('time'      ,'second'          ,'s'       ,s              )
    millisecond      = Unit('time'      ,'millisecond'     ,'ms'      ,ms             )
    nanosecond       = Unit('time'      ,'nanosecond'      ,'ns'      ,ns             )
    picosecond       = Unit('time'      ,'picosecond'      ,'ps'      ,ps             )
    femtosecond      = Unit('time'      ,'femtosecond'     ,'fs'      ,fs             )
                                                                      
    kilogram         = Unit('mass'      ,'kilogram'        ,'kg'      ,kg             )
    electron_mass    = Unit('mass'      ,'electron mass'   ,'me'      ,me             )
    proton_mass      = Unit('mass'      ,'proton mass'     ,'mp'      ,mp             )
    atomic_mass_unit = Unit('mass'      ,'atomic mass unit','amu'     ,amu            )
    dalton           = Unit('mass'      ,'Dalton'          ,'Da'      ,Da             )
        
    joule            = Unit('energy'    ,'Joule'           ,'J'       ,J              )
    electron_volt    = Unit('energy'    ,'electron Volt'   ,'eV'      ,eV             )
    rydberg          = Unit('energy'    ,'Rydberg'         ,'Ry'      ,Ry             )
    hartree          = Unit('energy'    ,'Hartree'         ,'Ha'      ,Ha             )
    kJ_mole          = Unit('energy'    ,'kJ_mole'         ,'kJ_mol'  ,kJ_mol         )
    kcal_mole        = Unit('energy'    ,'kcal_mole'       ,'kcal_mol',kcal_mol       )
    kelvin           = Unit('energy'    ,'Kelvin'          ,'K'       ,K              )
    celsius          = Unit('energy'    ,'Celsius'         ,'degC'    ,degC,degC_shift)
    fahrenheit       = Unit('energy'    ,'Fahrenheit'      ,'degF'    ,degF,degF_shift)
                                                           
    coulomb          = Unit('charge'    ,'Coulomb'         ,'C'       ,C              )
    electron_charge  = Unit('charge'    ,'electron charge' ,'e'       ,e              )

    pascal           = Unit('pressure'  ,'Pascal'          ,'Pa'      ,Pa             )
    bar              = Unit('pressure'  ,'bar'             ,'bar'     ,bar            )
    megabar          = Unit('pressure'  ,'megabar'         ,'Mbar'    ,Mbar           )
    gigapascal       = Unit('pressure'  ,'gigaPascal'      ,'Gpa'     ,GPa            )
    atmosphere       = Unit('pressure'  ,'atmosphere'      ,'atm'     ,atm            )
                                                                      
    newton           = Unit('force'     ,'Newton'          ,'N'       ,N              )
    piconewton       = Unit('force'     ,'picoNewton'      ,'pN'      ,pN             )
                                                                      
    W_per_mK         = Unit('therm_cond','W/(m*K)'         ,'W_mK'    ,W_mK           )


    distance_dict   = Unit.unit_dicts.distance
    mass_dict       = Unit.unit_dicts.mass
    energy_dict     = Unit.unit_dicts.energy
    charge_dict     = Unit.unit_dicts.charge
    pressure_dict   = Unit.unit_dicts.pressure
    force_dict      = Unit.unit_dicts.force
    therm_cond_dict = Unit.unit_dicts.therm_cond

    unit_dict = Unit.unit_dicts.all


    def __init__(self):
        self.error('UnitConverter should not be instantiated')
    #def __init__

    @staticmethod
    def convert(value,source_unit,target_unit):
        ui = UnitConverter.unit_dict[source_unit]
        uo = UnitConverter.unit_dict[target_unit]

        if(ui.type != uo.type):
            error('in UnitConverter.convert()\ntype conversion attempted between '+ui.type+' and '+uo.type)
        else:
            value_out = (value-ui.shift)*ui.value/uo.value+uo.shift
        #end if

        return (value_out,target_unit)
    #end def convert

    @staticmethod
    def convert_scalar_to_all(units,value_orig):
        unit_type = UnitConverter.unit_dict[units].type

        value = dict()
        value['orig'] = value_orig

        for k,u in UnitConverter.unit_dict.items():
            if(u.type == unit_type and u.value!=UnitConverter.unassigned):
                (value[k],utmp) = UnitConverter.convert(value['orig'],units,k)
            #end if
        #end for

        return value
    #end def convert_scalar_to_all
#end class UnitConverter


def convert(value,source_unit,target_unit):
    return UnitConverter.convert(value,source_unit,target_unit)[0]
#end def convert

