/*
 * 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/i2c.h>
#include <mraa/gpio.h>

#define PCA9685_I2C_BUS 0
#define PCA9685_DEFAULT_I2C_ADDR 0x60
// internal oscillator frequency
#define PCA9685_INTERNAL_OSC  25000000.0

// This is a 'special' LED number, used to refer to the ALL_LED registers
// that affect all LED outputs at once.
#define PCA9685_ALL_LED 0xff

namespace upm {
  
  /**
   * @brief PCA9685 PWM Controller library
   * @defgroup pca9685 libupm-pca9685
   * @ingroup adafruit i2c led motor
   */
  /**
   * @library pca9685
   * @sensor pca9685
   * @comname PCA9685 PWM Controller
   * @type led motor
   * @man adafruit
   * @web http://www.adafruit.com/products/815
   * @con i2c
   *
   * @brief API for the PCA9685 16-channel, 12-bit PWM LED Controller
   *
   * This controller is also used on the Adafruit* Motor Shield v2.3
   * board to control up to 4 DC motors, 2 stepper motors, and 2 servo
   * motors.
   *
   * This module was tested with the Adafruit Motor Shield v2.3
   *
   * @image html pca9685.jpg
   * @snippet pca9685.cxx Interesting
   */
  class PCA9685 {
  public:

    /**
     * PCA9685 registers
     */
    typedef enum { REG_MODE1       = 0x00,
                   REG_MODE2       = 0x01,
                   REG_I2C_SA1     = 0x02, // I2C subaddress 1
                   REG_I2C_SA2     = 0x03,
                   REG_I2C_SA3     = 0x04,
                   REG_ALLCALL     = 0x05, // I2C all call address

                   // LED output PWM control
                   REG_LED0_ON_L   = 0x06, // LED0 ON low byte
                   REG_LED0_ON_H   = 0x07, // LED0 ON high byte
                   REG_LED0_OFF_L  = 0x08, // LED0 OFF low byte
                   REG_LED0_OFF_H  = 0x09, // LED0 OFF high byte
                   REG_LED1_ON_L   = 0x0a,
                   REG_LED1_ON_H   = 0x0b,
                   REG_LED1_OFF_L  = 0x0c,
                   REG_LED1_OFF_H  = 0x0d,
                   REG_LED2_ON_L   = 0x0e,
                   REG_LED2_ON_H   = 0x0f,
                   REG_LED2_OFF_L  = 0x10,
                   REG_LED2_OFF_H  = 0x11,
                   REG_LED3_ON_L   = 0x12,
                   REG_LED3_ON_H   = 0x13,
                   REG_LED3_OFF_L  = 0x14,
                   REG_LED3_OFF_H  = 0x15,
                   REG_LED4_ON_L   = 0x16,
                   REG_LED4_ON_H   = 0x17,
                   REG_LED4_OFF_L  = 0x18,
                   REG_LED4_OFF_H  = 0x19,
                   REG_LED5_ON_L   = 0x1a,
                   REG_LED5_ON_H   = 0x1b,
                   REG_LED5_OFF_L  = 0x1c,
                   REG_LED5_OFF_H  = 0x1d,
                   REG_LED6_ON_L   = 0x1e,
                   REG_LED6_ON_H   = 0x1f,
                   REG_LED6_OFF_L  = 0x20,
                   REG_LED6_OFF_H  = 0x21,
                   REG_LED7_ON_L   = 0x22,
                   REG_LED7_ON_H   = 0x23,
                   REG_LED7_OFF_L  = 0x24,
                   REG_LED7_OFF_H  = 0x25,
                   REG_LED8_ON_L   = 0x26,
                   REG_LED8_ON_H   = 0x27,
                   REG_LED8_OFF_L  = 0x28,
                   REG_LED8_OFF_H  = 0x29,
                   REG_LED9_ON_L   = 0x2a,
                   REG_LED9_ON_H   = 0x2b,
                   REG_LED9_OFF_L  = 0x2c,
                   REG_LED9_OFF_H  = 0x2d,
                   REG_LED10_ON_L  = 0x2e,
                   REG_LED10_ON_H  = 0x2f,
                   REG_LED10_OFF_L = 0x30,
                   REG_LED10_OFF_H = 0x31,
                   REG_LED11_ON_L  = 0x32,
                   REG_LED11_ON_H  = 0x33,
                   REG_LED11_OFF_L = 0x34,
                   REG_LED11_OFF_H = 0x35,
                   REG_LED12_ON_L  = 0x36,
                   REG_LED12_ON_H  = 0x37,
                   REG_LED12_OFF_L = 0x38,
                   REG_LED12_OFF_H = 0x39,
                   REG_LED13_ON_L  = 0x3a,
                   REG_LED13_ON_H  = 0x3b,
                   REG_LED13_OFF_L = 0x3c,
                   REG_LED13_OFF_H = 0x3d,
                   REG_LED14_ON_L  = 0x3e,
                   REG_LED14_ON_H  = 0x3f,
                   REG_LED14_OFF_L = 0x40,
                   REG_LED14_OFF_H = 0x41,
                   REG_LED15_ON_L  = 0x42,
                   REG_LED15_ON_H  = 0x43,
                   REG_LED15_OFF_L = 0x44,
                   REG_LED15_OFF_H = 0x45,
                   // 0x46-0xf9 reserved

                   REG_ALL_LED_ON_L  = 0xfa, // write all LED ON L
                   REG_ALL_LED_ON_H  = 0xfb, // write all LED ON H
                   REG_ALL_LED_OFF_L = 0xfc, // write all LED OFF L
                   REG_ALL_LED_OFF_H = 0xfd, // write all LED OFF H
                   REG_PRESCALE      = 0xfe,
                   REG_TESTMODE      = 0xff  // don't use
    } PCA9685_REG_T;
    
    /**
     * MODE1 bits
     */
    typedef enum { MODE1_ALL_CALL    = 0x01, // all call status
                   MODE1_SUB3        = 0x02, // subcall 3 status
                   MODE1_SUB2        = 0x04, // subcall 2 status
                   MODE1_SUB1        = 0x08, // subcall 1 status
                   MODE1_SLEEP       = 0x10, // sleep/normal mode
                   MODE1_AI          = 0x20, // auto-increment enable
                   MODE1_EXTCLK      = 0x40, // external clock enable
                   MODE1_RESTART     = 0x80  // restart status
    } PCA9685_MODE1_T;
    
    /**
     * MODE2 bits
     */
    typedef enum { MODE2_OUTNE0      = 0x01, // output driver enable bit 0
                   MODE2_OUTNE       = 0x02, // output driver enable bit 1
                   MODE2_OUTDRV      = 0x04, // output open-drain/totem pole
                   MODE2_OCH         = 0x08, // output change on STOP or ACK
                   MODE2_INVRT       = 0x10, // output logic state invert
                   MODE2_RESERVE0    = 0x20, // reserved
                   MODE2_RESERVE1    = 0x40, // reserved
                   MODE2_RESERVE2    = 0x80  // reserved
    } PCA9685_MODE2_T;
    
    /**
     * PCA9685 constructor
     *
     * @param bus I2C bus to use
     * @param address Address for this device
     */
    PCA9685(int bus, uint8_t address = PCA9685_DEFAULT_I2C_ADDR, bool raw = false);

    /**
     * PCA9685 destructor
     */
    ~PCA9685();

    /**
     * Writes a byte value into a register
     *
     * @param reg Register location to write into
     * @param byte Byte to write
     * @return True if successful
     */
    bool writeByte(uint8_t reg, uint8_t byte);

    /**
     * Writes a word value into a register. Note: the device must have the
     * auto-increment bit set in the MODE1 register to work.
     *
     * @param reg Register location to write into
     * @param word Word to write
     * @return True if successful
     */
    bool writeWord(uint8_t reg, uint16_t word);

    /**
     * Reads a byte value from a register
     *
     * @param reg Register location to read from
     * @return Value in a specified register
     */
    uint8_t readByte(uint8_t reg);

    /**
     * Reads a word value from a register. Note: the device must have the
     * auto-increment bit set in the MODE1 register to work.
     *
     * @param reg Register location to read from
     * @return Value in a specified register
     */
    uint16_t readWord(uint8_t reg);

    /**
     * Puts the device in or out of the sleep mode. The device is always
     * in the sleep mode upon power-up.
     *
     * @param sleep True to put the device in the sleep mode, false to put out  
     * @return True if successful
     */
    bool setModeSleep(bool sleep);

    /**
     * Sets or clears the FULL ON bit for a given LED
     *
     * @param led LED number; valid values are 0-15, PCA9685_ALL_LED
     * @param val True to set the bit, false to clear it
     * @return True if successful
     */
    bool ledFullOn(uint8_t led, bool val);

    /**
     * Sets or clears the FULL OFF bit for a given LED. If the FULL ON
     * bit is also set, FULL OFF has precedence.
     *
     * @param led LED number; valid values are 0-15, PCA9685_ALL_LED
     * @param val True to set the bit, false to clear it
     * @return True if successful
     */
    bool ledFullOff(uint8_t led, bool val);

    /**
     * Sets the 'LED on' time (0-4,095). See the PCA9685 datasheet for details. 
     *
     * @param led LED number; valid values are 0-15, PCA9685_ALL_LED
     * @param time 12-bit value at which point the LED turns on
     * @return True if successful
     */
    bool ledOnTime(uint8_t led, uint16_t time);

    /**
     * Sets the 'LED off' time (0-4,095). See the PCA9685 datasheet for details. 
     *
     * @param led LED number; valid values are 0-15, PCA9685_ALL_LED
     * @param time 12-bit value at which point the LED turns off
     * @return True if successful
     */
    bool ledOffTime(uint8_t led, uint16_t time);

    /**
     * Sets the prescale value. See the PCA9685 datasheet for
     * details. The prescale can only be set when the device is in
     * the sleep mode.
     *
     * @param prescale Prescale value
     * @return True if successful
     */
    bool setPrescale(uint8_t prescale);

    /**
     * Sets the prescale value based on a desired frequency in Hz. The
     * prescale can only be set when the device is in the sleep mode.
     *
     * @param hz Desired frequency in Hz
     * @param oscFreq Oscillator frequency; default is 25 MHz 
     * @return True if successful
     */
    bool setPrescaleFromHz(float hz, 
                           float oscFreq=PCA9685_INTERNAL_OSC);

    /**
     * Enables or disables the restart capability of the controller
     *
     * @param enabled True to enable, false to disable
     * It is enabled by default.
     */
    void enableRestart(bool enabled) { m_restartEnabled = enabled; };

  private:
    /**
     * Enables the I2C register auto-increment. This needs to be enabled
     * for write/readWord() to work. The contructor enables this by
     * default.
     *
     * @param ai True to enable, false otherwise
     */
    bool enableAutoIncrement(bool ai);

    bool m_restartEnabled;
    mraa_i2c_context m_i2c;
    uint8_t m_addr;
  };
}