#include <stdlib.h>
#include <string.h>

#include "unit.h"


typedef struct Unit {

    unit_t unit;
    unit_category_t category;
    const char *symbol;
    const char *symbol_ascii;

} Unit;


//
// Static unit definitions. All supported units are defined here.
//

static const Unit __UNIT_RAW = { UNIT_RAW, UNIT_CATEGORY_UNKNOWN, "(raw)", "(raw)" };
static const Unit __UNIT_CELSIUS = { UNIT_CELSIUS, UNIT_CATEGORY_TEMPERATURE, "°C", "C" };
static const Unit __UNIT_FAHRENHEIT = { UNIT_FAHRENHEIT, UNIT_CATEGORY_TEMPERATURE, "°F", "F" };
static const Unit __UNIT_KELVIN = { UNIT_KELVIN, UNIT_CATEGORY_TEMPERATURE, "K", "K" };
static const Unit __UNIT_RH = { UNIT_RH, UNIT_CATEGORY_RELATIVE_HUMIDITY, "%", "%" };
static const Unit __UNIT_PA = { UNIT_PA, UNIT_CATEGORY_PRESSURE, "Pa", "Pa" };
static const Unit __UNIT_KPA = { UNIT_KPA, UNIT_CATEGORY_PRESSURE, "kPa", "kPa" };
static const Unit __UNIT_HPA = { UNIT_HPA, UNIT_CATEGORY_PRESSURE, "hPa", "hPa" };
static const Unit __UNIT_BAR = { UNIT_BAR, UNIT_CATEGORY_PRESSURE, "bar", "bar" };
static const Unit __UNIT_AT = { UNIT_AT, UNIT_CATEGORY_PRESSURE, "at", "at" };
static const Unit __UNIT_ATM = { UNIT_ATM, UNIT_CATEGORY_PRESSURE, "atm", "atm" };
static const Unit __UNIT_TORR = { UNIT_TORR, UNIT_CATEGORY_PRESSURE, "Torr", "Torr" };
static const Unit __UNIT_PSI = { UNIT_PSI, UNIT_CATEGORY_PRESSURE, "psi", "psi" };
static const Unit __UNIT_INHG = { UNIT_INHG, UNIT_CATEGORY_PRESSURE, "inHg", "inHg" };
static const Unit __UNIT_PERCENT = { UNIT_PERCENT, UNIT_CATEGORY_CONCENTRATION, "%", "%" };
static const Unit __UNIT_PPM = { UNIT_PPM, UNIT_CATEGORY_CONCENTRATION, "ppm", "ppm" };
static const Unit __UNIT_PPB = { UNIT_PPB, UNIT_CATEGORY_CONCENTRATION, "ppb", "ppb" };
static const Unit __UNIT_uG_PER_M3 = { UNIT_uG_PER_M3, UNIT_CATEGORY_UNKNOWN, "μg/m³", "ug/m^3" };
static const Unit __UNIT_COUNT_PER_CM3 = { UNIT_COUNT_PER_CM3, UNIT_CATEGORY_UNKNOWN, "#/cm³", "#/cm^3" };
static const Unit __UNIT_VOLT = { UNIT_VOLT, UNIT_CATEGORY_VOLTAGE, "V", "V" };
static const Unit __UNIT_MILLIVOLT = { UNIT_MILLIVOLT, UNIT_CATEGORY_VOLTAGE, "mV", "mV" };
static const Unit __UNIT_AMP = { UNIT_AMP, UNIT_CATEGORY_CURRENT, "A", "A" };
static const Unit __UNIT_MILLIAMP = { UNIT_MILLIAMP, UNIT_CATEGORY_CURRENT, "mA", "mA" };
static const Unit __UNIT_KILOWATT = { UNIT_KILOWATT, UNIT_CATEGORY_POWER, "kW", "kW" };
static const Unit __UNIT_WATT = { UNIT_WATT, UNIT_CATEGORY_POWER, "W", "W" };
static const Unit __UNIT_MILLIWATT = { UNIT_MILLIWATT, UNIT_CATEGORY_POWER, "mW", "mW" };
static const Unit __UNIT_LUX = { UNIT_LUX, UNIT_CATEGORY_UNKNOWN, "lx", "lx" };
static const Unit __UNIT_uW_PER_CM2 = { UNIT_uW_PER_CM2, UNIT_CATEGORY_UNKNOWN, "μW/cm²", "uW/cm^2" };
static const Unit __UNIT_METER_SEC = { UNIT_METER_SEC, UNIT_CATEGORY_UNKNOWN, "m/sec", "m/sec" };
static const Unit __UNIT_MILLIHZ = { UNIT_MILLIHZ, UNIT_CATEGORY_FREQUENCY, "mHz", "mHz" };
static const Unit __UNIT_HZ = { UNIT_HZ, UNIT_CATEGORY_FREQUENCY, "Hz", "Hz" };
static const Unit __UNIT_KHZ = { UNIT_KHZ, UNIT_CATEGORY_FREQUENCY, "kHz", "kHz" };
static const Unit __UNIT_MHZ = { UNIT_MHZ, UNIT_CATEGORY_FREQUENCY, "MHz", "MHz" };
static const Unit __UNIT_RPM = { UNIT_RPM, UNIT_CATEGORY_FREQUENCY, "rpm", "rpm" };
static const Unit __UNIT_METER = { UNIT_METER, UNIT_CATEGORY_LENGTH, "m", "m" };
static const Unit __UNIT_DECIMETER = { UNIT_DECIMETER, UNIT_CATEGORY_LENGTH, "dm", "dm" };
static const Unit __UNIT_CENTIMETER = { UNIT_CENTIMETER, UNIT_CATEGORY_LENGTH, "cm", "cm" };
static const Unit __UNIT_MILLIMETER = { UNIT_MILLIMETER, UNIT_CATEGORY_LENGTH, "mm", "mm" };
static const Unit __UNIT_MICROMETER = { UNIT_MICROMETER, UNIT_CATEGORY_UNKNOWN, "μm", "um" };
static const Unit __UNIT_MIL = { UNIT_MIL, UNIT_CATEGORY_LENGTH, "mil", "mil" };
static const Unit __UNIT_INCH = { UNIT_INCH, UNIT_CATEGORY_LENGTH, "in", "in" };
static const Unit __UNIT_FEET = { UNIT_FEET, UNIT_CATEGORY_LENGTH, "ft", "ft" };
static const Unit __UNIT_YARD = { UNIT_YARD, UNIT_CATEGORY_LENGTH, "yd", "yd" };
static const Unit __UNIT_ARBITRARY = { UNIT_ARBITRARY, UNIT_CATEGORY_UNKNOWN, "arb.unit", "arb.unit" };
static const Unit __UNIT_HEXCOLOR = { UNIT_HEXCOLOR, UNIT_CATEGORY_UNKNOWN, "?", "?" };
static const Unit __UNIT_UNKNOWN = { UNIT_UNKNOWN, UNIT_CATEGORY_UNKNOWN, "?", "?" };
static const Unit __UNIT_SENSOR_DEFAULT = { UNIT_SENSOR_DEFAULT, UNIT_CATEGORY_UNKNOWN, "?", "?" };

static const Unit *UNITS[256] = {

    // Sparse array, but that's OK.
    // The whole thing only takes 2 kB on 64-bit architectures, or 1 kB on 32-bit architectures.

    [UNIT_RAW] = &__UNIT_RAW,
    [UNIT_CELSIUS] = &__UNIT_CELSIUS,
    [UNIT_FAHRENHEIT] = &__UNIT_FAHRENHEIT,
    [UNIT_KELVIN] = &__UNIT_KELVIN,
    [UNIT_RH] = &__UNIT_RH,
    [UNIT_PA] = &__UNIT_PA,
    [UNIT_KPA] = &__UNIT_KPA,
    [UNIT_HPA] = &__UNIT_HPA,
    [UNIT_BAR] = &__UNIT_BAR,
    [UNIT_AT] = &__UNIT_AT,
    [UNIT_ATM] = &__UNIT_ATM,
    [UNIT_TORR] = &__UNIT_TORR,
    [UNIT_PSI] = &__UNIT_PSI,
    [UNIT_INHG] = &__UNIT_INHG,
    [UNIT_PERCENT] = &__UNIT_PERCENT,
    [UNIT_PPM] = &__UNIT_PPM,
    [UNIT_PPB] = &__UNIT_PPB,
    [UNIT_uG_PER_M3] = &__UNIT_uG_PER_M3,
    [UNIT_COUNT_PER_CM3] = &__UNIT_COUNT_PER_CM3,
    [UNIT_VOLT] = &__UNIT_VOLT,
    [UNIT_MILLIVOLT] = &__UNIT_MILLIVOLT,
    [UNIT_AMP] = &__UNIT_AMP,
    [UNIT_MILLIAMP] = &__UNIT_MILLIAMP,
    [UNIT_KILOWATT] = &__UNIT_KILOWATT,
    [UNIT_WATT] = &__UNIT_WATT,
    [UNIT_MILLIWATT] = &__UNIT_MILLIWATT,
    [UNIT_LUX] = &__UNIT_LUX,
    [UNIT_uW_PER_CM2] = &__UNIT_uW_PER_CM2,
    [UNIT_METER_SEC] = &__UNIT_METER_SEC,
    [UNIT_MILLIHZ] = &__UNIT_MILLIHZ,
    [UNIT_HZ] = &__UNIT_HZ,
    [UNIT_KHZ] = &__UNIT_KHZ,
    [UNIT_MHZ] = &__UNIT_MHZ,
    [UNIT_RPM] = &__UNIT_RPM,
    [UNIT_METER] = &__UNIT_METER,
    [UNIT_DECIMETER] = &__UNIT_DECIMETER,
    [UNIT_CENTIMETER] = &__UNIT_CENTIMETER,
    [UNIT_MILLIMETER] = &__UNIT_MILLIMETER,
    [UNIT_MICROMETER] = &__UNIT_MICROMETER,
    [UNIT_MIL] = &__UNIT_MIL,
    [UNIT_INCH] = &__UNIT_INCH,
    [UNIT_FEET] = &__UNIT_FEET,
    [UNIT_YARD] = &__UNIT_YARD,
    [UNIT_ARBITRARY] = &__UNIT_ARBITRARY,
    [UNIT_HEXCOLOR] = &__UNIT_HEXCOLOR,
    [UNIT_UNKNOWN] = &__UNIT_UNKNOWN,
    [UNIT_SENSOR_DEFAULT] = &__UNIT_SENSOR_DEFAULT,

};


//
// Units by category, for parsing.
// Include some alternate names for more flexible matching.
//

static const Unit __UNIT_CELSIUS_ALT = { UNIT_CELSIUS, UNIT_CATEGORY_TEMPERATURE, "Celsius", "Celsius" };
static const Unit __UNIT_FAHRENHEIT_ALT = { UNIT_FAHRENHEIT, UNIT_CATEGORY_TEMPERATURE, "Fahrenheit", "Fahrenheit" };
static const Unit __UNIT_KELVIN_ALT = { UNIT_KELVIN, UNIT_CATEGORY_TEMPERATURE, "Kelvin", "Kelvin" };
static const Unit __UNIT_PERCENT_ALT = { UNIT_PERCENT, UNIT_CATEGORY_CONCENTRATION, "percent", "percent" };

static const Unit *UNITS_BY_CATEGORY[][UNIT_CATEGORIES] = {

    [UNIT_CATEGORY_UNKNOWN] = {
        NULL
    },

    [UNIT_CATEGORY_TEMPERATURE] = {
        &__UNIT_CELSIUS,
        &__UNIT_FAHRENHEIT,
        &__UNIT_KELVIN,
        &__UNIT_CELSIUS_ALT,
        &__UNIT_FAHRENHEIT_ALT,
        &__UNIT_KELVIN_ALT,
        NULL
    },

    [UNIT_CATEGORY_PRESSURE] = {
        &__UNIT_PA,
        &__UNIT_KPA,
        &__UNIT_HPA,
        &__UNIT_BAR,
        &__UNIT_AT,
        &__UNIT_ATM,
        &__UNIT_TORR,
        &__UNIT_PSI,
        &__UNIT_INHG,
        NULL
    },

    [UNIT_CATEGORY_FREQUENCY] = {
        &__UNIT_MILLIHZ,
        &__UNIT_HZ,
        &__UNIT_KHZ,
        &__UNIT_MHZ,
        &__UNIT_RPM,
        NULL
    },

    [UNIT_CATEGORY_VOLTAGE] = {
        &__UNIT_VOLT,
        &__UNIT_MILLIVOLT,
        NULL
    },

    [UNIT_CATEGORY_CURRENT] = {
        &__UNIT_AMP,
        &__UNIT_MILLIAMP,
        NULL
    },

    [UNIT_CATEGORY_POWER] = {
        &__UNIT_KILOWATT,
        &__UNIT_WATT,
        &__UNIT_MILLIWATT,
        NULL
    },

    [UNIT_CATEGORY_LENGTH] = {
        &__UNIT_METER,
        &__UNIT_DECIMETER,
        &__UNIT_CENTIMETER,
        &__UNIT_MILLIMETER,
        &__UNIT_MIL,
        &__UNIT_INCH,
        &__UNIT_FEET,
        &__UNIT_YARD,
        NULL
    },

    [UNIT_CATEGORY_CONCENTRATION] = {
        &__UNIT_PERCENT,
        &__UNIT_PERCENT_ALT,
        &__UNIT_PPM,
        &__UNIT_PPB,
        NULL
    },

    [UNIT_CATEGORY_RELATIVE_HUMIDITY] = {
        &__UNIT_RH,
        NULL
    },

};


//
// Public functions
//

unit_category_t unit_category(unit_t unit) {

    const Unit *u = UNITS[unit];

    return u ? u->category : UNIT_CATEGORY_UNKNOWN;

}

const char *unit_to_string(unit_t unit, uint8_t ascii) {

    const Unit *u = UNITS[unit];

    if (!u) {
        u = &__UNIT_UNKNOWN;
    }

    return ascii ? u->symbol_ascii : u->symbol;

}

static unit_t __unit_parse(unit_category_t cat, const char *s) {

    const Unit **units = UNITS_BY_CATEGORY[cat];

    for (const Unit **p = units; *p != NULL; p++) {
        if (strcmp(s, (*p)->symbol_ascii) == 0) {
            return (*p)->unit;
        }
    }

    // No exact matches. Try case-insensitive matching.

    for (const Unit **p = units; *p != NULL; p++) {
        if (strcasecmp(s, (*p)->symbol_ascii) == 0) {
            return (*p)->unit;
        }
    }

    return UNIT_UNKNOWN;

}

unit_t unit_parse(unit_category_t cat, const char *s) {

    if (cat >= UNIT_CATEGORIES) {
        cat = UNIT_CATEGORY_UNKNOWN;
    }

    if (cat == UNIT_CATEGORY_UNKNOWN) {

        cat++;

        do {
            unit_t unit = __unit_parse(cat++, s);
            if (unit != UNIT_UNKNOWN) {
                return unit;
            }
        } while (cat < UNIT_CATEGORIES);

        return UNIT_UNKNOWN;

    }

    return __unit_parse(cat, s);

}


//
// Unit conversion functions, one per category
//

static double unit_convert_nothing(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_temperature(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_pressure(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_frequency(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_voltage(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_current(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_power(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_length(double value, unit_t src_unit, unit_t dst_unit);
static double unit_convert_concentration(double value, unit_t src_unit, unit_t dst_unit);

const unit_conversion_fn_t UNIT_CONVERSION_FUNCTIONS[UNIT_CATEGORIES] = {

    [UNIT_CATEGORY_UNKNOWN] = unit_convert_nothing,
    [UNIT_CATEGORY_TEMPERATURE] = unit_convert_temperature,
    [UNIT_CATEGORY_PRESSURE] = unit_convert_pressure,
    [UNIT_CATEGORY_FREQUENCY] = unit_convert_frequency,
    [UNIT_CATEGORY_VOLTAGE] = unit_convert_voltage,
    [UNIT_CATEGORY_CURRENT] = unit_convert_current,
    [UNIT_CATEGORY_POWER] = unit_convert_power,
    [UNIT_CATEGORY_LENGTH] = unit_convert_length,
    [UNIT_CATEGORY_CONCENTRATION] = unit_convert_concentration,
    [UNIT_CATEGORY_RELATIVE_HUMIDITY] = unit_convert_nothing,

};

int unit_convert(double *value, unit_t src_unit, unit_t dst_unit) {

    if (src_unit == dst_unit || dst_unit == UNIT_SENSOR_DEFAULT) {
        return 0;
    }

    unit_category_t src_cat = unit_category(src_unit);
    unit_category_t dst_cat = unit_category(dst_unit);

    if (src_cat != dst_cat) {
        return 1;
    }

    *value = UNIT_CONVERSION_FUNCTIONS[src_cat](*value, src_unit, dst_unit);

    return 0;

}

static double unit_convert_nothing(double value, unit_t src_unit, unit_t dst_unit) {

    return value;

}

static double unit_convert_temperature(double temperature, unit_t src_unit, unit_t dst_unit) {

    switch (src_unit)
    {
        case UNIT_CELSIUS:
            switch(dst_unit)
            {
                case UNIT_FAHRENHEIT:
                    temperature = (temperature * 1.8) + 32.0;
                    break;
                case UNIT_KELVIN:
                    temperature = temperature + 273.15;
                    break;
            }
            break;

        case UNIT_FAHRENHEIT:
            switch(dst_unit)
            {
                case UNIT_CELSIUS:
                    temperature = (temperature - 32.0) / 1.8;
                    break;
                case UNIT_KELVIN:
                    temperature = (temperature + 459.67) / 1.8;
                    break;
            }
            break;

        case UNIT_KELVIN:
            switch(dst_unit)
            {
                case UNIT_CELSIUS:
                    temperature = temperature - 273.15;
                    break;

                case UNIT_FAHRENHEIT:
                    temperature = (temperature * 1.8) - 459.67;
                    break;
            }
            break;
    }

    return temperature;

}

static double unit_convert_pressure(double pressure, unit_t src_unit, unit_t dst_unit)
{
    double pascals;

    if (src_unit == dst_unit)
        return pressure;

    switch (src_unit)
    {
        case UNIT_PA:
            pascals = pressure;
            break;

        case UNIT_KPA:
            pascals = pressure * 1000.0;
            break;

        case UNIT_HPA:
            pascals = pressure * 100.0;
            break;

        case UNIT_BAR:
            pascals = pressure * 100000.0;
            break;

        case UNIT_AT:
            pascals = pressure * 98066.5;
            break;

        case UNIT_ATM:
            pascals = pressure * 101325;
            break;

        case UNIT_TORR:
            pascals = pressure * 133.322;
            break;

        case UNIT_PSI:
            pascals = pressure * 6894.76;
            break;

        case UNIT_INHG:
            pascals = pressure * 3386.389;
            break;

        default:
            return pressure;
    }

    switch (dst_unit)
    {
        case UNIT_PA: return pascals;
        case UNIT_KPA: return pascals / 1000.0;
        case UNIT_HPA: return pascals / 100.0;
        case UNIT_BAR: return pascals / 100000.0;
        case UNIT_AT: return pascals / 98066.5;
        case UNIT_ATM: return pascals / 101325.0;
        case UNIT_TORR: return pascals / 133.322;
        case UNIT_PSI: return pascals / 6894.76;
        case UNIT_INHG: return pascals / 3386.389;
    }

    return pressure;
}

static double unit_convert_frequency(double freq, unit_t src_unit, unit_t dst_unit)
{
    double hz;

    if (src_unit == dst_unit)
        return freq;

    switch (src_unit)
    {
        case UNIT_MILLIHZ:
            hz = freq / 1000;
            break;
        case UNIT_HZ:
            hz = freq;
            break;
        case UNIT_KHZ:
            hz = freq * 1000;
            break;
        case UNIT_MHZ:
            hz = freq * 1000000;
            break;
        case UNIT_RPM:
            hz = freq / 60;
            break;
        default:
            return freq;
    }

    switch (dst_unit)
    {
        case UNIT_MILLIHZ:
            return hz * 1000;
        case UNIT_HZ:
            return hz;
        case UNIT_KHZ:
            return hz / 1000;
        case UNIT_MHZ:
            return hz / 1000000;
        case UNIT_RPM:
            return hz * 60;
    }

    return freq;
}

static double unit_convert_voltage(double v, unit_t src_unit, unit_t dst_unit)
{
    switch (src_unit) {
        case UNIT_VOLT:
            switch (dst_unit)
            {
                case UNIT_MILLIVOLT:
                    v = v * 1000;
                    break;
            }
            break;
        case UNIT_MILLIVOLT:
            switch (dst_unit)
            {
                case UNIT_VOLT:
                    v = v / 1000;
                    break;
            }
            break;
    }

    return v;
}

static double unit_convert_current(double c, unit_t src_unit, unit_t dst_unit)
{
    switch (src_unit)
    {
        case UNIT_AMP:
            switch (dst_unit)
            {
                case UNIT_MILLIAMP:
                    c = c * 1000;
                    break;
            }
            break;

        case UNIT_MILLIAMP:
            switch (dst_unit)
            {
                case UNIT_AMP:
                    c = c / 1000;
                    break;
            }
            break;
    }

    return c;
}

static double unit_convert_power(double p, unit_t src_unit, unit_t dst_unit)
{
    switch (src_unit)
    {
        case UNIT_KILOWATT:
            switch (dst_unit)
            {
                case UNIT_WATT:
                    p = p * 1000;
                    break;

                case UNIT_MILLIWATT:
                    p = p * (1000 * 1000);
                    break;
            }
            break;

        case UNIT_WATT:
            switch (dst_unit)
            {
                case UNIT_KILOWATT:
                    p = p / 1000;
                    break;
                case UNIT_MILLIWATT:
                    p = p * 1000;
                    break;
            }
            break;

        case UNIT_MILLIWATT:
            switch (dst_unit)
            {
                case UNIT_KILOWATT:
                    p = p / (1000 * 1000);
                    break;
                case UNIT_WATT:
                    p = p / 1000;
                    break;
            }
            break;
    }

    return p;
}


static double unit_convert_length(double length, unit_t src_unit, unit_t dst_unit)
{
    double converted = length;
    double meters = 0;

    // First convert to meters
    switch (src_unit)
    {
        case UNIT_METER:
            meters = length;
            break;
        case UNIT_DECIMETER:
            meters = length / 10;
            break;
        case UNIT_CENTIMETER:
            meters = length / 100;
            break;
        case UNIT_MILLIMETER:
            meters = length / 1000;
            break;
        case UNIT_MICROMETER:
            meters = length / 1000000;
            break;

        case UNIT_INCH:
            // 1 inch = 25.4mm = 2.54cm = 0.254dm = 0.0254m
            meters = length * 0.0254;
            break;

        case UNIT_FEET:
            meters = length * 12 * 0.0254;
            break;

        case UNIT_YARD:
            meters = length * 3 * 12 * 0.0254;
            break;

        case UNIT_MIL:
            meters = (length / 1000) * 0.0254;
            break;
    }

    // Now convert meters to target unit
    switch (dst_unit)
    {
        case UNIT_METER:
            converted = meters;
            break;

        case UNIT_DECIMETER:
            converted = meters * 10;
            break;

        case UNIT_CENTIMETER:
            converted = meters * 100;
            break;

        case UNIT_MILLIMETER:
            converted = meters * 1000;
            break;

        case UNIT_MICROMETER:
            converted = meters * 1000000;
            break;

        case UNIT_INCH:
            converted = meters / 0.0254;
            break;

        case UNIT_MIL:
            converted = meters / 0.0254 * 1000;
            break;

        case UNIT_FEET:
            converted = meters / 0.0254 / 12;
            break;

        case UNIT_YARD:
            converted = meters / 0.0254 / 12 / 3;
            break;
    }

    return converted;
}

static double unit_convert_concentration(double c, unit_t src_unit, unit_t dst_unit)
{
    double ppb;
    double out;

    // If no conversion is needed due to identical units, return the value as-is.
    // If no conversion is needed since native sensor units are requested, return the value as-is.
    if ((src_unit == dst_unit) || (dst_unit == UNIT_SENSOR_DEFAULT ) ) {
        return c;
    }

    switch (src_unit)
    {
        case UNIT_PPB:
            ppb = c;
            break;
        case UNIT_PPM:
            ppb = c * 1000;
            break;
        case UNIT_PERCENT:
            ppb = c * 1e9 / 100;
            break;
        default:
            return c;
    }

    switch (dst_unit)
    {
        case UNIT_PPB:
            out = ppb;
            break;
        case UNIT_PPM:
            out = ppb / 1000;
            break;
        case UNIT_PERCENT:
            out = ppb * 100 / 1e9;
            break;
        default:
            return c;
    }

    return out;
}
