/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015 Intel Corporation.
 *
 * 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.
 */
#pragma once

#include <string>
#include <mraa/common.hpp>
#include <mraa/i2c.hpp>

#include <interfaces/iMagnetometer.hpp>

#define AK8975_I2C_BUS 0
#define AK8975_DEFAULT_I2C_ADDR 0x0c

namespace upm {

  /**
   * @library mpu9150
   * @sensor ak8975
   * @comname 3-axis Magnetometer
   * @altname AK9875
   * @type compass
   * @man seeed
   * @con i2c
   * @web http://www.akm.com/akm/en/file/datasheet/AK8975.pdf
   *
   * @brief API for the AK8975 magnetometer
   *
   * This is a 3-axis magnetometer, which can be used alone, or
   * coupled with another device (such as the mcu9150 9-axis motion
   * sensor).
   *
   * @snippet mpu9150-ak8975.cxx Interesting
   */
  class AK8975: virtual public iMagnetometer {
  public:

    /**
     * AK8975 registers
     */
    typedef enum {
      REG_WIA                   = 0x00, // device id

      REG_INFO                  = 0x01, // undocumented (AK proprietary data)

      REG_ST1                   = 0x02, // status 1

      REG_HXL                   = 0x03, // magnetometer data, X axis low byte
      REG_HXH                   = 0x04, // magnetometer data, X axis high byte
      REG_HYL                   = 0x05,
      REG_HYH                   = 0x06,
      REG_HZL                   = 0x07,
      REG_HZH                   = 0x08,

      REG_ST2                   = 0x09, // status 2

      REG_CNTL                  = 0x0a, // control

      // REG_RSV 0x0b reserved

      REG_ASTC                  = 0x0c, // self test (internal mag field)

      // REG_TS1, REG_TS2 0x0d, 0x0e reserved/factory test

      // REG_I2CDIS 0x0f, I2C disable.  Not a good idea to use or support.
      //  write a 0x1b to disable i2c.  This requires a power cycle to undo.

      // These registers hold factory calibrated co-efficients needed to
      // properly compensate for production variations.  They can only be
      // read when device is in fuse mode.  They are used to adjust the
      // measured mag field values.
      REG_ASAX                  = 0x10, // X calibration
      REG_ASAY                  = 0x11,
      REG_ASAZ                  = 0x12
    } AK8975_REG_T;

    /**
     * ST1 bits
     */
    typedef enum {
      ST1_DRDY                  = 0x01 // data ready bit
    } ST1_BITS_T;

    /**
     * ST2 bits
     */
    typedef enum {
      ST2_DERR                  = 0x04, // data error
      ST2_HOFL                  = 0x08  // measurement overflow
    } ST2_BITS_T;

    /**
     * CNTL register, operating mode values
     */
    typedef enum {
      CNTL_PWRDWN               = 0x00, // power down
      CNTL_MEASURE              = 0x01, // single measurement
      CNTL_SELFTEST             = 0x08,
      CNTL_FUSE_ACCESS          = 0x0f  // access fuse (coeff) registers
    } CNTL_MODES_T;

    /**
     * ASTC (self test control) bits
     */
    typedef enum {
      ASTC_SELF                 = 0x40 // enable self test
    } ASTC_BITS_T;

    /**
     * ak8975 constructor
     *
     * @param bus i2c bus to use
     * @param address the address for this device
     */
    AK8975(int bus=AK8975_I2C_BUS, uint8_t address=AK8975_DEFAULT_I2C_ADDR);

    /**
     * AK8975 Destructor
     */
    ~AK8975();

    /**
     * set up initial values and start operation
     *
     * @param dsr the data sampling rate: one of the DSR_BITS_T values
     * @return true if successful
     */
    bool init();

    /**
     * put the chip into a specific mode
     *
     * @param mode one of the CNTL_MODES_T values
     * @return true if successful
     */
    bool setMode(CNTL_MODES_T mode);

    /**
     * check to see if the ST1_DRDY bit is set, indicating the device
     * can accept commands
     *
     * @return true if device is ready, false otherwise
     */
    bool isReady();

    /**
     * check to see if device is ready and sleep/retry if not.
     * Returns once device indicates it's ready.
     *
     * @return true if device is ready, false if retries exhausted
     */
    bool waitforDeviceReady();

    /**
     * take a measurement
     *
     * @param selfTest true if we are running a self test, false
     * (default) otherwise.
     * @return true if successful, false otherwise
     */
    bool update(bool selfTest=false);

    /**
     * do a self test sequence.  When self test is executed, the
     * device activates internal calibrated magnets, and measures
     * them, updating the measurement registers.  Once complete, the
     * data can be read as usual (getMagnetometer()) and the returned
     * values compared against the following limits to determine
     * correctness:
     *
     * -100 < X < +100; -100 < Y < +100; -1000 < Z < -300
     *
     * @return true if successful, false otherwise
     */
    bool selfTest();

    /**
     * return the compensated values for the x, y, and z axes.  The
     * unit of measurement is in micro-teslas (uT).
     *
     * @param x pointer to returned X axis value
     * @param y pointer to returned Y axis value
     * @param z pointer to returned Z axis value
     */
    void getMagnetometer(float *x, float *y, float *z);

    /**
     * Return magnetometer data in micro-Teslas (uT) in the form
     * of a floating point vector.  update() must have been called
     * prior to calling this method.
     *
     * @return A floating point vector containing x, y, and z in
     * that order.
     */
    std::vector<float> getMagnetometer();


  protected:
    /**
     * compute a compensated magnetometer axis value, based on the raw
     * axis value and a per-device, per-axis adjustment coefficient
     * that was read and stored at init() time.
     *
     * @param value the raw axis value to compensate
     * @param adj the adjustment coefficient
     * @return true if successful
     */
    float adjustValue(float value, float adj);

    // compensation coefficients (factory set) for this device
    float m_xCoeff;
    float m_yCoeff;
    float m_zCoeff;

    // uncompensated magnetometer readings
    float m_xData;
    float m_yData;
    float m_zData;

  private:
    mraa::I2c m_i2c;
    uint8_t m_addr;

  };
}


