import { units } from '../../units';
import * as u from './unitconvert_constants';

// !TODO add reduced Lennard-Jones units?

// NOTE: We assume a three-dimensional simulation here!
const DIM = 3.0;

const UNITSETS: { [key: string]: { [key: string]: number } } = {};

UNITSETS['ASE'] = {
    mass: 1.0 / units.kg,
    distance: 1.0 / units.m,
    time: 1.0 / units.second,
    energy: 1.0 / units.J,
    velocity: units.second / units.m,
    force: units.m / units.J,
    pressure: 1.0 / units.Pascal,
    charge: 1.0 / units.C,
};

UNITSETS['real'] = {
    mass: u.gram_per_mole_si,
    distance: u.angstrom_si,
    time: u.femtosecond_si,
    energy: u.kcal_per_mole_si,
    velocity: u.angstrom_per_femtosecond_si,
    force: u.kcal_per_mole_angstrom_si,
    torque: u.kcal_per_mole_si,
    temperature: u.kelvin_si,
    pressure: u.atmosphere_si,
    dynamic_viscosity: u.poise_si,
    charge: u.e_si,
    dipole: u.electron_angstrom_si,
    electric_field: u.volt_per_angstrom_si,
    density: u.gram_si / Math.pow(u.centimeter_si, DIM),
};

UNITSETS['metal'] = {
    mass: u.gram_per_mole_si,
    distance: u.angstrom_si,
    time: u.picosecond_si,
    energy: u.ev_si,
    velocity: u.angstrom_per_picosecond_si,
    force: u.ev_per_angstrom_si,
    torque: u.ev_si,
    temperature: u.kelvin_si,
    pressure: u.bar_si,
    dynamic_viscosity: u.poise_si,
    charge: u.e_si,
    dipole: u.electron_angstrom_si,
    electric_field: u.volt_per_angstrom_si,
    density: u.gram_si / Math.pow(u.centimeter_si, DIM),
};

UNITSETS['si'] = {
    mass: u.kilogram_si,
    distance: u.meter_si,
    time: u.second_si,
    energy: u.joule_si,
    velocity: u.meter_per_second_si,
    force: u.newton_si,
    torque: u.joule_si,
    temperature: u.kelvin_si,
    pressure: u.pascal_si,
    dynamic_viscosity: u.pascal_si * u.second_si,
    charge: u.coulomb_si,
    dipole: u.coulomb_meter_si,
    electric_field: u.volt_per_meter_si,
    density: u.kilogram_si / Math.pow(u.meter_si, DIM),
};

UNITSETS['cgs'] = {
    mass: u.gram_si,
    distance: u.centimeter_si,
    time: u.second_si,
    energy: u.erg_si,
    velocity: u.centimeter_per_second_si,
    force: u.dyne_si,
    torque: u.dyne_centimeter_si,
    temperature: u.kelvin_si,
    pressure: u.dyne_per_centimetersq_si, // or barye =u. 1.0e-6 bars
    dynamic_viscosity: u.poise_si,
    charge: u.statcoulomb_si, // or esu (4.8032044e-10 is a proton)
    dipole: u.statcoulomb_centimeter_si, // =u. 10^18 debye,
    electric_field: u.statvolt_per_centimeter_si, // or dyne / esu
    density: u.gram_si / Math.pow(u.centimeter_si, DIM),
};

UNITSETS['electron'] = {
    mass: u.amu_si,
    distance: u.bohr_si,
    time: u.femtosecond_si,
    energy: u.hartree_si,
    velocity: u.bohr_per_atu_si,
    force: u.hartree_per_bohr_si,
    temperature: u.kelvin_si,
    pressure: u.pascal_si,
    charge: u.e_si, // multiple of electron charge (1.0 is a proton)
    dipole: u.debye_si,
    electric_field: u.volt_per_centimeter_si,
};

UNITSETS['micro'] = {
    mass: u.picogram_si,
    distance: u.micrometer_si,
    time: u.microsecond_si,
    energy: u.picogram_micrometersq_per_microsecondsq_si,
    velocity: u.micrometer_per_microsecond_si,
    force: u.picogram_micrometer_per_microsecondsq_si,
    torque: u.picogram_micrometersq_per_microsecondsq_si,
    temperature: u.kelvin_si,
    pressure: u.picogram_per_micrometer_microsecondsq_si,
    dynamic_viscosity: u.picogram_per_micrometer_microsecond_si,
    charge: u.picocoulomb_si, // (1.6021765e-7 is a proton),
    dipole: u.picocoulomb_micrometer_si,
    electric_field: u.volt_per_micrometer_si,
    density: u.picogram_si / Math.pow(u.micrometer_si, DIM),
};

UNITSETS['nano'] = {
    mass: u.attogram_si,
    distance: u.nanometer_si,
    time: u.nanosecond_si,
    energy: u.attogram_nanometersq_per_nanosecondsq_si,
    velocity: u.nanometer_per_nanosecond_si,
    force: u.attogram_nanometer_per_nanosecondsq_si,
    torque: u.attogram_nanometersq_per_nanosecondsq_si,
    temperature: u.kelvin_si,
    pressure: u.attogram_per_nanometer_nanosecondsq_si,
    dynamic_viscosity: u.attogram_per_nanometer_nanosecond_si,
    charge: u.e_si, // multiple of electron charge (1.0 is a proton)
    dipole: u.electron_nanometer_si,
    electric_field: u.volt_si / u.nanometer_si,
    density: u.attogram_si / Math.pow(u.nanometer_si, DIM),
};

export function convert(value: number[] | number[][], quantity: string, fromunits: string, tounits: string) {
    /**
     * Convert units between LAMMPS and ASE.
     *
     * @param value: converted value
     * @param quantity: mass, distance, time, energy, velocity, force, torque,
     * temperature, pressure, dynamic_viscosity, charge, dipole,
     * electric_field or density
     * @param fromunits: ASE, metal, real or other (see lammps docs).
     * @param tounits: ASE, metal, real or other
     * @returns: converted value
     */
    const scaled = UNITSETS[fromunits][quantity] / UNITSETS[tounits][quantity];

    const toScaled = (v: number) => v * scaled;
    if (Array.isArray(value[0])) {
        return (value as number[][]).map(v => v.map(toScaled));
    }
    return (value as number[]).map(toScaled);
}
