#include <stdlib.h>
#include <math.h>

#include "virtual.h"
#include "chip.h"


#ifndef ARRAY_SIZE
    #define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
#endif


typedef struct Definition {

    const uint16_t *chip_ids;
    size_t len;
    Quantity *parent;

} Definition;


static char find_chip_id(uint16_t chip_id, const uint16_t array[], size_t len) {

    for (const uint16_t *p = array; p < array + len; p++) {
        if (*p == chip_id) {
            return 1;
        }
    }

    return 0;

}

static VirtualChannel *add_virtual_channel(List *virtual_channels, List *channels, VirtualOptions *opt, Definition *defs, size_t n) {

    size_t found = 0;

    LIST_FOR(channels) {

        Channel *c = LIST_CUR(Channel);
        uint16_t chip_id = c->chip_id;

        for (Definition *def = defs; def < defs + n; def++) {
            if (def->parent == NULL && find_chip_id(chip_id, def->chip_ids, def->len)) {
                def->parent = &c->quantity;
                found++;
                break;
            }
        }

        if (found >= n) {

            VirtualChannel *vc = malloc(sizeof(VirtualChannel));

            vc->channel.quantity = QUANTITY_NO_DATA;
            vc->opt = opt;
            vc->parents = malloc(n * sizeof(Quantity*));

            for (size_t i = 0; i < n; i++) {
                vc->parents[i] = defs[i].parent;
            }

            list_add(virtual_channels, vc);
            return vc;

        }

    }

    return NULL;

}

#define _VIRTUAL_CHANNEL_ADD(name) \
    static void add_ ## name (List *virtual_channels, List *channels, VirtualOptions *opt)

_VIRTUAL_CHANNEL_ADD(tsl2568_lux) {

    static const uint16_t vir_chips[] = {USBTENKI_CHIP_TSL2568_IR_VISIBLE};
    static const uint16_t ir_chips[] = {USBTENKI_CHIP_TSL2568_IR};
    static const uint16_t vir_16_chips[] = {USBTENKI_CHIP_TSL2568_IR_VISIBLE_16X};
    static const uint16_t ir_16_chips[] = {USBTENKI_CHIP_TSL2568_IR_16X};

    Definition defs[] = {
        {vir_chips, ARRAY_SIZE(vir_chips)},
        {ir_chips, ARRAY_SIZE(ir_chips)},
        {vir_16_chips, ARRAY_SIZE(vir_chips)},
        {ir_16_chips, ARRAY_SIZE(ir_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_TSL2568_LUX;
        vc->channel.quantity.unit = UNIT_LUX;
        vc->fn = virtual_tsl2568_lux;
    }

}

_VIRTUAL_CHANNEL_ADD(tsl2561_lux) {

    static const uint16_t vir_chips[] = {USBTENKI_CHIP_TSL2561_IR_VISIBLE};
    static const uint16_t ir_chips[] = {USBTENKI_CHIP_TSL2561_IR};

    Definition defs[] = {
        {vir_chips, ARRAY_SIZE(vir_chips)},
        {ir_chips, ARRAY_SIZE(ir_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_TSL2561_LUX;
        vc->channel.quantity.unit = UNIT_LUX;
        vc->fn = virtual_tsl2561_lux;
    }

}

#if 0
_VIRTUAL_CHANNEL_ADD(sht75_compensated_relative_humidity) {

    static const uint16_t t_chips[] = {USBTENKI_CHIP_SHT_TEMP};
    static const uint16_t rh_chips[] = {USBTENKI_CHIP_SHT_RH};

    Definition defs[] = {
        {t_chips, ARRAY_SIZE(t_chips)},
        {rh_chips, ARRAY_SIZE(rh_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_SHT75_COMPENSATED_RH;
        vc->channel.quantity.unit = UNIT_RH;
        vc->fn = virtual_sht75_compensated_relative_humidity;
    }

}
#endif

_VIRTUAL_CHANNEL_ADD(dew_point_internal) {

    static const uint16_t t_chips[] = {USBTENKI_CHIP_SHT31_T_INTERNAL};
    static const uint16_t rh_chips[] = {USBTENKI_CHIP_SHT31_RH_INTERNAL};

    Definition defs[] = {
        {t_chips, ARRAY_SIZE(t_chips)},
        {rh_chips, ARRAY_SIZE(rh_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_INTERNAL_DEW_POINT;
        vc->channel.quantity.unit = UNIT_CELSIUS;
        vc->fn = virtual_dew_point;
    }

}

_VIRTUAL_CHANNEL_ADD(dew_point) {

    static const uint16_t t_chips[] = {USBTENKI_CHIP_SHT_TEMP, USBTENKI_CHIP_BS02_TEMP, USBTENKI_CHIP_CC2_T, USBTENKI_CHIP_SHT31_T, USBTENKI_CHIP_SHT35_T, USBTENKI_CHIP_SHT31_T_EXTERNAL, USBTENKI_CHIPX_SHT3X_T};
    static const uint16_t rh_chips[] = {USBTENKI_CHIP_SHT_RH, USBTENKI_CHIP_BS02_RH, USBTENKI_CHIP_CC2_RH, USBTENKI_CHIP_SHT31_RH, USBTENKI_CHIP_SHT35_RH, USBTENKI_CHIP_SHT31_RH_EXTERNAL, USBTENKI_CHIPX_SHT3X_RH};

    Definition defs[] = {
        {t_chips, ARRAY_SIZE(t_chips)},
        {rh_chips, ARRAY_SIZE(rh_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_DEW_POINT;
        vc->channel.quantity.unit = UNIT_CELSIUS;
        vc->fn = virtual_dew_point;
    }

}

_VIRTUAL_CHANNEL_ADD(humidex) {

    static const uint16_t t_chips[] = {USBTENKI_CHIP_SHT_TEMP, USBTENKI_CHIP_BS02_TEMP, USBTENKI_CHIP_CC2_T, USBTENKI_CHIP_SHT31_T, USBTENKI_CHIP_SHT35_T, USBTENKI_CHIP_SHT31_T_EXTERNAL, USBTENKI_CHIPX_SHT3X_T};
    static const uint16_t rh_chips[] = {USBTENKI_CHIP_SHT_RH, USBTENKI_CHIP_BS02_RH, USBTENKI_CHIP_CC2_RH, USBTENKI_CHIP_SHT31_RH, USBTENKI_CHIP_SHT35_RH, USBTENKI_CHIP_SHT31_RH_EXTERNAL, USBTENKI_CHIPX_SHT3X_RH};

    Definition defs[] = {
        {t_chips, ARRAY_SIZE(t_chips)},
        {rh_chips, ARRAY_SIZE(rh_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_HUMIDEX;
        vc->channel.quantity.unit = UNIT_CELSIUS;
        vc->fn = virtual_humidex;
    }

}

_VIRTUAL_CHANNEL_ADD(heat_index) {

    static const uint16_t t_chips[] = {USBTENKI_CHIP_SHT_TEMP, USBTENKI_CHIP_BS02_TEMP, USBTENKI_CHIP_CC2_T, USBTENKI_CHIP_SHT31_T, USBTENKI_CHIP_SHT35_T, USBTENKI_CHIP_SHT31_T_EXTERNAL, USBTENKI_CHIPX_SHT3X_T};
    static const uint16_t rh_chips[] = {USBTENKI_CHIP_SHT_RH, USBTENKI_CHIP_BS02_RH, USBTENKI_CHIP_CC2_RH, USBTENKI_CHIP_SHT31_RH, USBTENKI_CHIP_SHT35_RH, USBTENKI_CHIP_SHT31_RH_EXTERNAL, USBTENKI_CHIPX_SHT3X_RH};

    Definition defs[] = {
        {t_chips, ARRAY_SIZE(t_chips)},
        {rh_chips, ARRAY_SIZE(rh_chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_HEAT_INDEX;
        vc->channel.quantity.unit = UNIT_CELSIUS;
        vc->fn = virtual_heat_index;
    }

}

_VIRTUAL_CHANNEL_ADD(altitude) {

    static const uint16_t chips[] = {USBTENKI_CHIP_MS5611_P, USBTENKI_CHIPX_MS5611_P};

    Definition defs[] = {
        {chips, ARRAY_SIZE(chips)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_ALTITUDE;
        vc->channel.quantity.unit = UNIT_METER;
        vc->fn = virtual_altitude;
    }

}

#if 0
_VIRTUAL_CHANNEL_ADD(hexcolor) {

    // Experiment: Arbitrary RGB intensity to Hex color. Does not work very well.

    static const uint16_t red[] = {USBTENKI_CHIP_RED};
    static const uint16_t green[] = {USBTENKI_CHIP_GREEN};
    static const uint16_t blue[] = {USBTENKI_CHIP_BLUE};

    Definition defs[] = {
        {red, ARRAY_SIZE(red)},
        {green, ARRAY_SIZE(green)},
        {blue, ARRAY_SIZE(blue)},
    };

    VirtualChannel *vc = add_virtual_channel(virtual_channels, channels, opt, defs, ARRAY_SIZE(defs));

    if (vc) {
        vc->channel.chip_id = USBTENKI_VIRTUAL_HEXCOLOR;
        vc->channel.quantity.unit = UNIT_HEXCOLOR;
        vc->fn = virtual_hexcolor;
    }

}
#endif

size_t virtual_channels_add(List *virtual_channels, List *channels, VirtualOptions *opt) {

    size_t size = virtual_channels->size;

    #define _ADD(name) add_ ## name(virtual_channels, channels, opt)

    // Order by ascending chip ID (see chip.h)
    _ADD(dew_point);
    _ADD(humidex);
    _ADD(heat_index);
    _ADD(tsl2561_lux);
    _ADD(tsl2568_lux);
    //_ADD(sht75_compensated_relative_humidity);
    _ADD(altitude);
    //_ADD(hexcolor);
    _ADD(dew_point_internal);

    return virtual_channels->size - size;  // how many elements were added

}

void virtual_channel_free(VirtualChannel *vc) {

    free(vc->parents);
    free(vc);

}

#define RETURN_ERROR(code) { \
    result->type = QUANTITY_TYPE_ERROR; \
    result->value_error = (code); \
    return; \
}

_VIRTUAL_CHANNEL_FUNCTION(tsl2568_lux) {

    // Parents:
    //   0: VIR
    //   1:  IR
    //   2: VIR 16x
    //   3:  IR 16x

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double ch0 = quantity_value_as_double(parents[0]);
    double ch1 = quantity_value_as_double(parents[1]);

    if ((ch0 < 3000) && (ch1 < 3000)) {

        // Based on these values, a 16x gain would not overflow.
        double ch0_g = quantity_value_as_double(parents[2]);
        double ch1_g = quantity_value_as_double(parents[3]);

        // stick to the low gain channels in case of saturation
        if (ch1_g != 65535 &&
            ch0_g != 65535) {
            ch0 = ch0_g / 16.0;
            ch1 = ch1_g / 16.0;
        }
    }

    if (ch0 > 65534 || ch1 > 65534) {
        RETURN_ERROR(CHIP_ERROR_SATURATED);
    }

    /*
    TMB Package
        For 0 < CH1/CH0 < 0.35     Lux = 0.00763  CH0 - 0.01031  CH1
        For 0.35 < CH1/CH0 < 0.50  Lux = 0.00817  CH0 - 0.01188  CH1
        For 0.50 < CH1/CH0 < 0.60  Lux = 0.00723  CH0 - 0.01000  CH1
        For 0.60 < CH1/CH0 < 0.72  Lux = 0.00573  CH0 - 0.00750  CH1
        For 0.72 < CH1/CH0 < 0.85  Lux = 0.00216  CH0 - 0.00254  CH1
        For CH1/CH0 > 0.85         Lux = 0
    */

    double lx;

    if (ch1/ch0 < 0.35)
        lx = 0.00763 * ch0 - 0.01031 * ch1;
    else if (ch1/ch0 < 0.50)
        lx = 0.00817 * ch0 - 0.01188 * ch1;
    else if (ch1/ch0 < 0.60)
        lx = 0.00723 * ch0 - 0.01000 * ch1;
    else if (ch1/ch0 < 0.72)
        lx = 0.00573 * ch0 - 0.00750 * ch1;
    else if (ch1/ch0 < 0.85)
        lx = 0.00216 * ch0 - 0.00254 * ch1;
    else
        lx = 0.0;

    result->unit = UNIT_LUX;
    result->value_float = (float)lx;
    result->type = QUANTITY_TYPE_FLOAT;

}

_VIRTUAL_CHANNEL_FUNCTION(tsl2561_lux) {

    // Parents:
    //   0: VIR
    //   1:  IR

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double ch0 = quantity_value_as_double(parents[0]);
    double ch1 = quantity_value_as_double(parents[1]);

    /*
    * TMB Package
    *
    * For 0 < CH1/CH0 <= 0.50       Lux = 0.0304 * CH0 - .062 * CH0 * ((CH1/CH0)1.4)
    * For 0.50 < CH1/CH0 <= 0.61    Lux = 0.0224 * CH0 - .031 * CH1
    * For 0.61 < CH1/CH0 <= 0.80    Lux = 0.0128 * CH0 - .0153 * CH1
    * For 0.80 < CH1/CH0 <= 1.30    Lux = 0.00146 *  CH0 - .00112 * CH1
    * For CH1/CH0 > 1.30            Lux = 0
    *
    */

    double lx;

    if (ch1/ch0 < 0.50)
        lx = 0.0304 * ch0 - 0.062 * ch0 * (pow((ch1/ch0),1.4));
    else if (ch1/ch0 < 0.61)
        lx = 0.0224 * ch0 - 0.031 * ch1;
    else if (ch1/ch0 < 0.80)
        lx = 0.0128 * ch0 - 0.0153 * ch1;
    else
        lx = 0.0;

    result->value_float = (float)lx;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_LUX;

}

_VIRTUAL_CHANNEL_FUNCTION(dew_point) {

    // Parents:
    //   0: Temperature
    //   1: Relative Humidity

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double T = quantity_value_as_double(parents[0]);
    double RH = quantity_value_as_double(parents[1]);

    double H = (log10(RH)-2.0)/0.4343 + (17.62*T)/(243.12+T);
    double Dp = 243.12 * H / (17.62 - H);

    result->value_float = (float)Dp;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

}

_VIRTUAL_CHANNEL_FUNCTION(humidex) {

    // Parents:
    //   0: Temperature
    //   1: Relative Humidity

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double RH, H, Dp, T, h, e;

    T = quantity_value_as_double(parents[0]);
    RH = quantity_value_as_double(parents[1]);

    H = (log10(RH)-2.0)/0.4343 + (17.62*T)/(243.12+T);
    Dp = 243.12 * H / (17.62 - H);

    // Weatheroffice.gc.ca: We only display humidex values of 25 or higher for a
    // location which reports a dew point temperature above zero (0°C) ...
    // ... AND an air temperature of 20°C or more.

    if ((vc->opt->flags & VIRTUAL_FLAG_NO_HUMIDEX_RANGE) || (Dp >= 0 && T >= 20)) {

        // We need dewpoint in kelvins...
        unit_convert(&Dp, UNIT_CELSIUS, UNIT_KELVIN);

        e = 6.11 * exp(5417.7530 * ((1.0/273.16) - (1.0/Dp)));
        h = (5.0/9.0)*(e - 10.0);

        T += h;

    }

    result->value_float = (float)T;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

}

_VIRTUAL_CHANNEL_FUNCTION(heat_index) {

    // Parents:
    //   0: Temperature
    //   1: Relative Humidity

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double T, R;

    T = quantity_value_as_double(parents[0]);
    R = quantity_value_as_double(parents[1]);

    unit_convert(&T, UNIT_CELSIUS, UNIT_FAHRENHEIT);

    if ((vc->opt->flags & VIRTUAL_FLAG_NO_HEAT_INDEX_RANGE) || (T >= 80 && R >= 40)) {

        /* Formula source:
        * Initially: http://www.crh.noaa.gov/jkl/?n=heat_index_calculator (2012: 404 error)
        *
        * http://en.wikipedia.org/wiki/Heat_index#Formula
        */
        double HI =     -42.379 +
                2.04901523 * T +
                10.14333127 * R -
                0.22475541 * T * R -
                6.83783e-3 * (T*T) -
                5.481717e-2 * (R*R) +
                1.22874e-3 * (T*T) * R +
                8.5282e-4 * T * (R*R) -
                1.99e-6 * (T*T) * (R*R);

        result->value_float = (float)HI;

    }
    else {
        // out of range
        result->value_float = (float)T;
    }

    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_FAHRENHEIT;

}

_VIRTUAL_CHANNEL_FUNCTION(altitude) {

    // Parents:
    //   0: Pressure

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double P = quantity_value_as_double(parents[0]);  // in pascals

    // constants
    const double g0 = 9.80665; // Gravitatinal acceleration in m/s^2
    const double M = 0.0289644; // Earth's air molar mass in kg/mol
    const double R = 8.31432; // Universal gas constant N*m/mol*K

    // Values for 0-11000 meters
    //double Pb = 101325; // static pressure (pascals)
    double Pb = vc->opt->standard_sea_level_pressure; // static pressure (pascals)
    const double Lb = -0.0065; // K/m
    const double Tb = 288.15; // standard temperature
    //double hb = 0; // height at bottom of layer 'b'

    const double x = (g0 * M) / (R * Lb);
    double y = pow((P / Pb), 1/x);
    double altitude = (Tb / y - Tb) / Lb;

    result->value_float = (float)altitude;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_METER;

}

_VIRTUAL_CHANNEL_FUNCTION(hexcolor) {

    // Experiment: Arbitrary RGB intensity to Hex color. Does not work very well.

    // Parents:
    //   0: Red
    //   1: Green
    //   2: Blue

    Quantity *result = &vc->channel.quantity;
    Quantity **parents = vc->parents;

    double red = quantity_value_as_double(parents[0]);
    double green = quantity_value_as_double(parents[1]);
    double blue = quantity_value_as_double(parents[2]);
    
    double max = red;
    if (max < green) {
        max = green;
    }
    if (max < blue) {
        max = blue;
    }

    uint8_t r = (uint8_t) ((red / max) * 255.0);
    uint8_t g = (uint8_t) ((green / max) * 255.0);
    uint8_t b = (uint8_t) ((blue / max) * 255.0);

    uint32_t color = (r<<16) | (g<<8) | b;

    result->value_uint32 = color;
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_HEXCOLOR;

}
