#ifndef _PCA9685_H_
#define _PCA9685_H_

#include <stdint.h>
#include <iostream>
#include <unistd.h>
#include <math.h>
#include "../../communication/i2c/i2c.hpp"

// https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf

// REGISTER ADDRESSES
#define PCA9685_REG_MODE1 0x00      /**< Mode Register 1 */
#define PCA9685_REG_MODE2 0x01      /**< Mode Register 2 */
#define PCA9685_SUBADR1 0x02    /**< I2C-bus subaddress 1 */
#define PCA9685_SUBADR2 0x03    /**< I2C-bus subaddress 2 */
#define PCA9685_SUBADR3 0x04    /**< I2C-bus subaddress 3 */
#define PCA9685_ALLCALLADR 0x05 /**< LED All Call I2C-bus address */

#define PCA9685_LED_PWM_REG_START 0x06  // Start register of the fisrt LED's PWM Register wich also corresponds to LED0_ON_L
#define PCA9685_PWM_ON_H_OFFSET 0x01	// PWM ON HIGH Register for the selected LED
#define PCA9685_PWM_OFF_L_OFFSET 0x02	// PWM OFF LOW Register for the selected LED
#define PCA9685_PWM_OFF_H_OFFSET 0x03	// PWM OFF HIGH Register for the selected LED
#define PCA9685_LED_NEXT_OFFSET 0x04	// Register ON LOW of the next LED 

#define PCA9685_LED_FULL_OFF_BIT 0x04 // Bit no to set on the LED OFF HIGH gerister of the selected LED to turn the LED fully OFF
#define PCA9685_LED_FULL_ON_BIT 0x04 // Bit no to set on the LED ON HIGH gerister of the selected LED to turn the LED fully ON

#define PCA9685_ALL_ON_L 0xFA	// load all the LEDn_ON registers, low
#define PCA9685_ALL_ON_H 0xFB	// load all the LEDn_ON registers, high
#define PCA9685_ALL_OFF_L 0xFC	// load all the LEDn_OFF registers, low
#define PCA9685_ALL_OFF_H 0xFD	// load all the LEDn_OFF registers,high
#define PCA9685_REG_PRESCALE 0xFE	// Prescaler  Register for PWM output frequency
#define PCA9685_TESTMODE 0xFF   // defines the test mode to be entered

// MODE1 bits
#define PCA9685_MODE1_ALLCAL 0x01  /**< respond to LED All Call I2C-bus address */
#define PCA9685_MODE1_SUB3 0x02    /**< respond to I2C-bus subaddress 3 */
#define PCA9685_MODE1_SUB2 0x04    /**< respond to I2C-bus subaddress 2 */
#define PCA9685_MODE1_SUB1 0x08    /**< respond to I2C-bus subaddress 1 */
#define PCA9685_MODE1_SLEEP 0x10   /**< Low power mode. Oscillator off */
#define PCA9685_MODE1_AI 0x20      /**< Auto-Increment enabled */
#define PCA9685_MODE1_EXTCLK 0x40  /**< Use EXTCLK pin clock */
#define PCA9685_MODE1_RESTART 0x80 /**< Restart enabled */

// MODE2 bits
#define PCA9685_MODE2_OUTNE_0 0x01	/**< Active LOW output enable input */
#define PCA9685_MODE2_OUTNE_1 0x02	/**< Active LOW output enable input - high impedience */
#define PCA9685_MODE2_OUTDRV 0x04	/**< totem pole structure vs open-drain */
#define PCA9685_MODE2_OCH 0x08		/**< Outputs change on ACK vs STOP */
#define PCA9685_MODE2_INVRT 0x10	/**< Output logic state inverted */

#define PCA9685_I2C_ADDRESS 0x40      /**< Default PCA9685 I2C Slave Address */
#define PCA9685_FREQUENCY_OSCILLATOR 25000000 /**< Int. osc. frequency in datasheet */
#define PCA9685_OSC_STAB_TIME_US 500 // Delay to allow time for oscillator to stabilize 500 us
#define PCA9685_PRESCALE_MIN 3   /**< minimum prescale value */
#define PCA9685_PRESCALE_MAX 255 /**< maximum prescale value */
#define PCA9685_PRESCALE_DEF 200 // Default prescaler value | Datasheet Page : 25

class Pca9685
{
	public:
		enum errors 
		{
			dutyCycleTooSlow,	// Selected duty cycle is too big
			dutyCycleTooFast,	// Selected duty cycle is too small
			freqTooFast,		// Selected PWM frequency is too fast
			freqTooSlow,		// Selected PWM frequency is too slow
			freqIsNull,			// Oscillator frequency can't be 0 or inferior to 0
			freqOver50Mhz,		// Oscillator frequency cant't be higher than 50 MHz
			onDutySetTooBig,	// On Duty Cyle if greater than alowed maximum of 4095
			offDutySetTooBig,	// Off Duty Cyle if greater than alowed maximum of 4095
			onOffDutySetTooBig,	// Cumulation of the On & Off Duty Cyle if greater than alowed maximum of 4095
			onEqaulOff,			// On & Off count cylces can't be equal to each other
			dutyCycle100Prcnt,	// Duty cycle can't be greater than 100 percent
			portInvalid			// This port number doesn't exist
		}; 
		
		Pca9685(I2C* i2c);
		void setPwmFreq(float frequency);
		void setPwmRaw(uint8_t preScale);
		void turnAllOff();
		void turnAllOn();
		uint8_t compensatePrescale(uint8_t preScale);
		float getPwmFreq(); 
		void setOnDutyPercent(uint8_t ledNo, uint8_t percent);
		void setDutyRaw(uint8_t ledNo, uint16_t on, uint16_t off);
		void setOnOff(uint8_t ledNo, bool onOff);
		void confClk(bool internExtern, uint32_t freq);
		void setOutputMode(bool mode);
		void reset();
		void sleep();
		void wakeup();
		void throwError(errors errNo);
	private:
		uint8_t	m_currentLED; 
		uint8_t m_curMode;
		float m_currentPwmFreq; 
		uint8_t m_currentPrescale;
		uint8_t m_isExternalClock; 
		float m_oscillatorFreq; 
		I2C* i2c_pca9685;
};


#endif// _PCA9685_H_

