Pith - grain
package com.vgmlr.grain

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CropFree
import androidx.compose.material.icons.filled.ElectricBolt
import androidx.compose.material.icons.filled.FitnessCenter
import androidx.compose.material.icons.outlined.Opacity
import androidx.compose.material.icons.outlined.Speed
import androidx.compose.material.icons.outlined.Straighten
import androidx.compose.material.icons.outlined.Thermostat
import androidx.compose.material.icons.outlined.TireRepair
import androidx.compose.ui.graphics.vector.ImageVector
import kotlin.math.PI

private const val AU_M = 149_597_870_700.0
private const val FT_M = 0.3048
private const val LB_KG = 0.45359237
private const val G_N = 9.80665

class MUnit(
    val name: String,
    val abbr: String,
    val toBase: (Double) -> Double,
    val fromBase: (Double) -> Double
)

class Category(
    val title: String,
    val icon: ImageVector,
    val units: List<MUnit>,
    a: String,
    b: String
) {
    val defA = units.indexOfFirst { it.abbr == a }.coerceAtLeast(0)
    val defB = units.indexOfFirst { it.abbr == b }.coerceAtLeast(0)
}

private fun lin(name: String, abbr: String, f: Double) =
    MUnit(name, abbr, { it * f }, { it / f })

// base m
private val LENGTH = listOf(
    lin("Angstrom", "Å", 1e-10),
    lin("Astronomical Unit", "au", AU_M),
    lin("Centimeter", "cm", 1e-2),
    lin("Fathom", "ftm", 1.8288),
    lin("Foot", "ft", FT_M),
    lin("Inch", "in", 0.0254),
    lin("Kilometer", "km", 1e3),
    lin("Light Year", "ly", 9_460_730_472_580_800.0),
    lin("Meter", "m", 1.0),
    lin("Micrometer", "um", 1e-6),
    lin("Mile", "mi", 1609.344),
    lin("Millimeter", "mm", 1e-3),
    lin("Nanometer", "nm", 1e-9),
    lin("Nautical Mile", "NM", 1852.0),
    lin("Parsec", "pc", AU_M * 648_000 / PI),
    lin("Yard", "yd", 0.9144)
)

// base m²
private val AREA = listOf(
    lin("Acre", "ac", 4046.8564224),
    lin("Centimeter²", "cm²", 1e-4),
    lin("Foot²", "ft²", 0.09290304),
    lin("Hectare", "ha", 1e4),
    lin("Inch²", "in²", 6.4516e-4),
    lin("Kilometer²", "km²", 1e6),
    lin("Meter²", "m²", 1.0),
    lin("Mile²", "mi²", 2_589_988.110336),
    lin("Millimeter²", "mm²", 1e-6),
    lin("Yard²", "yd²", 0.83612736)
)

// base m³
private val VOLUME = listOf(
    lin("Acre Foot", "ac ft", 1233.48183754752),
    lin("Barrel", "fl bl", 0.119240471196),
    lin("Centiliter", "cL", 1e-5),
    lin("Centimeter³", "cm³", 1e-6),
    lin("Cup", "cup", 2.365882365e-4),
    lin("Deciliter", "dL", 1e-4),
    lin("Decimeter³", "dm³", 1e-3),
    lin("Fluid Ounce", "fl oz", 2.95735295625e-5),
    lin("Foot³", "ft³", 0.028316846592),
    lin("Gallon", "gal", 3.785411784e-3),
    lin("Gill", "gi", 1.1829411825e-4),
    lin("Inch³", "in³", 1.6387064e-5),
    lin("Liter", "L", 1e-3),
    lin("Meter³", "m³", 1.0),
    lin("Milliliter", "mL", 1e-6),
    lin("Millimeter³", "mm³", 1e-9),
    lin("Pint", "pt", 4.73176473e-4),
    lin("Quart", "qt", 9.46352946e-4)
)

// base kg
private val MASS = listOf(
    lin("Carat Metric", "ct", 2e-4),
    lin("Dram", "dr", 1.7718451953125e-3),
    lin("Grain", "gr", 6.479891e-5),
    lin("Gram", "g", 1e-3),
    lin("Kilogram", "kg", 1.0),
    lin("Long Ton", "ton", 1016.0469088),
    lin("Microgram", "µg", 1e-9),
    lin("Milligram", "mg", 1e-6),
    lin("Ounce", "oz", 0.028349523125),
    lin("Pound", "lb", LB_KG),
    lin("Short Ton", "sh tn", 907.18474),
    lin("Stone", "st", 6.35029318),
    lin("Tonne", "t", 1e3)
)

// base K
private val TEMPERATURE = listOf(
    MUnit("Celsius", "C", { it + 273.15 }, { it - 273.15 }),
    MUnit("Fahrenheit", "F", { (it + 459.67) * 5 / 9 }, { it * 9 / 5 - 459.67 }),
    MUnit("Kelvin", "K", { it }, { it }),
    MUnit("Rankine", "R", { it * 5 / 9 }, { it * 9 / 5 })
)

// base m/s
private val VELOCITY = listOf(
    lin("Foot/Second", "fts", 0.3048),
    lin("Kilometer/Second", "kms", 1e3),
    lin("Kilometer/Hour", "kmh", 1 / 3.6),
    lin("Knot", "kn", 1852.0 / 3600),
    lin("Light", "c", 299_792_458.0),
    lin("Mach", "Ma", 340.29),
    lin("Meter/Second", "ms", 1.0),
    lin("Mile/Hour", "mph", 0.44704)
)

// base Pa
private val PRESSURE = listOf(
    lin("Atmosphere", "atm", 101_325.0),
    lin("Bar", "bar", 1e5),
    lin("Inch Mercury", "inHg", 3386.389),
    lin("Kilopascal", "kPa", 1e3),
    lin("Megapascal", "MPa", 1e6),
    lin("Millibar", "mbar", 100.0),
    lin("Millimeter Mercury", "mmHg", 133.322387415),
    lin("Pascal", "Pa", 1.0),
    lin("Pound/Inch²", "psi", 6894.757293168),
    lin("Torr", "Torr", 101_325.0 / 760)
)

// base J
private val ENERGY = listOf(
    lin("British Thermal Unit", "BTU", 1055.05585262),
    lin("Calorie", "cal", 4.184),
    lin("Electronvolt", "eV", 1.602176634e-19),
    lin("Erg", "erg", 1e-7),
    lin("Foot-Pound", "ft-lbf", FT_M * LB_KG * G_N),
    lin("Gigajoule", "GJ", 1e9),
    lin("Joule", "J", 1.0),
    lin("Kilocalorie", "kcal", 4184.0),
    lin("Kilojoule", "kJ", 1e3),
    lin("Kilowatt Hour", "kWh", 3.6e6),
    lin("Megajoule", "MJ", 1e6),
    lin("Megawatt Hour", "MWh", 3.6e9),
    lin("Therm", "thm", 105_480_400.0),
    lin("Watt Hour", "Wh", 3600.0)
)

internal val CATEGORIES = listOf(
    Category("Length", Icons.Outlined.Straighten, LENGTH, "mm", "in"),
    Category("Area", Icons.Filled.CropFree, AREA, "km²", "mi²"),
    Category("Volume", Icons.Outlined.Opacity, VOLUME, "L", "gal"),
    Category("Mass", Icons.Filled.FitnessCenter, MASS, "g", "oz"),
    Category("Temperature", Icons.Outlined.Thermostat, TEMPERATURE, "C", "F"),
    Category("Velocity", Icons.Outlined.Speed, VELOCITY, "kmh", "mph"),
    Category("Pressure", Icons.Outlined.TireRepair, PRESSURE, "mmHg", "mbar"),
    Category("Energy", Icons.Filled.ElectricBolt, ENERGY, "MWh", "cal")
)