/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "os/mynewt.h"
#include "hal/hal_i2c.h"
#include "i2cn/i2cn.h"
#include "sensor/sensor.h"
#include "sensor/light.h"
#include "tsl2591/tsl2591.h"
#include "tsl2591_priv.h"
#include "modlog/modlog.h"
#include "syscfg/syscfg.h"
#include "stats/stats.h"

/* Define the stats section and records */
STATS_SECT_START(tsl2591_stat_section)
    STATS_SECT_ENTRY(polled)
    STATS_SECT_ENTRY(gain_changed)
    STATS_SECT_ENTRY(timing_changed)
    STATS_SECT_ENTRY(ints_cleared)
    STATS_SECT_ENTRY(errors)
STATS_SECT_END

/* Define stat names for querying */
STATS_NAME_START(tsl2591_stat_section)
    STATS_NAME(tsl2591_stat_section, polled)
    STATS_NAME(tsl2591_stat_section, gain_changed)
    STATS_NAME(tsl2591_stat_section, timing_changed)
    STATS_NAME(tsl2591_stat_section, ints_cleared)
    STATS_NAME(tsl2591_stat_section, errors)
STATS_NAME_END(tsl2591_stat_section)

/* Global variable used to hold stats data */
STATS_SECT_DECL(tsl2591_stat_section) g_tsl2591stats;

#if MYNEWT_VAL(TSL2591_ITIME_DELAY)
static int g_tsl2591_itime_delay_ms;
#endif

/* Exports for the sensor API */
static int tsl2591_sensor_read(struct sensor *, sensor_type_t,
        sensor_data_func_t, void *, uint32_t);
static int tsl2591_sensor_get_config(struct sensor *, sensor_type_t,
        struct sensor_cfg *);

static const struct sensor_driver g_tsl2591_sensor_driver = {
    tsl2591_sensor_read,
    tsl2591_sensor_get_config
};

int
tsl2591_write8(struct sensor_itf *itf, uint8_t reg, uint32_t value)
{
    int rc;
    uint8_t payload[2] = { reg, value & 0xFF };

    struct hal_i2c_master_data data_struct = {
        .address = itf->si_addr,
        .len = 2,
        .buffer = payload
    };

    rc = sensor_itf_lock(itf, MYNEWT_VAL(TSL2591_ITF_LOCK_TMO));
    if (rc) {
        return rc;
    }

    rc = i2cn_master_write(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                           MYNEWT_VAL(TSL2591_I2C_RETRIES));
    if (rc) {
        TSL2591_LOG_ERROR(
                    "Failed to write 0x%02X:0x%02X with value 0x%02lX\n",
                    data_struct.address, reg, value);
        STATS_INC(g_tsl2591stats, errors);
    }

    sensor_itf_unlock(itf);

    return rc;
}

int
tsl2591_write16(struct sensor_itf *itf, uint8_t reg, uint16_t value)
{
    int rc;
    uint8_t payload[3] = { reg, value & 0xFF, (value >> 8) & 0xFF };

    struct hal_i2c_master_data data_struct = {
        .address = itf->si_addr,
        .len = 3,
        .buffer = payload
    };

    rc = sensor_itf_lock(itf, MYNEWT_VAL(TSL2591_ITF_LOCK_TMO));
    if (rc) {
        return rc;
    }

    rc = i2cn_master_write(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                           MYNEWT_VAL(TSL2591_I2C_RETRIES));
    if (rc) {
        TSL2591_LOG_ERROR(
                    "Failed to write @0x%02X with value 0x%02X 0x%02X\n",
                    reg, payload[0], payload[1]);
        STATS_INC(g_tsl2591stats, errors);
    }

    sensor_itf_unlock(itf);

    return rc;
}

int
tsl2591_read8(struct sensor_itf *itf, uint8_t reg, uint8_t *value)
{
    int rc;
    uint8_t payload;

    struct hal_i2c_master_data data_struct = {
        .address = itf->si_addr,
        .len = 1,
        .buffer = &payload
    };

    rc = sensor_itf_lock(itf, MYNEWT_VAL(TSL2591_ITF_LOCK_TMO));
    if (rc) {
        return rc;
    }

    /* Register write */
    payload = reg;
    rc = i2cn_master_write(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                           MYNEWT_VAL(TSL2591_I2C_RETRIES));
    if (rc) {
        TSL2591_LOG_ERROR("Failed to address sensor\n");
        STATS_INC(g_tsl2591stats, errors);
        goto err;
    }

    /* Read one byte back */
    payload = 0;
    rc = i2cn_master_read(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                          MYNEWT_VAL(TSL2591_I2C_RETRIES));
    *value = payload;
    if (rc) {
        TSL2591_LOG_ERROR("Failed to read @0x%02X\n", reg);
        STATS_INC(g_tsl2591stats, errors);
    }

err:
    sensor_itf_unlock(itf);

    return rc;
}

int
tsl2591_read16(struct sensor_itf *itf, uint8_t reg, uint16_t *value)
{
    int rc;
    uint8_t payload[2] = { reg, 0 };

    struct hal_i2c_master_data data_struct = {
        .address = itf->si_addr,
        .len = 1,
        .buffer = payload
    };

    rc = sensor_itf_lock(itf, MYNEWT_VAL(TSL2591_ITF_LOCK_TMO));
    if (rc) {
        return rc;
    }

    /* Register write */
    rc = i2cn_master_write(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                           MYNEWT_VAL(TSL2591_I2C_RETRIES));
    if (rc) {
        TSL2591_LOG_ERROR("Failed to address sensor\n");
        STATS_INC(g_tsl2591stats, errors);
        goto err;
    }

    /* Read two bytes back */
    memset(payload, 0, 2);
    data_struct.len = 2;
    rc = i2cn_master_read(itf->si_num, &data_struct, OS_TICKS_PER_SEC / 10, 1,
                          MYNEWT_VAL(TSL2591_I2C_RETRIES));
    *value = (uint16_t)payload[0] | ((uint16_t)payload[1] << 8);
    if (rc) {
        TSL2591_LOG_ERROR("Failed to read @0x%02X\n", reg);
        STATS_INC(g_tsl2591stats, errors);
        goto err;
    }

err:
    sensor_itf_unlock(itf);

    return rc;
}

int
tsl2591_enable(struct sensor_itf *itf, uint8_t state)
{
    /* Enable the device by setting the PON and AEN bits */
    return tsl2591_write8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE,
                          state ? TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN :
                          TSL2591_ENABLE_POWEROFF);
}

int
tsl2591_get_enable(struct sensor_itf *itf, uint8_t *enabled)
{
    int rc;
    uint8_t reg;

    /* Check the two enable bits (PON and AEN) */
    rc =  tsl2591_read8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE,
                        &reg);
    if (rc) {
        goto err;
    }

    *enabled = reg & (TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN) ? 1 : 0;

    return 0;
err:
    return rc;
}

int
tsl2591_set_integration_time(struct sensor_itf *itf,
                             uint8_t int_time)
{
    int rc;
    uint8_t gain;

    if (int_time > TSL2591_LIGHT_ITIME_600MS) {
      int_time = TSL2591_LIGHT_ITIME_600MS;
    }

    rc = tsl2591_get_gain(itf, &gain);
    if (rc) {
        goto err;
    }

    rc = tsl2591_write8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
                        int_time | gain);
    if (rc) {
        goto err;
    }

#if MYNEWT_VAL(TSL2591_ITIME_DELAY)
    /* Set the intergration time delay value in ms (+8% margin of error) */
    g_tsl2591_itime_delay_ms = (int_time + 1) * 108;
#endif

    /* Increment the timing changed counter */
    STATS_INC(g_tsl2591stats, timing_changed);

    return 0;
err:
    return rc;
}

int
tsl2591_get_integration_time(struct sensor_itf *itf, uint8_t *itime)
{
    int rc;
    uint8_t reg;

    rc = tsl2591_read8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
                       &reg);
    if (rc) {
        goto err;
    }

    *itime = reg & 0x07;

    return 0;
err:
    return rc;
}

int
tsl2591_set_gain(struct sensor_itf *itf, uint8_t gain)
{
    int rc;
    uint8_t int_time;

    if ((gain != TSL2591_LIGHT_GAIN_LOW)
        && (gain != TSL2591_LIGHT_GAIN_MED)
        && (gain != TSL2591_LIGHT_GAIN_HIGH)
        && (gain != TSL2591_LIGHT_GAIN_MAX)) {
            TSL2591_LOG_ERROR("Invalid gain value\n");
            rc = SYS_EINVAL;
            goto err;
    }

    rc = tsl2591_get_integration_time(itf, &int_time);
    if (rc) {
        goto err;
    }

    rc = tsl2591_write8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
                        int_time | gain);
    if (rc) {
        goto err;
    }

    /* Increment the gain change counter */
    STATS_INC(g_tsl2591stats, gain_changed);

    return 0;
err:
    return rc;
}

int
tsl2591_get_gain(struct sensor_itf *itf, uint8_t *gain)
{
    int rc;
    uint8_t reg;

    rc = tsl2591_read8(itf, TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
                       &reg);
    if (rc) {
        goto err;
    }

    *gain = reg & 0x30;

    return 0;
err:
    return rc;
}

int
tsl2591_get_data_r(struct sensor_itf *itf, uint16_t *broadband, uint16_t *ir)
{
    int rc;

#if MYNEWT_VAL(TSL2591_ITIME_DELAY)
    /* Insert a delay of integration time to ensure valid sample */
    os_time_delay((OS_TICKS_PER_SEC * g_tsl2591_itime_delay_ms) / 1000);
#endif

    /* CHAN0 must be read before CHAN1 */
    /* See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176 */
    *broadband = *ir = 0;
    rc = tsl2591_read16(itf, TSL2591_COMMAND_BIT |
                        TSL2591_REGISTER_CHAN0_LOW, broadband);
    if (rc) {
        goto err;
    }
    rc = tsl2591_read16(itf, TSL2591_COMMAND_BIT |
                        TSL2591_REGISTER_CHAN1_LOW, ir);
    if (rc) {
        goto err;
    }

    return 0;
err:
    return rc;
}

int
tsl2591_get_data(struct sensor_itf *itf, uint16_t *broadband, uint16_t *ir)
{
    int rc;
    uint8_t itime;
    uint16_t igain;
    uint16_t maxval;
    uint16_t divisor;

#if !(MYNEWT_VAL(TSL2591_AUTO_GAIN))
    rc = tsl2591_get_data_r(itf, broadband, ir);
    if (rc) {
        goto err;
    }

    /* Increment the polling counter */
    STATS_INC(g_tsl2591stats, polled);

    return rc;
#endif

    /* Use auto-gain algorithm for better range at the expensive of */
    /* unpredictable conversion times */

    /* Get the integration time to determine max raw value */
    rc = tsl2591_get_integration_time(itf, &itime);
    if (rc) {
        goto err;
    }
    /* Max value for 100ms = 37888, otherwise 65535 */
    maxval = itime ? 65535 : 37888;

    /* Set gain to 1x for the baseline conversion */
    rc = tsl2591_set_gain(itf, TSL2591_LIGHT_GAIN_LOW);
    if (rc) {
        goto err;
    }

    /* Get the baseline conversion values */
    /* Note: double-read required to empty cached values with prev gain */
    rc = tsl2591_get_data_r(itf, broadband, ir);
    rc = tsl2591_get_data_r(itf, broadband, ir);
    if (rc) {
        goto err;
    }

    /* Determine the ideal gain setting */
    divisor = *broadband > *ir ? *broadband : *ir;
    /* Avoid potential divide by 0 errors in low light */
    igain = maxval / (divisor == 0 ? 1 : divisor);


    /* Find the closest gain <= igain */
    if (igain < 25) {
        /* Gain 1x, return the current sample */
        return 0;
    } else if (igain < 428) {
        /* Set gain to ~25x */
        rc = tsl2591_set_gain(itf, TSL2591_LIGHT_GAIN_MED);
        if (rc) {
            goto err;
        }
    } else if (igain < 9876) {
        /* Set gain to ~428x */
        rc = tsl2591_set_gain(itf, TSL2591_LIGHT_GAIN_HIGH);
        if (rc) {
            goto err;
        }
    } else {
        /* Set gain to ~9876x */
        rc = tsl2591_set_gain(itf, TSL2591_LIGHT_GAIN_MAX);
        if (rc) {
            goto err;
        }
    }

    /* Get a new data sample */
    /* Note: double-read required to empty cached values with prev gain */
    rc = tsl2591_get_data_r(itf, broadband, ir);
    rc = tsl2591_get_data_r(itf, broadband, ir);
    if (rc) {
        goto err;
    }

    /* Increment the polling counter */
    STATS_INC(g_tsl2591stats, polled);

    return 0;
err:
    return rc;
}

int
tsl2591_init(struct os_dev *dev, void *arg)
{
    struct tsl2591 *tsl2591;
    struct sensor *sensor;
    int rc;

    if (!arg || !dev) {
        rc = SYS_ENODEV;
        goto err;
    }

    tsl2591 = (struct tsl2591 *) dev;

    tsl2591->cfg.mask = SENSOR_TYPE_ALL;

    sensor = &tsl2591->sensor;

    /* Initialise the stats entry */
    rc = stats_init(
        STATS_HDR(g_tsl2591stats),
        STATS_SIZE_INIT_PARMS(g_tsl2591stats, STATS_SIZE_32),
        STATS_NAME_INIT_PARMS(tsl2591_stat_section));
    SYSINIT_PANIC_ASSERT(rc == 0);
    /* Register the entry with the stats registry */
    rc = stats_register(dev->od_name, STATS_HDR(g_tsl2591stats));
    SYSINIT_PANIC_ASSERT(rc == 0);

#ifdef ARCH_sim
    /* Register the sim driver */
    tsl2591_sim_init();
#endif

    rc = sensor_init(sensor, dev);
    if (rc) {
        goto err;
    }

    /* Add the light driver */
    rc = sensor_set_driver(sensor, SENSOR_TYPE_LIGHT,
            (struct sensor_driver *) &g_tsl2591_sensor_driver);
    if (rc) {
        goto err;
    }

    /* Set the interface */
    rc = sensor_set_interface(sensor, arg);
    if (rc) {
        goto err;
    }

    rc = sensor_mgr_register(sensor);
    if (rc) {
        goto err;
    }

    return 0;
err:
    STATS_INC(g_tsl2591stats, errors);
    return rc;
}

float tsl2591_calculate_lux_f(struct sensor_itf *itf, uint16_t broadband,
  uint16_t ir, struct tsl2591_cfg *cfg)
{
    int      rc;
    uint8_t  itime;
    uint8_t  gain;
    float    again;
    float    cpl;
    float    lux;

    /* Check for overflow conditions */
    if ((broadband == 0xFFFF) | (ir == 0xFFFF)) {
        return 0;
    }

    rc = tsl2591_get_gain(itf, &gain);
    if (rc) {
        return 0;
    }

    switch (gain)
    {
      case TSL2591_LIGHT_GAIN_MED :
        again = 25.0F;
        break;
      case TSL2591_LIGHT_GAIN_HIGH :
        again = 428.0F;
        break;
      case TSL2591_LIGHT_GAIN_MAX :
        again = 9876.0F;
        break;
      case TSL2591_LIGHT_GAIN_LOW :
      default:
        again = 1.0F;
        break;
    }

    rc = tsl2591_get_integration_time(itf, &itime);
    if (rc) {
        return 0;
    }

    cpl = ((float)((itime+1)*101) * again) / TSL2591_LUX_DF;

    lux = (((float)broadband - (float)ir)) *
      (1.0F - ((float)ir/(float)broadband)) / cpl;

    /* Note that this implementation will truncate values < 1.0 lux! */
    return lux;
}

uint32_t
tsl2591_calculate_lux(struct sensor_itf *itf, uint16_t broadband, uint16_t ir,
  struct tsl2591_cfg *cfg)
{
    return (uint32_t)tsl2591_calculate_lux_f(itf, broadband, ir, cfg);
}

static int
tsl2591_sensor_read(struct sensor *sensor, sensor_type_t type,
        sensor_data_func_t data_func, void *data_arg, uint32_t timeout)
{
    struct tsl2591 *tsl2591;
    struct sensor_light_data sld;
    struct sensor_itf *itf;
    uint16_t full;
    uint16_t ir;
    uint32_t lux;
    int rc;

    /* If the read isn't looking for light data, don't do anything. */
    if (!(type & SENSOR_TYPE_LIGHT)) {
        rc = SYS_EINVAL;
        goto err;
    }

    itf = SENSOR_GET_ITF(sensor);
    tsl2591 = (struct tsl2591 *)SENSOR_GET_DEVICE(sensor);

    /* Get a new light sample */
    if (type & SENSOR_TYPE_LIGHT) {
        full = ir = 0;

        rc = tsl2591_get_data(itf, &full, &ir);
        if (rc) {
            goto err;
        }

        lux = tsl2591_calculate_lux(itf, full, ir, &(tsl2591->cfg));
        sld.sld_full = full;
        sld.sld_ir = ir;
        sld.sld_lux = lux;

        sld.sld_full_is_valid = 1;
        sld.sld_ir_is_valid   = 1;
        sld.sld_lux_is_valid  = 1;

        /* Call data function */
        rc = data_func(sensor, data_arg, &sld, SENSOR_TYPE_LIGHT);
        if (rc != 0) {
            goto err;
        }
    }

    return 0;
err:
    return rc;
}

static int
tsl2591_sensor_get_config(struct sensor *sensor, sensor_type_t type,
        struct sensor_cfg *cfg)
{
    int rc;

    if ((type != SENSOR_TYPE_LIGHT)) {
        rc = SYS_EINVAL;
        goto err;
    }

    cfg->sc_valtype = SENSOR_VALUE_TYPE_INT32;

    return 0;
err:
    return rc;
}

int
tsl2591_config(struct tsl2591 *tsl2591, struct tsl2591_cfg *cfg)
{
    int rc;
    struct sensor_itf *itf;

    itf = SENSOR_GET_ITF(&(tsl2591->sensor));

    rc = tsl2591_enable(itf, 1);
    if (rc) {
        goto err;
    }

    rc = tsl2591_set_integration_time(itf, cfg->integration_time);
    if (rc) {
        goto err;
    }

    tsl2591->cfg.integration_time = cfg->integration_time;

    rc = tsl2591_set_gain(itf, cfg->gain);
    if (rc) {
        goto err;
    }

    tsl2591->cfg.gain = cfg->gain;

    rc = sensor_set_type_mask(&(tsl2591->sensor), cfg->mask);
    if (rc) {
        goto err;
    }

    tsl2591->cfg.mask = cfg->mask;

    return 0;
err:
    return rc;
}
