/*-------------------------------- PRST ------------------------------
----------------------- Pristupovy registracni system ----------------
-------------------------- prstd - system server ---------------------
**********************************************************************
*/

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <time.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include "common.h"

// Packet offsets defines
#define POS_SOURCE 0
#define POS_COMMAND 1
#define POS_DATA 2

#define BUFFOUT_CURRENT_ITEM mainbuff[(point_buf_out % MAIN_BUFFER_SIZE)]
#define BUFFOUT_CURRENT_ITEM_UPDATE() point_buf_out++

// Command defines
#define SERIN_SEND_SCANNED_FPR_CMD	0xA0
#define SERIN_SEND_ACTIVE_MODE_CMD	0xA1
#define SERIN_SEND_KEY_PRESSED_CMD	0xA2
#define SERIN_SEND_ACK				0xAA
#define SERIN_SEND_CARD_READ		0xA3
#define SERIN_ECHO_REPLY_CMD		0xA4
#define SERIN_SEND_SCAN_PROGRESS	0xA5
#define SERIN_SEND_ERR				0xAE

#define KEYBOARD_MODE_LEAVING 0x01
#define KEYBOARD_MODE_ALPHANUMERIC 0x02

// PATHS defines
#define INPIPE_PATH "/var/pipes/prstd_in"
#define OUTPIPE_PATH "/var/pipes/prstd_out"
#define MAINDBASE_PATH "/sdcard/test.db" // /sdcard/prst.db
#define DAILYDBASE_PATH "/var/tmp/daily"


extern serial_t* serial; 
FILE* pipka = NULL;
sem_t sem_full;
sem_t sem_empty;
unsigned int point_buf_in=0;
unsigned int point_buf_out=0;
char modificator = ' ';

void TimerFunction(){

	modificator=' ';	

}

void* IOReceive(void* ThreadData){
	unsigned char RxBuffer[280];
	static unsigned char state=0;
	unsigned int checksum=0, lenghth=0, CmdPos=0;
	unsigned char data;
	int i;
	int epd,filena=0;

	syslog(LOG_MAKEPRI(LOG_USER, LOG_DEBUG),"RECEIVE THREAD running");
	serial = NULL;

	serial = serial_open(SERIAL_PORT);
		if (!serial) {
			syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SERIAL Open failed");
			return((void*)1);
		}

	if (serial_setup(serial,SERIAL_BAUD_9600,SERIAL_BITS_8,SERIAL_PARITY_NONE,SERIAL_STOPBIT_1) != SERIAL_ERR_OK) {
			syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SERIAL Setup failed");
			return((void*)1);
		}

	filena = open(INPIPE_PATH, O_CREAT | O_RDONLY | O_NONBLOCK, 0777);
	if(filena == 0){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"INPIPE Open failed %s",strerror(errno));
		return((void*)1);
	}

	epd = epoll_create(3);
	struct epoll_event epe[2];
	memset(epe,0, sizeof(epe));
	epe[0].events = EPOLLIN;

	if(serial->fd)
		epe[0].data.fd = serial->fd;
	
	epe[1].events = EPOLLIN | EPOLLET;

	if(epoll_ctl(epd, EPOLL_CTL_ADD, serial->fd, &epe[0])){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SERIAL Poll add failed ! %s",strerror(errno));
		return((void*)1);
	}
	if(epoll_ctl(epd, EPOLL_CTL_ADD, filena, &epe[1])){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"INPIPE Poll add failed ! %s",strerror(errno));
		return((void*)1);
	}

	//------------- INFINITE BLOCKING POOL LOOP --------------------
	//--------------------------------------------------------------

	while(1){

		epoll_wait(epd, epe, 1, -1);
		if(epe[0].data.fd == serial->fd){
			serial_read(serial,&data,1);

			// Packet decode state machine
		    switch (state){

		    	case 0 : //Waiting the first packet byte 
					if(data==BORDER_P) state=1;
			  		lenghth=0;
		    	break;
		    	case 1 : //Waiting checksum high byte
		    		lenghth = ((((uint16_t)data)<<8) & 0xFF00);
		    		state = 2;
			
		    	break;

		    	case 2 :
		    		lenghth += data;
		    		if(lenghth>HOST_MAX_PACKET_SIZE){
		    			state=0;
		    		}
		    		else{
		    			state = 3;
		    			CmdPos=0;
		    		}
		    	break;

		    	case 3 :
					RxBuffer[CmdPos++]=data;
					if(CmdPos<=(lenghth-2))		
						checksum += data;
					if((CmdPos) >= lenghth){
						if(checksum != ((uint16_t)(((RxBuffer[CmdPos-2]<<8)&0xFF00)|(RxBuffer[CmdPos-1])))){
							syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SERIAL Checksum error !");
						}
						else { 	
							sem_wait(&sem_empty);
							mainbuff[point_buf_in % MAIN_BUFFER_SIZE][0] = FROM_SERIAL;					
							for(i=1;i<lenghth;i++)
								mainbuff[point_buf_in % MAIN_BUFFER_SIZE][i] = RxBuffer[i-1];			
							point_buf_in++;
							sem_post(&sem_full);
						}
						checksum=0;
						state=0;
		
					}
		    	break;
		    	}
		}
		else{ 
			sem_wait(&sem_empty);	
			data=read(filena,&mainbuff[point_buf_in % MAIN_BUFFER_SIZE][1],sizeof(RxBuffer));
			mainbuff[point_buf_in % MAIN_BUFFER_SIZE][0] = FROM_WEB;
			printf("Buffer %u \n",(point_buf_in % MAIN_BUFFER_SIZE));	
			point_buf_in++;
			sem_post(&sem_full);
		}
	}

	return(NULL);
}
  
//DBase callback function

static int dbCallback(void *NotUsed, int argc, char **argv, char **azColName){
	int i;
	char action = (char)NotUsed;

	if(action == DBASE_NAMERESOLVE_CODE){
	
		for(i=0; i<argc; i++){
   			printf("%s ", argv[i] ? argv[i] : "NULL");
			fprintf(pipka,"%s ", argv[i] ? argv[i] : "NULL");
   	 	}
  		printf("\n");
 		fprintf(pipka,"\n");
		fflush(NULL);	
	}

	if(action == DBASE_GETFPRTEMPATE_CODE){
		printf("User %s FPR %s",argv[0], argv[1]);
		ExpCom_DownloadFPR(atoi(argv[0]), argv[1]);
	}

    return 0;
}


//Main function  

int main(int argc, char **argv){
    	int rc;
    	pthread_t threads[1];
	
	
	int tmp = 0;
	char CurrentKeybmode = KEYBOARD_MODE_LEAVING;
	
	timer_t timer1;
	struct sigevent event;
	struct itimerspec time1;
	

	event.sigev_notify = SIGEV_THREAD;
    event.sigev_notify_function = TimerFunction;
    event.sigev_notify_attributes = NULL;
    
	if(timer_create(CLOCK_REALTIME, &event, &timer1)==-1){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"TIMER Create failed ! err: %s",strerror(errno));
		return(1);
	}

	time1.it_interval.tv_sec = 0;
    time1.it_interval.tv_nsec = 0;
    time1.it_value.tv_sec = 5;
    time1.it_value.tv_nsec = 0;


	//Semaphor init

	if(sem_init(&sem_full,0,0)==-1){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SEMAPHORE_FULL Create failed ! %s", strerror(errno));
		return(errno);
	}
	if(sem_init(&sem_empty,0,5)==-1){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"SEMAPHORE_EMPTY Create failed ! %s", strerror(errno));
		return(errno);
	}

	if(mkfifo(OUTPIPE_PATH, 0777)==-1){
		if(errno == EEXIST)
			syslog(LOG_MAKEPRI(LOG_USER, LOG_INFO),"OUTPIPE Already exists.");
		else{
			syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"OUTPIPE Create failed ! %s",strerror(errno));
			return(errno);
		}	
	}	

	if(mkfifo(INPIPE_PATH, 0777)==-1){
		if(errno == EEXIST)
			syslog(LOG_MAKEPRI(LOG_USER, LOG_INFO),"INPIPE Already exists.");
		else{
			syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"INPIPE Create failed ! %s",strerror(errno));
			return(errno);
		}
	}


	if ((tmp = fork ()) == -1) {
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"FORK failed ! %s",strerror(errno));
		exit (errno);
	}
	if (tmp == 0) {        
		execl ("/sdcard/vizulaizace", "vizulaizace","-qws", NULL);
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"EXEC failed ! %s",strerror(errno));
		exit (errno);
	}

	pipka = fopen(OUTPIPE_PATH, "w");
	if(pipka == NULL){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"OUTPIPE Open failed ! %s",strerror(errno));
		return(errno);
	}

	rc=DBase_Open(MAINDBASE_PATH);
	if(rc){
		syslog(LOG_MAKEPRI(LOG_USER, LOG_ERR),"DBASE Open failed ! %u",rc);
		return(rc);
	}

	DBase_CallbackRegister(dbCallback);
	DBase_AttachDaily(DAILYDBASE_PATH);

	pthread_create(&threads[0],NULL,&IOReceive,NULL);
  

//*******************************************************************
//Event-driven infinite loop --------------------------------------
//-------------------------------------------------------------------


while(1){
	sem_wait(&sem_full); //Wait for event

	// Requests from IO board
	if(BUFFOUT_CURRENT_ITEM[POS_SOURCE]==FROM_SERIAL){
		switch (BUFFOUT_CURRENT_ITEM[POS_COMMAND]){
	
			//Ack received
			case SERIN_SEND_ACK :
				Guard_regACK(BUFFOUT_CURRENT_ITEM[POS_DATA]);
			break;

			//Err received
			case SERIN_SEND_ERR :
				Guard_regERR(BUFFOUT_CURRENT_ITEM[POS_DATA],BUFFOUT_CURRENT_ITEM[POS_DATA+1]);
			break;
	
			//Switch active mode on or off
			case SERIN_SEND_ACTIVE_MODE_CMD :
				tmp=open("/dev/backlight", O_WRONLY | O_NONBLOCK, 0777);
	
				if(BUFFOUT_CURRENT_ITEM[POS_DATA])
					write(tmp,"1",1);	
				else
					write(tmp,"0",1);
				close(tmp);
			break;
	
			//Scanned fpr	
			case SERIN_SEND_SCANNED_FPR_CMD :
				printf("Scanned %u %u \n",BUFFOUT_CURRENT_ITEM[POS_DATA],BUFFOUT_CURRENT_ITEM[POS_DATA+1]);
				if(BUFFOUT_CURRENT_ITEM[POS_DATA+1]==0xFF){
					fprintf(pipka,"Otisk neznámého skauta\n");
					fflush(NULL);
					break;
				}	
				if(modificator < '5'){
					if(modificator ==' '){
						if(DBase_EntryTypeResolve(BUFFOUT_CURRENT_ITEM[POS_DATA+1]))
							modificator = '*';
						else 
							modificator = '#';
					}	
					DBase_LogEntry(BUFFOUT_CURRENT_ITEM[POS_DATA+1],'A',modificator);		
					modificator=' ';
				}	
				DBase_UserNameResolve(BUFFOUT_CURRENT_ITEM[POS_DATA+1]);
			break;

			//Pressed key
			case SERIN_SEND_KEY_PRESSED_CMD :
				if(CurrentKeybmode == KEYBOARD_MODE_ALPHANUMERIC){
					fprintf(pipka,"#%c\n", BUFFOUT_CURRENT_ITEM[POS_DATA+1]);
				}
				else{
					modificator = BUFFOUT_CURRENT_ITEM[POS_DATA+1];
					timer_settime( timer1, 0, &time1, NULL);
					fprintf(pipka,"$%c\n", modificator);
				}
				fflush(NULL);
			break;

			//Card read
			case SERIN_SEND_CARD_READ :
				//printf("Carded\n");
				BUFFOUT_CURRENT_ITEM[POS_COMMAND]='C';
				fwrite(&BUFFOUT_CURRENT_ITEM[POS_COMMAND], sizeof(char), 11, pipka);
				fflush(NULL);
			break;

			//Fingerprint scan progress
			case SERIN_SEND_SCAN_PROGRESS :
				//printf("Progress %u\n", BUFFOUT_CURRENT_ITEM[POS_DATA]);
				fprintf(pipka,"P%u\n", BUFFOUT_CURRENT_ITEM[POS_DATA]);
				fflush(NULL);
			break;

			//FPR template received
			default :
				//tmp=open("/var/tmp/template", O_CREAT |O_WRONLY | O_NONBLOCK, 0777);
				write(tmp,&BUFFOUT_CURRENT_ITEM[POS_COMMAND],278);
				close(tmp);

		}

	}

	// Requests from another processes
	//-------------------
	else if(BUFFOUT_CURRENT_ITEM[POS_SOURCE]==FROM_WEB){

		switch (BUFFOUT_CURRENT_ITEM[POS_COMMAND]){
			
			//Switch FPR off			
			case 'A' :
				ExpCom_SwitchFPR(0);
			break;
			
			//Switch FPR on
			case 'B' :
				ExpCom_SwitchFPR(1);
			break;
		
			//Start scanning process
			case 'S' :
				ExpCom_ScanFPR();
			break;
			
			//Store template in FPR 	
			case 'X' :
				DBase_GetFprTemplate(atoi((char*)&BUFFOUT_CURRENT_ITEM[POS_DATA]));
			break;	
	
			//Delete template from FPR
			case 'D' :
				ExpCom_DeleteFPR(atoi((char*)&BUFFOUT_CURRENT_ITEM[POS_DATA]));
			break;
	
			//Invoke exp-board bootload restart
			case 'Q' :
				ExpCom_BootloadReset();
			break;

			//Change keyboard mode
			case 'K' :
				if(BUFFOUT_CURRENT_ITEM[POS_DATA] == 'A'){
					ExpCom_SwitchKeybMode(keybmode_alpha);
					CurrentKeybmode = KEYBOARD_MODE_ALPHANUMERIC;
					fprintf(stdout,"Changed to ALPHA\n");
				}
				else if(BUFFOUT_CURRENT_ITEM[POS_DATA] == 'L'){
					CurrentKeybmode = KEYBOARD_MODE_LEAVING;
					ExpCom_SwitchKeybMode(keybmode_numeric);
					fprintf(stdout,"Changed to leaving\n");	
				}
				else{
					ExpCom_SwitchKeybMode(keybmode_numeric);
					CurrentKeybmode = KEYBOARD_MODE_ALPHANUMERIC;
					fprintf(stdout,"Changed to NUMERIC\n");
				}
			break;
		}
	}

	// Requests from GUI
	else if(BUFFOUT_CURRENT_ITEM[POS_SOURCE]==FROM_GUI){}
	// Unknown source
	else {}

BUFFOUT_CURRENT_ITEM[POS_COMMAND]=0; 

BUFFOUT_CURRENT_ITEM_UPDATE();
sem_post(&sem_empty);

}

pthread_join(threads[0],NULL);


serial_close(serial);
unlink(OUTPIPE_PATH);

return 0;
  }
