/*
 * TODO : output mde selection
 * TODO : Implement chainning function to use it with more than one module 
 * 
 *
 *
 *
 *
 */

#include "pca9685.h"


Pca9685::Pca9685(I2C* i2c)
{
	i2c_pca9685 = i2c; 
	m_isExternalClock = 0; 	
	m_oscillatorFreq = PCA9685_FREQUENCY_OSCILLATOR;
	turnAllOff();
	setPwmFreq(1000); 
	m_currentLED = PCA9685_LED_PWM_REG_START; 
}

/*Refere to datasheet page 25 
 *
 *
 *						osc_clock 
 * prescale vlaue = _______________________ - 1
 *						4096 * update_rate
 *
 */

void Pca9685::setPwmFreq(float frequency)
{	
	if(frequency > 1526)
	{
		frequency = 1526; 
		throwError(freqTooFast); 
	}
	else if (frequency == 1526) // Rounding correction
	{
		frequency -= 1;
	}
	
	if(frequency < 24)
	{
		frequency = 24; 
		throwError(freqTooSlow); 
	}

	m_currentPrescale = round(((m_oscillatorFreq / ( 4096 * frequency))) - 1);

	
	setPwmRaw(m_currentPrescale);
}


void Pca9685::turnAllOff()
{
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS,PCA9685_ALL_OFF_H, 16 );
}
void Pca9685::turnAllOn()
{
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS,PCA9685_ALL_ON_H, 16 );
}
//The impresition leves of thise device is out from this world this is a crude way from me trying to mitigate de awfullness. 
//Tested with other module all to react the same 
//maybe an external Clock could help with that but MCU level precision will never be reached !! Too sad
uint8_t Pca9685::compensatePrescale(uint8_t preScale)
{
	uint8_t compensation = 0;

	if(preScale > 236)
	{
		compensation = 12;
	}
	else if(preScale > 216 )
	{
		compensation = 11;
	}
	else if(preScale > 200)
	{
		compensation = 10;
	}
	else if(preScale > 178)
	{
		compensation = 9;
	}
	else if(preScale > 157)
	{
		compensation = 8;
	}
	else if(preScale > 134)
	{
		compensation = 7;
	}
	else if(preScale > 114)
	{
		compensation = 6;
	}
	else if(preScale > 93)
	{
		compensation = 5;
	}
	else if(preScale > 71)
	{
		compensation = 4;
	}
	else if(preScale > 52)
	{
		compensation = 3;
	}
	else if(preScale > 32)
	{
		compensation = 2;
	}
	else if(preScale > 9)
	{
		compensation = 1;
	}
	return preScale - compensation;
}


// direct register acess and raw value to set PWM
// Datashhet page 25 
// FEh | PRE_SCALE7:0 | PRE_SCALE[7:0] | R/W | 0001 1110 | prescaler to program the PWM output frequency (default is 200 Hz)
void Pca9685::setPwmRaw(uint8_t preScale)
{
	m_currentPrescale = compensatePrescale(preScale);
	if(m_currentPrescale < 3 )
	{
		m_currentPrescale = 3; 
		throwError(freqTooFast); 
	}
	else if(m_currentPrescale > 255)
	{
		m_currentPrescale = 255; 
		throwError(freqTooSlow); 
	}
	//std::cout << unsigned(m_currentPrescale) << " " << std::endl;
	sleep(); 
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, PCA9685_REG_PRESCALE, m_currentPrescale);
	wakeup(); 
}


/*Refere to datasheet page 25 
 *
 *
 *				osc_clock 
 * Freq = _______________________
 *			4096 * (prescaler -1)
 *
 */
float Pca9685::getPwmFreq()
{
	m_currentPrescale = compensatePrescale(i2c_pca9685->readByte(PCA9685_I2C_ADDRESS,PCA9685_REG_PRESCALE)); 
	m_currentPwmFreq = (m_oscillatorFreq / (4096 * ((float)m_currentPrescale + 1)));
//	std::cout << m_currentPwmFreq << std::endl;
	return m_currentPwmFreq;
}

/*
 *The duty cylce is set in a special fation, rather than being relation between on and off time it is the timing definition of their ocurances. 
 * ON Meas after how many cycle counts from the PWM start will the risign edge occur 
 * OFF Means after heo many cycle counts from the PWM start will the falling edge will occur
 * Than means : ON can't be equal to OFF anf OFF cna't be smaller than off 
 * 4096 as value is not a typo error it is used to turn one led fully on or off. Refere to datasheet page 21
 */
void Pca9685::setDutyRaw(uint8_t ledNo, uint16_t on, uint16_t off)
{
	if(ledNo > 15)
	{
		throwError(portInvalid); 
	}

	if(on > 4095)
	{
		on = 4096; 
		off = 0; 
		throwError(onDutySetTooBig); 
	}
	
	if(off > 4095)
	{
		off = 4096; 
		on = 0; 
		throwError(offDutySetTooBig); 
	}

	if(on + off > 4095)
	{
		off = 4096; 
		on = 0; 
		throwError(onOffDutySetTooBig); 
	}
	
	if(on == off)
	{
		off = 4096; 
		on = 0; 
		throwError(onEqaulOff); 
	}

	if(off == 0)// if there is no falling edge that measn that the signal is allways off 
	{
		off = 4096; // set to fully off
		on = 0; 
	}

	m_currentLED = PCA9685_LED_PWM_REG_START + (ledNo * PCA9685_LED_NEXT_OFFSET); 
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED, on & 0xFF);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_ON_H_OFFSET, on >> 8);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_OFF_L_OFFSET, off & 0xFF);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_OFF_H_OFFSET, off >> 8);
}


void Pca9685::setOnDutyPercent(uint8_t ledNo, uint8_t percent)
{
	uint16_t onLength = 0; 
	if(ledNo > 15)
	{
		throwError(portInvalid); 
	}

	if(percent <= 100)
	{
		if(percent == 0)
		{
			setOnOff(ledNo, 0);
		}
		else if(percent == 100)
		{
			setOnOff(ledNo, 1);
		}
		else 
		{
			onLength = (4095 * percent)	/ 100; 
			setDutyRaw(ledNo,0,onLength);
		}
	}
	else 
	{
		throwError(dutyCycle100Prcnt); 
	}
}

//turns the given Port fully on or foo using the special value of 4096
void Pca9685::setOnOff(uint8_t ledNo, bool onOff)
{
	uint16_t on, off = 0; 		
	
	if(ledNo > 15)
	{
		throwError(portInvalid); 
	}

	if(onOff)
	{
		off = 0; 
		on = 4096; 
	}
	else 
	{
		off = 4096; 
		on = 0; 
	}
	
	m_currentLED = PCA9685_LED_PWM_REG_START + (ledNo * PCA9685_LED_NEXT_OFFSET); 
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED, on & 0xFF);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_ON_H_OFFSET, on >> 8);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_OFF_L_OFFSET, off & 0xFF);
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, m_currentLED + PCA9685_PWM_OFF_H_OFFSET, off >> 8);
}


//Sets if internal or External clok would be used. 
//if external clok will be used user must define it's frequency "uint32_t freq" in Hz
//The maximum supported external clok frequency if 50 Mhz
//Defaul intern Clok is 25 Mhz
void Pca9685::confClk(bool internExtern, uint32_t freq)
{
	if(internExtern) 
	{
		m_isExternalClock = 0; 	
	}
	else 
	{
		m_isExternalClock = 1; 
		
		if(freq > 0)
		{
			if(freq <= 50000000)
			{
				m_oscillatorFreq = freq;
			}
			else
			{
				throwError(freqOver50Mhz); 
			}
		}
		else
		{
			throwError(freqIsNull); 
		}
	}
}
//TODO
void Pca9685::setOutputMode(bool mode)
{
	
}

void Pca9685::reset()
{
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, PCA9685_REG_MODE1, PCA9685_MODE1_RESTART);
}

void Pca9685::sleep()
{
	m_curMode = i2c_pca9685->readByte(PCA9685_I2C_ADDRESS,PCA9685_REG_MODE1);
	m_curMode = m_curMode | PCA9685_MODE1_SLEEP; 	
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, PCA9685_REG_MODE1, m_curMode);
}

void Pca9685::wakeup()
{
	m_curMode = i2c_pca9685->readByte(PCA9685_I2C_ADDRESS,PCA9685_REG_MODE1);
	m_curMode = m_curMode & ~PCA9685_MODE1_SLEEP; 	
	i2c_pca9685->writeWord(PCA9685_I2C_ADDRESS, PCA9685_REG_MODE1, m_curMode);
	usleep(PCA9685_OSC_STAB_TIME_US); // refer to datasheet page 14
}

void Pca9685::throwError(Pca9685::errors errNo)
{	
	#ifdef LINUX
	std::cout << "Linux pca9685 Error" << std::endl; 
	exit(1); 
	#endif
}
