/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015 Intel Corporation.
 *
 * Thanks to Adafruit for some important clues
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <unistd.h>
#include <math.h>
#include <iostream>
#include <stdexcept>
#include <string>

#include "si114x.hpp"

using namespace upm;
using namespace std;


SI114X::SI114X(int bus, uint8_t address)
{
  m_addr = address;
  m_uvIndex = 0;

  // setup our i2c link
  if ( !(m_i2c = mraa_i2c_init(bus)) )
    {
      throw std::invalid_argument(std::string(__FUNCTION__) +
                                  ": mraa_i2c_init() failed");
      return;
    }
      
  mraa_result_t rv;
  
  if ( (rv = mraa_i2c_address(m_i2c, m_addr)) != MRAA_SUCCESS)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": mraa_i2c_address() failed");
      return;
    }

  // The data sheet recommends setting the UV calibration values to
  // 0x7b, 0x6b, 0x01, and 0x00, however the adafruit code uses a
  // different set of values, presumably calibrated to their specific
  // implementation.  We will use those defaults here, as this was
  // developed on an adafruit device.  

  // Use setUVCalibration() to set a different set of values before
  // calling init() if you need different values.

  setUVCalibration(0x29, 0x89, 0x02, 0x00);
}

SI114X::~SI114X()
{
  mraa_i2c_stop(m_i2c);
}

bool SI114X::writeByte(uint8_t reg, uint8_t byte)
{
  mraa_result_t rv = mraa_i2c_write_byte_data(m_i2c, byte, reg);

  if (rv != MRAA_SUCCESS)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": mraa_i2c_write_byte() failed");
      return false;
    }

  return true;
}

uint8_t SI114X::readByte(uint8_t reg)
{
  int x = mraa_i2c_read_byte_data(m_i2c, reg);
  if (x != -1) {
    return (uint8_t) x;
  }
  return 0;
}

uint16_t SI114X::readWord(uint8_t reg)
{
  int x = mraa_i2c_read_word_data(m_i2c, reg);
  if (x != -1) {
    return (uint16_t) x;
  }
  return 0;
}

void SI114X::setUVCalibration(uint8_t uvcoeff0, uint8_t uvcoeff1, 
                              uint8_t uvcoeff2, uint8_t uvcoeff3)
{
  m_uv_cal[0] = uvcoeff0;
  m_uv_cal[1] = uvcoeff1;
  m_uv_cal[2] = uvcoeff2;
  m_uv_cal[3] = uvcoeff3; 
};


void SI114X::writeParam(SI114X_PARAM_T param, uint8_t value)
{
  // write a parameter to the RAM parameter area

  // We write the value to the PARAM_WR register, then execute a
  // PARAM_WRITE command

  writeByte(REG_PARAM_WR, value);

  // now write it to parameter memory
  writeByte(REG_COMMAND, CMD_PARAM_SET | param);
}

uint8_t SI114X::readParam(SI114X_PARAM_T param)
{
  // get the parameter into register REG_PARAM_READ, then read and return it.

  writeByte(REG_COMMAND, CMD_PARAM_QUERY | param);
  return readByte(REG_PARAM_READ);
}

void SI114X::reset()
{
  // reset the device 

  // zero out measuring rate
  writeByte(REG_MEAS_RATE0, 0);
  writeByte(REG_MEAS_RATE1, 0);

  // disable IRQ MODES
  // these are undocumented in the datasheet, but mentioned in Adafruit's code
  writeByte(REG_IRQ_MODE1, 0);
  writeByte(REG_IRQ_MODE2, 0);
  
  // turn off interrupts
  writeByte(REG_INT_CFG, 0);
  writeByte(REG_IRQ_STATUS, 0xff);

  // send a reset
  writeByte(REG_COMMAND, CMD_RESET);
  usleep(100);

  // set the hardware key
  writeByte(REG_HW_KEY, SI114X_HW_KEY);
  usleep(100);
}

void SI114X::initialize()
{
  // initialize the device

  // first, reset it
  reset();

  // UV coefficients
  writeByte(REG_UCOEF0, m_uv_cal[0]);
  writeByte(REG_UCOEF1, m_uv_cal[1]);
  writeByte(REG_UCOEF2, m_uv_cal[2]);
  writeByte(REG_UCOEF3, m_uv_cal[3]);
  
  // enable UV sensor only for now
  writeParam(PARAM_CHLIST, CHLIST_EN_UV);

  // auto-measure speed - slowest - (rate * 31.25us)
  writeByte(REG_MEAS_RATE0, 0xff); // 7.9ms

  // set autorun
  writeByte(REG_COMMAND, CMD_ALS_AUTO);
}

void SI114X::update()
{
  // for now, just update the UV Index member variable
  uint16_t uvi = readWord(REG_AUX_UVINDEX0);

  m_uvIndex = float(uvi) / 100.0;

  // Add any further data gets() here

  return;
}
