#include "i2c.hpp"

#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

class I2C::i2cImpl
{
	public:
		int16_t deviceDescriptor; 
		uint8_t device_address;
		uint8_t send_buffer[32];
		uint8_t recieve_buffer[32];
		uint8_t blocks; 
		uint8_t channel; 
		uint8_t mode;
		uint8_t status; 
		i2cImpl()
		{
			char filename[20];
			
			snprintf(filename, 19, "/dev/i2c-%d", 1);

			deviceDescriptor = open(filename, O_RDWR);
			if (deviceDescriptor < 0) {
				throwError(initFailed);	
			}
		}
		uint8_t readByte(const uint8_t& address, const uint8_t& reg)
		{
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}
			
			writeByte(address,reg); //Initiate a write to indicate the desired register to read

			if (read(deviceDescriptor, recieve_buffer, 1) != 1)  // An then initare a read request of 1 byte
			{
				throwError(writeFailed); 	
			} 
			return recieve_buffer[0] ;
		}
	 
		uint16_t readWord(const uint8_t& address, const uint8_t& reg)
		{
			uint16_t result = 0 ;
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}

			writeByte(address,reg); //Initiate a write to indicate the desired register to read
			
			if (read(deviceDescriptor, recieve_buffer, 2) != 2)  // An then initare a read request of 2 bytes
			{
				throwError(writeFailed); 	
			} 
			result = (recieve_buffer[0] << 8) + recieve_buffer[1] ;
			return result ; 
		}
	 
		uint8_t writeByte(const uint8_t& address, const uint8_t& data)
		{
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}
			
			send_buffer[0] = data; 

			if ((write(deviceDescriptor, send_buffer, 1)) != 1)
			{	
				throwError(writeFailed); 	
			}
			return 0; 
		}
	 
		uint8_t writeWord(const uint8_t& address, const uint8_t& reg, const uint8_t& data)
		{
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}
			
			send_buffer[0] = reg; 
			send_buffer[1] = data;

			if ((write(deviceDescriptor, send_buffer, 2)) != 2)
			{	
				throwError(writeFailed); 	
			}
			return 0; 
		}
		
		void writeBuffer(const uint8_t& address, const uint8_t* buffer, uint8_t len)
		{
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}
			
			if ((write(deviceDescriptor,buffer, len)) != len)
			{	
				throwError(writeFailed); 	
			} 
		}
		
		void readBuffer(const uint8_t& address, const uint8_t* buffer, uint8_t len)
		{
			uint16_t result = 0 ;
			if (ioctl(deviceDescriptor, I2C_SLAVE, address) < 0) 
			{
				throwError(falseAddrs);	
			}

			writeByte(address,buffer[0]); //Initiate a write to indicate the desired register to read
			
			if (read(deviceDescriptor, recieve_buffer, len) != len)  // An then initare a read request of 2 bytes
			{
				throwError(writeFailed); 	
			} 
		}

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

I2C::I2C():i2cPimpl(new i2cImpl()){}

I2C::~I2C(){}

uint8_t I2C::readByte(const uint8_t& address, const uint8_t& reg)
{
	return i2cPimpl->readByte(address,reg); 
} 

uint16_t I2C::readWord(const uint8_t& address, const uint8_t& reg)
{
	return i2cPimpl->readWord(address,reg);
} 

uint8_t I2C::writeByte(const uint8_t& address, const uint8_t& data)
{
	return i2cPimpl->writeByte(address,data);
} 

uint8_t I2C::writeWord(const uint8_t& address, const uint8_t& reg, const uint8_t& data)
{
	return i2cPimpl->writeWord(address,reg,data);
}	

void I2C::writeBuffer(const uint8_t& address, const uint8_t* buffer, uint8_t len)
{
	return i2cPimpl->writeBuffer(address,buffer,len);
}	

void I2C::readBuffer(const uint8_t& address, const uint8_t* buffer, uint8_t len)
{
	return i2cPimpl->readBuffer(address,buffer,len); 
}	

void I2C::throwError(I2C::errors errNo)
{
	i2cPimpl->throwError(errNo);
} 	
