
#include <pthread.h>
#include <dirent.h>
#include <sys/stat.h>

#define MAX_QUEUE_SIZE			65535
#define MAX_LOG_BUFFER_LEN		1024
#define MAX_FILENAME_LEN		256
#define MAX_CHANNEL			100
#define MAX_IP_ADDRESS_LEN		18
#define MAX_LOG_KEY_LEN			17
#define MAX_VOICE_TYPE_LEN		10

#define FILE_PROCESSING			1
#define DIR_PROCESSING			2

#define EMPTY_STATUS			0
#define WAIT_STATUS			1
#define RECOG_STATUS			2
#define FINISH_STATUS			3

#define AUDIO_TYPE_WAV_FORMAT		".wav"
#define AUDIO_TYPE_PCM_FORMAT		".pcm"
#define AUDIO_TYPE_ALAW_FORMAT		".alaw"
#define AUDIO_TYPE_MULAW_FORMAT		".mulaw"

#include "vstapi_e2e_ast.h"

typedef struct _file_list
{
	int status;
	char filename[MAX_FILENAME_LEN];
	char out_name[MAX_FILENAME_LEN];
} FILE_LIST;

typedef struct _recv_arg
{
	int sockfd;
	int channel;
	FILE *output;
} RECV_ARG;

typedef struct _arg
{
	int channel;
} ARG;

FILE_LIST fList[MAX_QUEUE_SIZE];

int STT_PORT;
int Channel;
int file_list_count;
int wait_file_count;
char STT_IP_ADDR[MAX_IP_ADDRESS_LEN];
char STT_LOG_KEY[MAX_LOG_KEY_LEN];
char STT_VOICE_TYPE[MAX_VOICE_TYPE_LEN];
char TargetName[MAX_FILENAME_LEN];
char LOG_FILENAME[MAX_FILENAME_LEN];

pthread_mutex_t LIST_LOCK;
pthread_mutex_t COUNT_LOCK;
pthread_mutex_t LOG_LOCK;

FILE *output[MAX_CHANNEL];
void InitData(void);
void file_process(char *Name);
void dir_process(int Ch);
void SaveFileList( char *path, char *filename );
void SearchDirectory( char *dir_name );
void *recv_thread(void *data);
void *recog_thread(void *data);
void create_log_filename(void);
void debug_print_argument(int Ch, int Op);
void print_help_message(char *program);
void write_service_log( const char *format, ... );
int find_audio_file(char *name);

void print_help_message(char *program)
{
	printf("\n====================================================================================\n");
	printf(" %s [IP Address] [Port] [Channel] [Option] [Filename] [File Type] [Log Key]\n", program);
	printf("  - [IP Address]     : STT Server IP Address\n");
	printf("  - [Port]           : STT Server Port Number\n");
	printf("  - [Channel]        : Multi Processing Channel ( max 100 channel )\n");
	printf("  - [Option]         : Voice File Option\n");
	printf("     > FILE ---------- Processing for each File\n");
	printf("     > DIR ----------- Processing for Directory\n");
	printf("  - [Filename]       : Voice File Name\n");
	printf("  - [File Type]      : Voice File Yype\n");
	printf("     > PCM\n");
	printf("     > WAV\n");
	printf("     > ALAW\n");
	printf("     > MULAW\n");
	printf("  - [Log Key]        : Primary Key ( max 16 byte )\n");
	printf("====================================================================================\n\n");
}

void debug_print_argument(int Ch, int Op)
{
	printf("\n--------------------------------------------\n");
	printf(" STT IP Address     : %s\n", STT_IP_ADDR);
	printf(" STT PORT Number    : %d\n", STT_PORT);
	printf(" STT Client Channel : %d\n", Ch);
	printf(" FILE Option        : %s\n", (Op == FILE_PROCESSING) ? "FILE" : "DIRECTORY");
	printf(" Target Name        : %s\n", TargetName);
	printf(" FILE Type          : %s\n", STT_VOICE_TYPE);
	printf(" STT Log Key        : %s\n", STT_LOG_KEY);
	printf("--------------------------------------------\n\n");
}

void write_service_log( const char *format, ... )
{
	FILE *fp;
	struct tm tm;
	struct timeval tv;
	time_t now;

	pthread_mutex_lock( &LOG_LOCK );
	
	if (( fp = fopen( LOG_FILENAME, "at" ) ) != NULL )
	{
		va_list args;
		char 	buffer[MAX_LOG_BUFFER_LEN + 1];
	
		now = time(NULL);
                gettimeofday(&tv, NULL);
                localtime_r(&now, &tm);

		va_start( args, format );
		vsnprintf( buffer, MAX_LOG_BUFFER_LEN, format, args );
		va_end(args);

		fprintf( fp, "[%04d-%02d-%02d:%02d:%02d:%02d.%03ld] %s\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec/1000, buffer);
		fflush( fp );
		fclose( fp );
	}

	pthread_mutex_unlock( &LOG_LOCK );
}

void create_log_filename(void)
{
	struct tm tm;
	struct timeval tv;
	time_t now;
	char curr_dir_name[MAX_FILENAME_LEN];

	bzero( curr_dir_name, MAX_FILENAME_LEN );
	getcwd( curr_dir_name, MAX_FILENAME_LEN );

	now = time(NULL);
	gettimeofday(&tv, NULL);
	localtime_r(&now, &tm);

	sprintf( LOG_FILENAME, "%s/vstsample_%04d-%02d-%02d.log", curr_dir_name, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday );
}

void InitData(void)
{
	int cnt = 0;

	STT_PORT = 0;
	Channel = 1;
	file_list_count = 0;
	wait_file_count = 0;
	bzero( STT_IP_ADDR, MAX_IP_ADDRESS_LEN );
	bzero( STT_LOG_KEY, MAX_LOG_KEY_LEN );
	bzero( STT_VOICE_TYPE, MAX_VOICE_TYPE_LEN );
	bzero( TargetName, MAX_FILENAME_LEN );
	bzero( LOG_FILENAME, MAX_FILENAME_LEN );

	for( cnt = 0; cnt < MAX_QUEUE_SIZE; cnt++ )
	{
		fList[cnt].status = EMPTY_STATUS;
		bzero( fList[cnt].filename, MAX_FILENAME_LEN );
		bzero( fList[cnt].out_name, MAX_FILENAME_LEN );
	}

	pthread_mutex_init( &LIST_LOCK, NULL );
	pthread_mutex_init( &COUNT_LOCK, NULL );
	pthread_mutex_init( &LOG_LOCK, NULL );
}

int main(int argc, char *argv[])
{
	int OperationType = FILE_PROCESSING;

	//int timeMin, timeSec;
	if ( argc != 8 )
	{
		printf( "Invalid Argument\n\n");
		print_help_message(argv[0]);
		return EXIT_FAILURE;
	}

	// Init Data
	InitData();

	// Create Log Filename
	create_log_filename();

	// Set Data
	sprintf( STT_IP_ADDR, "%s", argv[1] );
	STT_PORT = atoi( argv[2] );
	sprintf( STT_VOICE_TYPE, "%s", argv[6]);
	sprintf( STT_LOG_KEY, "%s", argv[7]);

	Channel = atoi( argv[3] );
	if ( Channel < 0 || Channel > MAX_CHANNEL )
	{
		printf( "Invalid Argument\n\n");
		print_help_message(argv[0]);
		return EXIT_FAILURE;
	}

	sprintf( TargetName, "%s", argv[5]);
	if ( strstr( argv[4], "FILE" ) != NULL )
	{
		OperationType = FILE_PROCESSING;
		//sprintf( TargetName, "%s", argv[5]);

		if ( Channel > 1 )
		{
			write_service_log( "FILE Processing is one file about only one.....( Channel %d --> 1 )\n", Channel);
			Channel = 1;
		}
		wait_file_count = 1;
	}
	else if ( strstr( argv[4], "DIR" ) != NULL )
	{
		OperationType = DIR_PROCESSING;
		SearchDirectory( TargetName );
		wait_file_count = file_list_count - 1;
	}
	else
	{
		printf( "Invalid Argument\n\n");
		print_help_message(argv[0]);
		return EXIT_FAILURE;
	}

	debug_print_argument(Channel, OperationType);

	switch ( OperationType )
	{
		case FILE_PROCESSING:
			file_process(TargetName);
			break;
		case DIR_PROCESSING:
			dir_process(Channel);
			break;
		default:
			break;
	}

	while ( 1 )
	{
		if ( wait_file_count == 0 )
		{
			sleep(1);
			break;
		}
	}

	pthread_mutex_destroy( &LOG_LOCK );
	pthread_mutex_destroy( &COUNT_LOCK );
	pthread_mutex_destroy( &LIST_LOCK );

	return EXIT_SUCCESS;
}

void file_process(char *Name)
{
	int index = 0;
	pthread_t thrd_ID;

	fList[0].status = WAIT_STATUS;
	sprintf(fList[0].filename, "%s", Name);
	sprintf(fList[0].out_name, "%s.recog", Name);
	file_list_count += 1;

	thrd_ID = index;
	pthread_create( &thrd_ID, NULL, recog_thread, &index );

	(void) pthread_join( thrd_ID, NULL );
}

void dir_process(int Ch)
{
	int index = 0;
	ARG *arg = NULL;
	pthread_t thrd_ID;

	if ( file_list_count <= 0 )
	{
		fprintf(stdout, "Not Found File....\n");
		return;
	}

	for ( index = 0; index < Ch; index++ )
	{
		arg = (ARG *)malloc(sizeof(ARG));
		arg->channel = index;
		thrd_ID = index;

		write_service_log( "Create Recog_Thread function #%d", index);
		pthread_create( &thrd_ID, NULL, recog_thread, (void *)arg );
		sleep(.5);
	}

/*
	for ( index = 0; index < Ch; index++ )
	{
		thrd_ID = index;
		(void) pthread_join( thrd_ID, NULL );
	}
*/
}

void *recog_thread(void *data)
{
	int index = 0;
	int sample_num = 0;
	int frame_num = 0;
	int FoundFile = 0;
	int channel = 0;
	short *speech;
	char inFileName[MAX_FILENAME_LEN];
	char outFileName[MAX_FILENAME_LEN];
	char *resultbuf = NULL;
	unsigned char *speech_data = NULL;
	FILE *fd = NULL;
	RECV_ARG *recvArg;

	pthread_t thid;
	timeval t_start, t_end;

	channel = ((ARG *)data)->channel;

	write_service_log("[%04d] [%s] --------------- RECOG-THREAD START ---------------", channel, __func__);

	while ( 1 )
	{
		// Set Receive Argument
		recvArg = (RECV_ARG *)malloc(sizeof(RECV_ARG));
		recvArg->sockfd = -1;
		//recvArg->channel = ((ARG *)data)->channel;
		recvArg->channel = channel;
		recvArg->output = NULL;

		// Get Filename
		FoundFile = 0;
		bzero( inFileName, MAX_FILENAME_LEN );
		bzero( outFileName, MAX_FILENAME_LEN );

		pthread_mutex_lock( &LIST_LOCK );
		for ( index = 0; index < file_list_count; index++ )
		{
			if ( fList[index].status == WAIT_STATUS )
			{
				write_service_log("[%04d] [%s] %s processing", recvArg->channel, __func__, fList[index].filename);
				sprintf(inFileName, "%s", fList[index].filename);
				sprintf(outFileName, "%s", fList[index].out_name);
				fList[index].status = RECOG_STATUS;
				FoundFile = 1;
				break;
			}
		}
		pthread_mutex_unlock( &LIST_LOCK );

		if ( FoundFile == 0 )
		{
			write_service_log("[%04d] Not Fount Audio File... Thread Exit...", recvArg->channel);
			free( recvArg );
			break;
		}

		// Set Start Time
		gettimeofday( &t_start, NULL );

		// Voice File Open
		fd = fopen( inFileName, "rb" );
		if ( fd == NULL )
		{
			write_service_log("[%04d] %s file open error", recvArg->channel, inFileName);
			sleep(.5);
			continue;
		}

		write_service_log("[%04d] %s file open sucess", recvArg->channel, inFileName);

		sample_num = (int)(VST_Get_filesize( fd ) / (sizeof( short )));
		if ( sample_num < 0 )
		{
			write_service_log("[%04d] EasrFile -- file open error ( %s )", recvArg->channel, inFileName);
			fclose( fd );
			free(recvArg);
			sleep(.5);
			continue;
		}

		speech = (short *)malloc( sample_num * sizeof( short ) );
		fread( speech, sizeof( short ), sample_num, fd );
		fclose( fd );
		write_service_log("[%04d] %s sample number : %d", recvArg->channel, inFileName, sample_num);

		// Recog File Open
		if ( access( outFileName, F_OK ) != -1 )
		{
			write_service_log("[%04d] Found Output File ( %s )... Delete & Create", recvArg->channel, inFileName);
			unlink( outFileName );
		}
		recvArg->output = fopen( outFileName, "wb+" );

		frame_num = sample_num / ((int)(FRAME_SHIFT));
		speech_data = (unsigned char *)malloc(sample_num * sizeof(unsigned char));
		if ( frame_num < 10 )
		{
			write_service_log("[%04d] %s : Too short input frames...", recvArg->channel, inFileName);
			free( speech_data );
			free( speech );
			free( recvArg );
			sleep(.5);
			continue;
		}

		// Connect to STT Server
		write_service_log("[%04d] VST Server Connect( %s, %d)", recvArg->channel, STT_IP_ADDR, STT_PORT + recvArg->channel);
		recvArg->sockfd = VST_Connect_To_Server( STT_IP_ADDR, STT_PORT + recvArg->channel );
		if ( recvArg->sockfd < 0 )
		{
			write_service_log("[%04d] STT Server Connect Error...", recvArg->channel);

			if ( recvArg->sockfd )
				VST_Disconnect( recvArg->sockfd );

			free(recvArg);
			sleep(.5);
			continue;
		}
		write_service_log("[%04d] [%s] sockfd : %d", recvArg->channel, __func__, recvArg->sockfd);

		// Send Voice Data
		write_service_log("[%04d] Send Header Data", recvArg->channel);
		if ( VST_Send_Header_Data( recvArg->sockfd, STT_LOG_KEY, STT_VOICE_TYPE ) != SUCCESS_SEND_DATA )
		{
			write_service_log("[%04d] Send Header Data Error...", recvArg->channel);

			if ( recvArg->sockfd )
				VST_Disconnect( recvArg->sockfd );

			free( recvArg );
			sleep(.5);
			continue;
		}

		// Create Recv Thread
		write_service_log("[%04d] Create receive thread", recvArg->channel);
		pthread_create( &thid, NULL, recv_thread, recvArg );

		write_service_log("[%04d] Send Audio data", recvArg->channel);
		int sampleByteNum = sample_num * 2;
		int send_block_byte = 320 * 20;
		int sendBlockX = 0;
		int TotalSendByteNum = 0;
		int resByteNum;
		short sendByteNum;
		char sendbuf[20000];

		while ( TotalSendByteNum < sampleByteNum )
		{
			resByteNum = sampleByteNum - TotalSendByteNum;
			if ( resByteNum >= send_block_byte )
				sendByteNum = (short)send_block_byte;
			else
			{
				sendByteNum = (short)resByteNum;
				if ( sendByteNum < 320 ) break;
			}

			memcpy( sendbuf, &sendBlockX, 4 );
			memcpy( sendbuf + 4, &sendByteNum, 2 );
			memcpy( sendbuf + 6, speech + (TotalSendByteNum / 2), (int)sendByteNum );

			VST_Send_Audio_Data( recvArg->sockfd, sendbuf, sendByteNum );

			TotalSendByteNum += sendByteNum;
			sendBlockX++;
		}

		write_service_log("[%04d] Send Audio Final data", recvArg->channel);
		VST_Send_Final_Data( recvArg->sockfd, sendbuf, sample_num, AUDIO_TYPE_MULAW );

		pthread_join( thid, (void **)&resultbuf );
		write_service_log("[%04d] Recog Receive Finish", recvArg->channel);

		// Output File Close
		fclose( recvArg->output );

		// Set End Time
		gettimeofday( &t_end, NULL );

		double diff_time;
		diff_time = ( t_end.tv_sec - t_start.tv_sec ) + (( t_end.tv_usec - t_start.tv_usec ) / 1000000 );
		write_service_log("[%04d] %s decording time %f sec", recvArg->channel, inFileName, diff_time);

		if ( recvArg->sockfd )
			VST_Disconnect( recvArg->sockfd );

		free( recvArg );

		pthread_mutex_lock( &COUNT_LOCK );
		wait_file_count -= 1;
		pthread_mutex_unlock( &COUNT_LOCK );
	}

	pthread_exit(NULL);
}

void *recv_thread(void *data)
{
	int ret = 0;
	int retry_flag = 0;
	int retry_count = 0;
	int code, size;
	int sockfd = -1;
	int prev_new_line = 0;
	FILE *output = ((RECV_ARG *)data)->output;
	char *result = NULL;

	sockfd = ((RECV_ARG *)data)->sockfd;

	while ( true )
	{
		if ( retry_count > 5 )
			break;

		usleep( 200 * 100 );
		ret = VST_Recv_Header_Data(sockfd, &code, &size);

		if ( retry_flag == 0) 
			result = (char *)malloc( sizeof(char) * size + 1 );

		ret = VST_Recv_Recog_Data(sockfd, size, result, NULL);

		if ( ret == ERR_RECV_RECOG_DATA )
		{
			retry_flag = 1;
			retry_count += 1;
			continue;
		}
		else if ( ret == SUCCESS_RECV_RECOG_DATA )
		{
			retry_flag = 0;
		}

		if (code == 600 )
		{
			write_service_log("[%04d] recv code : 600", ((RECV_ARG *)data)->channel);
		}
		else if (code == 100 || code == 900 || code == 901)
		{
			write_service_log("[%04d] Result ( %d ) : %s", ((RECV_ARG *)data)->channel, size, result);
			if ( size == 0 && prev_new_line == 0)
			{
				fprintf(output, "\n");
				prev_new_line = 1;
			}
			else
			{
				fprintf(output, "%s\n", result);
				prev_new_line = 0;
			}
			fflush(output);

			if(code == 100) break;
		}
		else
		{
			write_service_log("[%04d] --- ASR DONE [ code : %d ] --- ", ((RECV_ARG *)data)->channel, code);
			break;
		}

		free( result );
		result = NULL;
		retry_count = 0;
	}

	return NULL;
}

void SaveFileList( char *path, char *filename )
{
	write_service_log( " - %04d : %s/%s ( %d )", file_list_count, path, filename, WAIT_STATUS);

	fList[file_list_count].status = WAIT_STATUS;
	sprintf( fList[file_list_count].filename, "%s/%s", path, filename );
	sprintf( fList[file_list_count].out_name, "%s/%s.recog", path, filename );
	file_list_count += 1;
}

void SearchDirectory( char *dir_name )
{
	DIR *dp;
	struct dirent *entry;
	char curr_dir_name[MAX_FILENAME_LEN];

	bzero( curr_dir_name, MAX_FILENAME_LEN );
	getcwd( curr_dir_name, MAX_FILENAME_LEN );

	if( dir_name[strlen( dir_name ) - 1] == '/' )
		dir_name[strlen( dir_name ) - 1] = '\0';

	if (( dp = opendir( dir_name ) ) == NULL )
	{
		fprintf( stdout, "\nCan not open directory : %s\n", dir_name );
		return;
	}

	chdir( dir_name );

	while (( entry = readdir( dp )) != NULL )
	{
		if ( strcmp( entry->d_name, "." ) == 0 || strcmp( entry->d_name, ".." ) == 0 )
			continue;
		else if ( entry->d_type != DT_DIR )
		{
			if ( find_audio_file( entry->d_name ) == 1 )
			{
				SaveFileList( dir_name, entry->d_name );
			}
		}
	}

	chdir( curr_dir_name );
	closedir( dp );
}

int find_audio_file(char *name)
{
	char *ext;

	ext = strrchr(name, '.');

	if ( ext == NULL )
		return 0;

	if ( ( strcmp( ext, AUDIO_TYPE_WAV_FORMAT ) == 0 ) ||
		( strcmp( ext, AUDIO_TYPE_PCM_FORMAT ) == 0 ) ||
		( strcmp( ext, AUDIO_TYPE_ALAW_FORMAT ) == 0 ) ||
		( strcmp( ext, AUDIO_TYPE_MULAW_FORMAT ) == 0 ) )
		return 1;

	return 0;
}
