#include "gpio.hpp" 
#include "../../systems/systemCall.h"

#include <fstream>
#include <iostream>

#define PIN_COUNT 26

class Gpio::gpioImpl
{
	public:
		uint8_t pinCount = PIN_COUNT; 
		char buffer[50];
		uint8_t currentPin = 0; 
		int ck = 0; 

	    std::ifstream bufFile; 
	
		pinDefinition pins[PIN_COUNT + 1]; 
		
		gpioImpl()
		{
			checkCurrentPins();
		}
		
		void setFunction(uint8_t gpioNo, Gpio::pinFunction function)
		{
			pinInitiate(gpioNo);
			
			switch(pins[gpioNo].function)
			{
				case passive:
					pins[gpioNo].function = function; 
					break;
				case gpio:
					pins[gpioNo].function = function; 
					break;
				case i2c:
					pins[gpioNo].function = function; 
					break;
				case spi:
					pins[gpioNo].function = function; 
					break;
				case pwm:
					pins[gpioNo].function = function; 
					break;
				case pcm:
					pins[gpioNo].function = function; 
					break;
				case uart:
					pins[gpioNo].function = function; 
					break;
				case eeprom:
					pins[gpioNo].function = function; 
					break;
				case clk:
					pins[gpioNo].function = function; 
					break;
			}
		}

		void setMode(uint8_t gpioNo, Gpio::pinMode mode)
		{
			switch(mode)
			{
				case input:
					sprintf(buffer, "echo \"in\" > /sys/class/gpio/gpio%d/direction",gpioNo);
					pins[gpioNo].mode = mode; 
					system(buffer);
					break; 
				case output:
					sprintf(buffer, "echo \"out\" > /sys/class/gpio/gpio%d/direction",gpioNo);
					pins[gpioNo].mode = mode; 
					system(buffer);
					break; 
				case analog:
					std::cout << "No analog pins are awailable for raspberry" << std::endl;
					break; 
				case alternate:
				default :
					throwError(__LINE__,notValidMode); 
					break; 

			}
		}
		
		void setOutputState(uint8_t gpioNo, Gpio::pinState state)
		{	
			std::cout << "This version of the code doesn't allow pin output Stage configuration for the raspberry" << std::endl;
		}
	 
		void setPullUpDonw(uint8_t gpioNo, Gpio::pinPullUpDown resistance)
		{
			std::cout << "This version of the code doesn't allow Pull up or Pull donw configurations for the raspberry" << std::endl;
		}
	 
		void setSpeed(uint8_t gpioNo, Gpio::pinSpeed speed)
		{
			std::cout << "This version of the code doesn't allow pin speed changes for the raspberry" << std::endl;
		}
	 
		void config(uint8_t gpioNo,Gpio::pinFunction function, Gpio::pinMode mode, Gpio::pinState state, Gpio::pinPullUpDown resistance, Gpio::pinSpeed speed)
		{
			pinInitiate(gpioNo);
			setFunction(gpioNo, function);
			setMode(gpioNo, mode); 
		}
	 
		
		int8_t readPin(uint8_t gpioNo)
		{
			if(pins[gpioNo].mode == undefined)
			{
				throwError(__LINE__,pinNotDeclared);	
			}
			else
			{
				if(pins[gpioNo].function == gpio)
				{
					sprintf(buffer,"/sys/class/gpio/gpio%d/value",gpioNo);
					bufFile.open(buffer,std::ios::in);
					if(bufFile.is_open())
					{
						bufFile.read(buffer,3);  
						bufFile.read(buffer,1);  

						if(buffer[0] == '1')
						{
							bufFile.close();
							return 1; 
						}
						else if (buffer[0] == '0')
						{
							bufFile.close();
							return 0; 
						}
					}
				}
				else
				{
					throwError(__LINE__, pinNotGpio);
				}
			}
			return 2;	
		}

		void writePin(uint8_t gpioNo, uint8_t value)
		{
			if(pins[gpioNo].mode == undefined)
			{
				throwError(__LINE__,pinNotDeclared);	
			}
			else
			{
				if(pins[gpioNo].function == gpio)
				{
					if(value == 0) 
					{
						sprintf(buffer, "echo \"%d\" > /sys/class/gpio/gpio%d/value",value,gpioNo);
						system(buffer);
					}
					else if (value == 1)
					{
						sprintf(buffer, "echo \"%d\" > /sys/class/gpio/gpio%d/value",value,gpioNo);
						system(buffer);
					}
					else 
					{
						throwError(__LINE__,notValidOut); 
					}
				}
				else
				{
					throwError(__LINE__, pinNotGpio);
				}
			}
		}


		uint16_t readRange(uint8_t start, uint8_t stop)
		{
			return 0; 	
		}
	 
		void writeRange(uint8_t start, uint8_t stop, uint16_t value)
		{
		
		}
	 

		void pinInitiate(uint8_t gpioNo)
		{
			if(gpioNo <= PIN_COUNT)
			{
				sprintf(buffer,"[ -d /sys/class/gpio/gpio%d ]",gpioNo); // retunrs null if directory exists
				ck = system(buffer);
				if( ck != 0) 
				{
					sprintf(buffer, "echo \"%d\" > /sys/class/gpio/export",gpioNo);
					ck = system(buffer);
				}
				else
				{
					std::cout << "Pin already initiated"<< std::endl;	
					std::cout << "     - ";	
					pinPrintInfo(gpioNo);
				}
			}
			else 
			{
				throwError(__LINE__,pinOutOfRange);
			}
		}

		void pinUninitiate(uint8_t gpioNo)
		{
			if(gpioNo <= PIN_COUNT)
			{
				sprintf(buffer,"[ -d /sys/class/gpio/gpio%d ]",gpioNo); // retunrs null if directory exists
				ck = system(buffer);
				if( ck == 0) 
				{
					sprintf(buffer, "echo \"%d\" > /sys/class/gpio/unexport",gpioNo);
					system(buffer);
				}
				else
				{
					throwError(__LINE__,pinNotDeclared); 
				}
			}
			else 
			{
				throwError(__LINE__,pinOutOfRange);
			}
		}

		void checkCurrentPins()
		{
			uint8_t i = 0; 
			
			for( i = 0; i < PIN_COUNT; i++) 
			{
				sprintf(buffer,"[ -d /sys/class/gpio/gpio%d ]",i); // retunrs null if directory exists
				ck = system(buffer);
				if( ck == 0) 
				{
					std::cout << "Pin: "<< unsigned(i) << " Exists " << std::endl; 					
					sprintf(buffer,"/sys/class/gpio/gpio%d/direction",i); 
					bufFile.open(buffer,std::ios::in);
					if(bufFile.is_open())
					{
						bufFile.read(buffer,3);  

						if(buffer[0] == 'i')
						{
							if (buffer[1] == 'n')
							{
								pins[i].mode = input; // init of the direction and the rest as a stanard gpio pin. 
								pins[i].state = openDrain;
								pins[i].pud = none;
								pins[i].speed = normal;
								pins[i].interrupt = disabled;
								pins[i].function = gpio;
							}
						}
						else if (buffer[0] == 'o')
						{
							if(buffer[1] == 'u')
							{
								if(buffer[2] == 't')
								{
									pins[i].mode = output;
									pins[i].state = openDrain;
									pins[i].pud = none;
									pins[i].speed = normal;
									pins[i].interrupt = disabled;
									pins[i].function = gpio;
								}
							}
						}
						bufFile.close();
					}
				}
			}
		}

		void pinList() // Lists allthe pins whom their mode in not set to undefined. 
		{
			uint8_t i = 0; 

			for(i = 0; i < PIN_COUNT; i++)
			{
				if(pins[i].mode != undefined)
				{
					std::cout << "|Pin "<< unsigned(i) <<" already exists and will be initialized as default GPIO|" << std::endl; 
					pinPrintInfo(i);
				}
			}
		}

		void pinPrintInfo(uint8_t gpioNo) // a graphical arrangement to see the cunnrently acive pins
		{
			std::cout << "Pin No: " << unsigned(gpioNo); 
			switch(pins[gpioNo].mode)
			{

				case undefined:
					std::cout << "| Mode: Undefined\t";
					break;
				case input: 
					std::cout << "| Mode: Input\t";
					break;
				case output: 
					std::cout << "| Mode: Output\t";
					break;
				case analog: 
					std::cout << "| Mode: Analog\t";
					break;
				case alternate:
					std::cout << "| Mode: Alternate\t";
					break;

			}

			switch(pins[gpioNo].state)
			{

				case floating:
					std::cout << "| State: Floating ";
					break;
				case pushPull: 
					std::cout << "| State: Push-Pull ";
					break;
				case openDrain: 
					std::cout << "| State: Open Drain ";
					break;

			}
			
			switch(pins[gpioNo].pud)
			{

				case none:
					std::cout << "| Pud: None ";
					break;
				case pullUp: 
					std::cout << "| Pud: Pull-up ";
					break;
				case pullDown: 
					std::cout << "| Pud: Pull-down ";
					break;

			}

			switch(pins[gpioNo].speed)
			{

				case slow:
					std::cout << "| Speed: Slow ";
					break;
				case normal:
					std::cout << "| Speed: Normal ";
					break;
				case fast:
					std::cout << "| Speed: Fast ";
					break;
				case veryFast:
					std::cout << "| Speed: Very Fast ";
					break;

			}

			switch(pins[gpioNo].interrupt)
			{

				case disabled:
					std::cout << "| Interrupt: No ";
					break;
				case enabled:
					std::cout << "| Interrupt: Yes ";
					break;

			}

			switch(pins[gpioNo].function)
			{
				case passive:
					std::cout << "| Function: NONE";
					break;
				case gpio:
					std::cout << "| Function: GPIO";
					break;
				case i2c:
					std::cout << "| Function: I2C";
					break;
				case spi:
					std::cout << "| Function: SPI";
					break;
				case pwm:
					std::cout << "| Function: PWM";
					break;
				case pcm:
					std::cout << "| Function: PCM";
					break;
				case uart:
					std::cout << "| Function: UART";
					break;
				case eeprom:
					std::cout << "| Function: EEPROM";
					break;
				case clk:
					std::cout << "| Function: CLK";
					break;

			}
			std::cout << "|" << std::endl; 
		}

		void throwError(uint16_t line, Gpio::errors errNo)
		{
			std::cout << "Error GPIO.C | Code Line: "<< unsigned(line) << " | Error No: "<< errNo << " | Description: "; 
			switch(errNo)
			{
			
				case notValidMode:
					std::cout << "notValidMode";
					break;
				case notValidOut:
					std::cout << "notValidOut";
					break;
				case pinOutOfRange:
					std::cout << "pinOutOfRange";
					break;
				case pinNotDeclared:
					std::cout << "pinNotDeclared";
					break;
				case pinNotReachable:
					std::cout << "pinNotReachable";
					break;
				case pinNoPullUpDown:
					std::cout << "pinNoPullUpDown";
					break;
				case pinNotAnalog:
					std::cout << "pinNotAnalog";
					break;
				case pinNotDigital: 
					std::cout << "pinNotDigital";
					break;
				case pinTooFast:
					std::cout << "pinTooFast";
					break;
				case pinBocked:
					std::cout << "pinBocked";
					break;
				case pinAlreadyUsed:
					std::cout << "pinAlreadyUsed";
					break;
				case pinNotGpio:
					std::cout << "pinAlreadyUsed";
					break;
				default: 
					std::cout << "Unhandled Error";
					break;

			}
			std::cout << " |" << std::endl;
		}
};

Gpio::Gpio():gpioPimpl(new gpioImpl()){}
Gpio::~Gpio(){}

void Gpio::setFunction(uint8_t gpioNo, Gpio::pinFunction function)
{
	return gpioPimpl->setFunction(gpioNo, function); 
}

void Gpio::setMode(uint8_t gpioNo, Gpio::pinMode mode)
{
	return gpioPimpl->setMode(gpioNo, mode); 
}
 
void Gpio::setOutputState(uint8_t gpioNo, Gpio::pinState state)
{
	return gpioPimpl->setOutputState(gpioNo,state);
}
 
void Gpio::setPullUpDonw(uint8_t gpioNo, Gpio::pinPullUpDown resistance)
{
	return gpioPimpl->setPullUpDonw(gpioNo,resistance);
}
 
void Gpio::setSpeed(uint8_t gpioNo, Gpio::pinSpeed speed)
{
	return gpioPimpl->setSpeed(gpioNo, speed); 
}
 
void Gpio::config(uint8_t gpioNo,Gpio::pinFunction function, Gpio::pinMode mode, Gpio::pinState state, Gpio::pinPullUpDown resistance, Gpio::pinSpeed speed)
{
	return gpioPimpl->config(gpioNo,function ,mode,state,resistance,speed);
}
 
uint8_t Gpio::readPin(uint8_t gpioNo)
{
	return gpioPimpl->readPin(gpioNo);
}

void Gpio::writePin(uint8_t gpioNo, uint8_t value)
{
	return gpioPimpl->writePin(gpioNo, value);
}

uint16_t Gpio::readRange(uint8_t start, uint8_t stop)
{
	return gpioPimpl->readRange(start,stop); 
}
 
void Gpio::writeRange(uint8_t start, uint8_t stop, uint16_t value)
{
	return gpioPimpl->writeRange(start,stop,value);
}
 
void Gpio::pinInitiate(uint8_t gpioNo)
{
	return gpioPimpl->pinInitiate(gpioNo);
}

void Gpio::pinUninitiate(uint8_t gpioNo)
{
	return gpioPimpl->pinUninitiate(gpioNo);
}

void Gpio::checkCurrentPins()
{
	return gpioPimpl->checkCurrentPins();
}

void Gpio::pinList()
{
	return gpioPimpl->pinList();
}

void Gpio::pinPrintInfo(uint8_t gpioNo)
{
	return gpioPimpl->pinPrintInfo(gpioNo);
}

void Gpio::throwError(uint16_t line, Gpio::errors error)
{
	return gpioPimpl->throwError(line, error);
}
