/** 
 * @file    ltr329.c
 * @brief   Driver for LTR-329 ambient light sensor.
 *          https://www.mouser.com/datasheet/2/239/Lite-On_LTR-329ALS-01%20DS_ver1.1-348647.pdf
 * @author  Kenny Tran
 * @date    Mar 06 2024
 * @version 0.0.1
 *
 * @section LICENSES
 * Copyright (C) Morgan Advanced Programmable Systems, Inc.
 */


/*--------------------------------------------------------------------------------------------------
 * INCLUDE FILES
 *------------------------------------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#include <zephyr/kernel.h>

#include "../lib/errno.h"
#include "../lib/print.h"

#include "../wrappers/i2c.h"

#include "als.h"


/*--------------------------------------------------------------------------------------------------
 * MACROS & CONSTANTS
 *------------------------------------------------------------------------------------------------*/
#define _LTR329_I2C_DEV_ADDR    (0x29)  /* I2C address                              */
#define _LTR329_REG_PART_ID     (0x86)  /* Part RD/revision register                */
#define _LTR329_REG_MFG_ID      (0x87)  /* Manufacturer ID register                 */
#define _LTR329_REG_CTRL        (0x80)  /* ALS control register                     */
#define _LTR329_REG_MEAS_RATE   (0x85)  /* Integration time and data rate           */
#define _LTR329_REG_CH1_0_DATA  (0x88)  /* Data for channel 1 (read all 4 bytes)    */
#define _LTR329_REG_CH1_1_DATA  (0x89)
#define _LTR329_REG_CH0_0_DATA  (0x8A)
#define _LTR329_REG_CH0_1_DATA  (0x8B)
#define _LTR329_REG_STATUS      (0x8C)  /* Status register                          */

#define _LTR329_PART_ID         (0xA0)  /* Expected part ID                         */
#define _LTR329_MFG_ID          (0x05)  /* Expected manufacturing ID                */


/*--------------------------------------------------------------------------------------------------
 * TYPE DEFINITIONS & ENUMERATIONS
 *------------------------------------------------------------------------------------------------*/
typedef struct
{
    const uint8_t reg;
    const uint8_t bit_start;
    const uint8_t bit_len;
} _als_field_s;

typedef struct
{
    const _als_field_s gain;
    const _als_field_s rst;
    const _als_field_s enable;
    const _als_field_s int_time;
    const _als_field_s measure_rate;
    const _als_field_s part_no;
    const _als_field_s rev_id;
    const _als_field_s mfg_id;
    const _als_field_s data_valid;
    const _als_field_s gain_range;
    const _als_field_s data_rdy;
} _als_fields_s;


/*--------------------------------------------------------------------------------------------------
 * PRIVATE CONSTANTS & VARIABLES
 *------------------------------------------------------------------------------------------------*/
static       bool          _als_init   = false;
static const _als_fields_s _als_fields = {
    .gain         = (_als_field_s) { .reg = _LTR329_REG_CTRL,      .bit_start = 4, .bit_len = 3 },
    .rst          = (_als_field_s) { .reg = _LTR329_REG_CTRL,      .bit_start = 1, .bit_len = 1 },
    .enable       = (_als_field_s) { .reg = _LTR329_REG_CTRL,      .bit_start = 0, .bit_len = 1 },
    .int_time     = (_als_field_s) { .reg = _LTR329_REG_MEAS_RATE, .bit_start = 5, .bit_len = 3 },
    .measure_rate = (_als_field_s) { .reg = _LTR329_REG_MEAS_RATE, .bit_start = 2, .bit_len = 3 },
    .part_no      = (_als_field_s) { .reg = _LTR329_REG_PART_ID,   .bit_start = 7, .bit_len = 4 },
    .rev_id       = (_als_field_s) { .reg = _LTR329_REG_PART_ID,   .bit_start = 3, .bit_len = 4 },
    .mfg_id       = (_als_field_s) { .reg = _LTR329_REG_MFG_ID,    .bit_start = 7, .bit_len = 8 },
    .data_valid   = (_als_field_s) { .reg = _LTR329_REG_STATUS,    .bit_start = 7, .bit_len = 1 },
    .gain_range   = (_als_field_s) { .reg = _LTR329_REG_STATUS,    .bit_start = 6, .bit_len = 3 },
    .data_rdy     = (_als_field_s) { .reg = _LTR329_REG_STATUS,    .bit_start = 2, .bit_len = 1 }
};


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION PROTOTYPES
 *------------------------------------------------------------------------------------------------*/


/*--------------------------------------------------------------------------------------------------
 * PUBLIC FUNCTION DEFINITIOPNS
 *------------------------------------------------------------------------------------------------*/
ERRNO_e ALS_init (void)
{
    uint8_t rx;

    if (_als_init)
    {
        return ERRNO_ALREADY_INITIALIZED;
    }

    if (I2C_init (I2C_CH_0) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    /* Read part ID. */
    if (I2C_bytes_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _LTR329_REG_PART_ID, &rx, 1) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    /* Return error if part ID mismatch. */
    if (rx != _LTR329_PART_ID)
    {
        return ERRNO_ID_FAILURE;
    }

    /* Read manufacturing ID. */
    if (I2C_bytes_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _LTR329_REG_MFG_ID, &rx, 1) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    /* Return error if manufacturing ID mismatch. */
    if (rx != _LTR329_MFG_ID)
    {
        return ERRNO_ID_FAILURE;
    }

    /* Set init flag */
    _als_init = true;

    /* Perform a soft reset. */
    return ALS_reset ();
}

ERRNO_e ALS_reset (void)
{
    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Write soft reset bit to control register. */
    if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.rst.reg,
                        1, _als_fields.rst.bit_start, _als_fields.rst.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    /* Per datasheet, need to wait at least 10 ms after reset. */
    k_busy_wait (10000);

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_enable (bool en)
{
    uint8_t val = (uint8_t) en;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    if (en)
    {
        /* Write enable bit to control register. */
        if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.enable.reg,
                            val, _als_fields.enable.bit_start, _als_fields.enable.bit_len) != ERRNO_SUCCESS)
        {
            return ERRNO_I2C_FAILURE;
        }
    }
    else
    {
         if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.enable.reg,
                            val, _als_fields.enable.bit_start, _als_fields.enable.bit_len) != ERRNO_SUCCESS)
        {
            return ERRNO_I2C_FAILURE;
        }
    }

    /* Per datasheet, need to wait 200 ms for measurement. */
    k_sleep (K_MSEC(200));

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_is_enabled (bool *p_enabled)
{
    uint8_t rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    if (I2C_bits_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.enable.reg, &rx,
                       _als_fields.enable.bit_start, _als_fields.enable.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_enabled = (bool) rx;
    
    return ERRNO_SUCCESS;
}

ERRNO_e ALS_gain_set (ALS_gain_e gain)
{
    uint8_t tx = (uint8_t) gain;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Write gain bits to control register. */
    if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.gain.reg,
                        tx, _als_fields.gain.bit_start, _als_fields.gain.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_gain_get (ALS_gain_e *p_gain)
{
    uint8_t rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Read gain bits from control register. */
    if (I2C_bits_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.gain.reg,
                       &rx, _als_fields.gain.bit_start, _als_fields.gain.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_gain = (ALS_gain_e) rx;

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_int_time_set (ALS_int_time_e time)
{
    uint8_t tx = (uint8_t) time;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Write integration time bits to measure rate register. */
    if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.int_time.reg,
                        tx, _als_fields.int_time.bit_start, _als_fields.int_time.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_int_time_get (ALS_int_time_e *p_time)
{
    uint8_t rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Read integration time bits from measure rate register. */
    if (I2C_bits_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.int_time.reg,
                       &rx, _als_fields.int_time.bit_start, _als_fields.int_time.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_time = (ALS_int_time_e) rx;

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_measure_rate_set (ALS_measure_rate_e rate)
{
    uint8_t tx = (uint8_t) rate;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Write measurement bits to measure rate register. */
    if (I2C_bits_write (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.measure_rate.reg,
                        tx, _als_fields.measure_rate.bit_start, _als_fields.measure_rate.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_measure_rate_get (ALS_measure_rate_e *p_rate)
{
    uint8_t rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Read measurement bits from measure rate register. */
    if (I2C_bits_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.measure_rate.reg,
                       &rx, _als_fields.measure_rate.bit_start, _als_fields.measure_rate.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_rate = (ALS_measure_rate_e) rx;

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_new_data_available (bool *p_available)
{
    uint8_t rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Read measurement bits from measure rate register. */
    if (I2C_bits_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _als_fields.data_rdy.reg,
                       &rx, _als_fields.data_rdy.bit_start, _als_fields.data_rdy.bit_len) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_available = (bool) rx;

    return ERRNO_SUCCESS;
}

ERRNO_e ALS_read (ALS_data_s *p_data)
{
    ALS_data_s rx;

    if (!_als_init)
    {
        return ERRNO_NOT_INITIALIZED;
    }

    /* Read measurement bits from measure rate register. */
    if (I2C_bytes_read (I2C_CH_0, _LTR329_I2C_DEV_ADDR, _LTR329_REG_CH1_0_DATA,
                        (uint8_t *) &rx, sizeof (rx)) != ERRNO_SUCCESS)
    {
        return ERRNO_I2C_FAILURE;
    }

    *p_data = rx;

    return ERRNO_SUCCESS;
}

void ALS_cli_cmd (int32_t argc, char **argv)
{
    if (argc == 0)
    {
        ALS_gain_e         gain;
        ALS_int_time_e     int_time;
        ALS_measure_rate_e meas_rate;
        ALS_data_s         data;
        bool               enabled;

        (void) ALS_gain_get (&gain);
        (void) ALS_int_time_get (&int_time);
        (void) ALS_measure_rate_get (&meas_rate);
        (void) ALS_read (&data);
        (void) ALS_is_enabled (&enabled);


        PRINT (INFO, "LTR329\r\n");
        PRINT (INFO, "-------------------------------------------\r\n");
        PRINT (INFO, "Enabled               : %s\r\n", (enabled ? "true" : "false"));
        PRINT (INFO, "Gain                  : ");
        switch (gain)
        {
            case ALS_GAIN_1:
                PRINT (INFO, "1\r\n");
                break;
            case ALS_GAIN_2:
                PRINT (INFO, "2\r\n");
                break;
            case ALS_GAIN_4:
                PRINT (INFO, "4\r\n");
                break;
            case ALS_GAIN_8:
                PRINT (INFO, "8\r\n");
                break;
            case ALS_GAIN_48:
                PRINT (INFO, "48\r\n");
                break;
            case ALS_GAIN_96:
                PRINT (INFO, "96\r\n");
                break;
        }
        PRINT (INFO, "Integration Time (ms) : ");
        switch (int_time)
        {
            case ALS_INT_TIME_100MS:
                PRINT (INFO, "100\r\n");
                break;
            case ALS_INT_TIME_50MS:
                PRINT (INFO, "50\r\n");
                break;
            case ALS_INT_TIME_200MS:
                PRINT (INFO, "200\n");
                break;
            case ALS_INT_TIME_400MS:
                PRINT (INFO, "400\n");
                break;
            case ALS_INT_TIME_150MS:
                PRINT (INFO, "150\r\n");
                break;
            case ALS_INT_TIME_250MS:
                PRINT (INFO, "250\r\n");
                break;
            case ALS_INT_TIME_300MS:
                PRINT (INFO, "300\r\n");
                break;
            case ALS_INT_TIME_350MS:
                PRINT (INFO, "350\r\n");
                break;
        }
        PRINT (INFO, "Measurement Rate (ms) : ");
        switch (meas_rate)
        {
            case ALS_MEASURE_RATE_50MS:
                PRINT (INFO, "50\r\n");
                break;
            case ALS_MEASURE_RATE_100MS:
                PRINT (INFO, "100\r\n");
                break;
            case ALS_MEASURE_RATE_200MS:
                PRINT (INFO, "200\n");
                break;
            case ALS_MEASURE_RATE_500MS:
                PRINT (INFO, "500\n");
                break;
            case ALS_MEASURE_RATE_1000MS:
                PRINT (INFO, "1000\r\n");
                break;
            case ALS_MEASURE_RATE_2000MS:
                PRINT (INFO, "2000\r\n");
                break;
        }
        PRINT (INFO, "Visible               : %d\r\n", data.visible);
        PRINT (INFO, "Infrared              : %d\r\n", data.infrared);
    }
    else if (argc == 1)
    {
        if (!strcmp (argv[0], "--init") || !strcmp (argv[0], "-i"))
        {
            PRINT (INFO, "Ret code : %d\r\n", ALS_init ());
        }
        else if (!strcmp (argv[0], "--reset") || !strcmp (argv[0], "-r"))
        {
            PRINT (INFO, "Ret code : %d\r\n", ALS_reset ());
        }
        else
        {
            goto USAGE;
        }
    }
    else if (argc == 2)
    {
        if (!strcmp (argv[0], "--enable") || !strcmp (argv[0], "-e"))
        {
            if (!strcmp (argv[1], "true"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_enable (true));
            }
            else if (!strcmp (argv[1], "false"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_enable (false));
            }
            
        }
        else if (!strcmp (argv[0], "-g"))
        {
            if (!strcmp (argv[1], "1"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_1));
            }
            else if (!strcmp (argv[1], "2"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_2));
            }
            else if (!strcmp (argv[1], "4"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_4));
            }
            else if (!strcmp (argv[1], "8"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_8));
            }
            else if (!strcmp (argv[1], "48"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_48));
            }
            else if (!strcmp (argv[1], "96"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_gain_set (ALS_GAIN_96));
            }
            else
            {
                goto USAGE;
            }
        }
        else if (!strcmp (argv[0], "-it"))
        {
            if (!strcmp (argv[1], "100"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_100MS));
            }
            else if (!strcmp (argv[1], "50"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_50MS));
            }
            else if (!strcmp (argv[1], "200"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_200MS));
            }
            else if (!strcmp (argv[1], "400"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_400MS));
            }
            else if (!strcmp (argv[1], "150"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_150MS));
            }
            else if (!strcmp (argv[1], "250"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_250MS));
            }
            else if (!strcmp (argv[1], "300"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_300MS));
            }
            else if (!strcmp (argv[1], "350"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_int_time_set (ALS_INT_TIME_350MS));
            }
            else
            {
                goto USAGE;
            }
        }
        else if (!strcmp (argv[0], "-mr"))
        {
            if (!strcmp (argv[1], "50"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_50MS));
            }
            else if (!strcmp (argv[1], "100"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_100MS));
            }
            else if (!strcmp (argv[1], "200"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_200MS));
            }
            else if (!strcmp (argv[1], "500"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_500MS));
            }
            else if (!strcmp (argv[1], "1000"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_1000MS));
            }
            else if (!strcmp (argv[1], "2000"))
            {
                PRINT (INFO, "Ret code : %d\r\n", ALS_measure_rate_set (ALS_MEASURE_RATE_2000MS));
            }
            else
            {
                goto USAGE;
            }
        }
        else
        {
            goto USAGE;
        }
    }
    else
    {
USAGE:
        PRINT (INFO, "Usage: als [OPTION...]\r\n");
        PRINT (INFO, "  -i | --init          Initializes LTR329 ALS module\r\n");
        PRINT (INFO, "  -r | --reset         Resets LTR329 ALS module\r\n");
        PRINT (INFO, "  -e | --enable        Enable LTR329 ALS to start measuring\r\n");
        PRINT (INFO, "    <%%b>               true = enable\r\n");
        PRINT (INFO, "                       false = disable\r\n");
        PRINT (INFO, "  -g                   Sets the gain\r\n");
        PRINT (INFO, "    <%%d>               [1, 2, 4, 8, 48, 96]\r\n");
        PRINT (INFO, "  -it                  Sets the integration time (ms)\r\n");
        PRINT (INFO, "    <%%d>               [100, 50, 200, 400, 150, 250, 300, 350]\r\n");
        PRINT (INFO, "  -mr                  Sets the measurement rate (ms)\r\n");
        PRINT (INFO, "    <%%d>               [50, 100, 200, 500, 1000, 2000]\r\n");
    }
}


/*--------------------------------------------------------------------------------------------------
 * PRIVATE FUNCTION DEFINITIONS
 *------------------------------------------------------------------------------------------------*/
