#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <sys/uio.h>
#include <time.h>

#define TS_PACKET_SIZE 188
#define NB_BUFF 1
#define PACKET_SIZE TS_PACKET_SIZE/NB_BUFF
#define MAX_PID_SIZE 40
#define TIMEOUT_SEC 300
#define TIMEOUT_USEC 0
#define DISPLAY 10
#define FIRST_TIMEOUT_DELAY 400

int countDisplay=0;

int pid[MAX_PID_SIZE]; //store pids (can store many similar pid)
int diff[MAX_PID_SIZE]; // store different pids only
int diff_current_pos=0; //current position of diff array
int pid_c_pos=0; // current position of pid array

unsigned int getB=0; // store the baud value
int sockfd=-1; // socket descriptor 
int port_serie=-1; // serial port descriptor

enum{
	SUCCEED=0,
	FAILED=1
};

enum{
	NONE_DATA=0,
	DATA
};

enum{
	UDP_MODE=0,
	FILE_MODE=1
};

///um232r bauds values
/*
 Compared the parameter to baud value existing.
 This function checked the parameter enter by the user
 args :
  bauds - the value to compared
 
 return :
  the good baud value otherwise a defaut value 
*/
unsigned int getBauds(long long int bauds)
{
	long long int setBauds=B1000000;
	getB=bauds;

	switch(bauds)
	{
		case 1200 :
			setBauds=B1200;
		break;
		case 2400 :
			setBauds=B2400;
		break;
		case 9600 :
			setBauds=B9600;
		break;
		case 19200 :
			setBauds=B19200;
		break;
		case 38400 :
			setBauds=B38400;
		break;
		case 57600 :
			setBauds=B57600;
		break;
		case 115200 :
			setBauds=B115200;
		break;
		case 230400 :
			setBauds=B230400;
		break;
		case 460800 :
			setBauds=B460800;
		break;
		case 500000 :
			setBauds=B500000;
		break;
		case 921600 :
			setBauds=B921600;
		break;
		case 1500000 :
			setBauds=B1500000;
		break;
		case 2000000 :
			setBauds=B2000000;
		break;
		case 3000000 :
			setBauds=B3000000;
		break;
		case 4000000 :
			setBauds=B4000000;
		break;
		default :
			printf("default bitrate value is setted\n");
			setBauds=B1000000;
			getB=1000000;
		 break;
	}
	
	return setBauds;
}

/*
 Write data in file
 args :
 data - data to write
 len - data length
 name - file name

*/
int writeData(char *data, int len,char *name)
{

	FILE *fp = fopen(name,"a+");
	int count=0;
	
	 count=fwrite(data,sizeof(char),len,fp);

	fclose(fp);
	
	if(count != len){
		printf("write : error\n");
		return FAILED;
	}
}

struct timespec time_start;
struct timespec time_stop;
unsigned long long int real_time=0;

void displayValues(unsigned char *buff,int nbdisplay)
{
	int i=0;
	printf("display %d :\n",countDisplay);
	countDisplay++;
	for(i=0;i<nbdisplay;i++)
	{
		printf("%d ",buff[i]);
	}

	printf("\n");
}

//read data on serial port and write data depending on output mode
int readDataAndWrite(int port_serie,int len,char *name,int bitrate,struct sockaddr_in *addr,int sockfd,int choix)
{
	int ok=DATA;
	int succ=FAILED;
	struct iovec readData[NB_BUFF];
	unsigned char *buff =calloc(len,1);
	struct termios options;

	tcflush(port_serie,TCIFLUSH);
	tcflush(port_serie,TCOFLUSH);
	fcntl(port_serie,F_SETFL,O_RDONLY);
	tcgetattr(port_serie,&options);
	cfsetispeed(&options,bitrate);
	cfsetospeed(&options,bitrate);
	options.c_cflag |=(CLOCAL | CREAD);
	options.c_cflag &= ~CSIZE;
	options.c_cflag &= ~CSTOPB;
	options.c_cflag |= CS8;
	options.c_iflag &= ~IXOFF;
	options.c_iflag &= ~IXON;
	options.c_iflag &= ~(IGNBRK);
	options.c_iflag &= ~(IGNPAR | IGNCR);
	options.c_iflag &= ~ (ICRNL);
	options.c_oflag &= ~( NLDLY | CRDLY | BSDLY | VTDLY | FFDLY);
	float time=((float)((PACKET_SIZE+2)*10))/((float)getB);
	options.c_cc[VMIN]=PACKET_SIZE;
	options.c_cc[VTIME]=time;
	int rv=0;

	struct timeval timeout;
	timeout.tv_sec=FIRST_TIMEOUT_DELAY;

	//set options now
	tcsetattr(port_serie,TCSANOW,&options);
	int j=0,i=0,nbbytes=0,count=0;

	clock_gettime(CLOCK_MONOTONIC,&time_start);


	while(ok > NONE_DATA)
	{
		ok=read(port_serie,buff,len);
		printf("received - %d\n",ok);	
		
		//send only if bytes received is more than or equal to 0
		if(ok > NONE_DATA)
	   	 {				
				if(choix == UDP_MODE){
					succ=sendto(sockfd,buff, TS_PACKET_SIZE, 0,(const struct sockaddr *)addr, sizeof(struct sockaddr_in));		
				}else if(choix == FILE_MODE){
					succ=writeData(buff,TS_PACKET_SIZE,name);			
				}
		}else if(ok < NONE_DATA){
			fprintf(stderr,"error :read()\n");
			succ= FAILED;
			ok=-1;
		}else{
			clock_gettime(CLOCK_MONOTONIC,&time_stop);
			real_time=(time_stop.tv_sec - time_start.tv_sec);
			printf("ts done - %lu s\n",real_time-TIMEOUT_SEC);
			succ=FAILED;
			ok=-1;
		}
	   		
	}	
	return succ;
}


// free all when the signal is received
void free_allv(int n)
{
	//free and close all
	if(sockfd >= 0)
		close(sockfd);
	if(port_serie >= 0)
		close(port_serie);
	
	clock_gettime(CLOCK_MONOTONIC,&time_stop);
	real_time =(time_stop.tv_sec - time_start.tv_sec);
	printf("finished - %lu s\n",real_time-TIMEOUT_SEC);
	exit(EXIT_FAILURE);
}

/*
	open serial port : /dev/ttyUSB1 and send data to either file or socket
*/
int main(int argc, char **argv)
{
	int packet_size= 1*PACKET_SIZE; //packet size : 188 bytes
   	struct sockaddr_in addr; // address
	char *tsfile=NULL; // tsfile to store data read
	char *portfile=NULL;
	int choix=UDP_MODE; // output mode
	int bitrate=0; //Here bitrate == baudrate (1 baud == 1 bit)
	int succ=FAILED;
	
	//signals
	signal(SIGINT,free_allv);
	signal(SIGQUIT,free_allv);
	
	if(argc != 4 && argc != 5)
	{
		fprintf(stderr,"Usage : %s serial_port IPaddress port bitrate\n",argv[0]);
		fprintf(stderr,"Usage : %s serial_port tsfile bitrate\n",argv[0]);
		return  FAILED;
	}else if(argc == 5){
		addr.sin_family = AF_INET;
		choix=UDP_MODE;
		portfile=argv[1];
		addr.sin_addr.s_addr = inet_addr(argv[2]);
		addr.sin_port = htons(atoi(argv[3]));
		bitrate = getBauds(atoi(argv[4]));
		printf("bitrate - %d\n",getB);
	}else{
		choix=FILE_MODE;
		portfile=argv[1];
		tsfile=argv[2];
		bitrate=getBauds(atoi(argv[3]));
		printf("bitrate - %d\n",getB);
	}
	
	//initialize socket
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    	if(sockfd < 0) {
		fprintf(stderr,"socket : error \n");
	return  FAILED;
    } 
		
	//suppress file if exists and place the descriptor to initial position (=0)
	if(tsfile !=NULL)
	{
		FILE *fp = fopen(tsfile,"w+");
		if(fp != NULL)
			fclose(fp);
	}
	
	//open serial port
	port_serie=open(portfile,O_RDONLY | O_NOCTTY);
	if(port_serie < 0)
	{
		fprintf(stderr,"failed to open %s\n",portfile);
		goto free_port;
	}
	
	//read and write data 
	succ=readDataAndWrite(port_serie,packet_size,tsfile,bitrate,&addr,sockfd,choix);
	
//free and close all
free_all :
	if(sockfd >= 0)
		close(sockfd);
free_port :
	if(port_serie >= 0)
		close(port_serie);

	return succ;
}
