/*
 *******************************************************************************
 *  Copyright (c) 2010-2022 VATICS(KNERON) Inc. All rights reserved.
 *
 *  +-----------------------------------------------------------------+
 *  | THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY ONLY BE USED |
 *  | AND COPIED IN ACCORDANCE WITH THE TERMS AND CONDITIONS OF SUCH  |
 *  | A LICENSE AND WITH THE INCLUSION OF THE THIS COPY RIGHT NOTICE. |
 *  | THIS SOFTWARE OR ANY OTHER COPIES OF THIS SOFTWARE MAY NOT BE   |
 *  | PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY OTHER PERSON. THE   |
 *  | OWNERSHIP AND TITLE OF THIS SOFTWARE IS NOT TRANSFERRED.        |
 *  |                                                                 |
 *  | THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT   |
 *  | ANY PRIOR NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY |
 *  | VATICS(KNERON) INC.                                             |
 *  +-----------------------------------------------------------------+
 *
 *******************************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/time.h>

#include <audiotk/audio_playback_mmap.h>
#include <audiotk/audio_vol_ctrl.h>
#include "gpio_sysfs_ctrl.h"


#define SND_PLAYBACK_APP_NAME		"snd_playback"

// The period size for audio libraries.
#define PERIOD_SIZE_IN_FRAMES 	1024

// Default GPIO pin number.
#define DEFAULT_GPIO_PIN_NUM	85

// Default size of fd set.
#define FD_SET_NUM				2

// Poll timeout for fds.
#define POLL_TIMEOUT 			(1 * 1000) 				// 3 seconds

// The flag to indicate whether the loop is running or not.
static int bTerminate = 0;

static ATK_AUDIOPLAY_HANDLE_T *atk_playback_handle = NULL;
static ATK_AUDIOPLAY_CONFIG_T atk_playback_config;
static size_t period_frame_size = 0;
static unsigned int playback_volume = 100;

// Audio file buffer.
static char *snd_file_buffer = NULL;
static long int snd_file_size = 0L;

// Audio output buffer.
static unsigned char *atk_playback_bufs[ATK_AUDIO_MAX_CHANNELS] = {NULL};

// GPIO pin number.
static unsigned int gpio_pin_num = DEFAULT_GPIO_PIN_NUM;

// GPIO fdset.
static struct pollfd gpio_fdset[FD_SET_NUM];


typedef struct runtime_context
{
	pthread_mutex_t playback_mutex;
	pthread_cond_t playback_cond;
} runtime_context;

static	runtime_context _runtime_context;

void *playback_thread(void *arg)
{
	runtime_context *context = (runtime_context *) arg;
	char *snd_file_buffer_iterator = NULL;
	long int snd_file_processed_size = 0L;
	ssize_t playback_processed_size = 0;
	(void)playback_processed_size;
	size_t playback_size = 0;

	struct timeval prev_tv;
	struct timeval tv;

	float fltotal_time = 0.0;

	if (!snd_file_buffer)
	{
		fprintf(stderr, "[%s,%d] Failed: snd_file_buffer is NULL\n", __func__, __LINE__);
		return NULL;
	}
	if (!atk_playback_handle)
	{
		fprintf(stderr, "[%s,%d] Failed: atk_playback_handle is NULL\n", __func__, __LINE__);
		return NULL;
	}

	// Get the first output buffer.
	if(ATK_AudioPlay_PlayPeriodFrames(atk_playback_handle, atk_playback_bufs) < 0)
	{
		fprintf(stderr, "[%s,%s] Can't get the audio output buffer..\n", __FILE__, __func__);
		return NULL;
	}

	if (!atk_playback_bufs[0])
	{
		fprintf(stderr, "[%s,%d] Failed: atk_playback_bufs[0] is NULL\n", __func__, __LINE__);
		return NULL;
	}

	pthread_mutex_lock(&context->playback_mutex);
	while (!bTerminate)
	{
		pthread_cond_wait(&context->playback_cond, &context->playback_mutex);
		if (bTerminate)
		{
			printf("[%s,%d] Program exiting ...\n", __func__, __LINE__);
			break;
		}

		printf("[%s,%d] +++++ Start playback +++++ \n", __func__, __LINE__);
		snd_file_buffer_iterator = snd_file_buffer;
		snd_file_processed_size = 0L;
		playback_processed_size = 0;
		playback_size = 0;
		gettimeofday(&prev_tv, NULL);
		fltotal_time = 0.0;

		while (!bTerminate && atk_playback_bufs[0] && (snd_file_processed_size < snd_file_size))
		{
			playback_size = ((snd_file_processed_size + period_frame_size) > (unsigned long int) snd_file_size) ? (snd_file_size - snd_file_processed_size) : period_frame_size;
			memcpy(atk_playback_bufs[0], snd_file_buffer_iterator, playback_size);

			//playback_processed_size = ATK_AudioPlay_PlayFrames(atk_playback_handle, atk_playback_bufs, (unsigned int)playback_size);
			// Output the current buffer.
			if(ATK_AudioPlay_PlayPeriodFrames(atk_playback_handle, atk_playback_bufs) < 0)
			{
				fprintf(stderr, "[%s,%s] Can't get the audio output buffer..\n", __FILE__, __func__);
				break;
			}
			snd_file_processed_size += playback_size;
			snd_file_buffer_iterator += playback_size;
		}

		gettimeofday(&tv, NULL);
		fltotal_time = ((tv.tv_sec - prev_tv.tv_sec)*1000000+tv.tv_usec)-prev_tv.tv_usec;
		printf("[%s,%d] Playback lasted for %f seconds... \n", __func__, __LINE__, fltotal_time/1000000.0f);
		printf("[%s,%d] ----- End playback ----- \n", __func__, __LINE__);
	}
	pthread_mutex_unlock(&context->playback_mutex);

	return NULL;
}

void *gpio_keypad_event_thread(void *arg)
{
	runtime_context *context = (runtime_context *) arg;

	int n_fds = 1;
	int gpio_fd = -1;
	char buf[MAX_BUF];
	unsigned int gpio = gpio_pin_num;

	memset(gpio_fdset, 0, sizeof(gpio_fdset));
	gpio_fdset[0].fd = fileno(stdin);
	gpio_fdset[0].events = POLLRDNORM;

	gpio_export(gpio);
	gpio_set_dir(gpio, 0);
	gpio_set_edge(gpio, GPIO_EDGE_RISING);

	gpio_fd = gpio_fd_open(gpio);
	if (gpio_fd > 0)
	{
		memset(buf, 0, sizeof(buf));
		gpio_fdset[1].fd = gpio_fd;
		gpio_fdset[1].events = POLLPRI;
		n_fds++;
	}
	while (!bTerminate)
	{
		if (poll(gpio_fdset, n_fds, POLL_TIMEOUT) < 0) {
			printf("Poll fail\n");
			continue;
		}

		if (gpio_fdset[0].revents & POLLRDNORM)
		{
			if (read(gpio_fdset[0].fd, buf, MAX_BUF) < 0) {
				continue;
			}
			printf("Reading data from stdin\n");
			pthread_mutex_lock(&context->playback_mutex);
			pthread_cond_signal(&context->playback_cond);
			pthread_mutex_unlock(&context->playback_mutex);
		}
		if (gpio_fdset[1].revents & POLLPRI)
		{
			if (read(gpio_fdset[1].fd, buf, MAX_BUF) < 0) {
				continue;
			}
			printf("Reading data from GPIO pin %u\n", gpio);
			pthread_mutex_lock(&context->playback_mutex);
			pthread_cond_signal(&context->playback_cond);
			pthread_mutex_unlock(&context->playback_mutex);
		}
	}
	if (gpio_fd > 0)
	{
		gpio_fd_close(gpio_fd);
	}
	return NULL;
}

static void print_usage(const char *ap_name)
{
        fprintf(stderr, "Usage:\n"
                        "    %s -f filepath [-c channel_num] [-g gpio_pin] [-i interleaved] [-p periods_per_buffer]\n"
                        "                   [-r sample_rate] [-s simple_config] [-v volume]\n"
                        "Options:\n"
                        "    -f                 [Required] Raw data file path (Required) \n"
                        "    -c                 [Optional] Number of channels (Default: 2) \n"
                        "    -g                 [Optional] GPIO pin number (Default: 85) \n"
                        "    -i                 [Optional] Interleaved playback (0 or 1, default: 1) \n"
                        "    -p                 [Optional] Periods per buffer (Default: 4)\n"
                        "    -r                 [Optional] Sample rate (8000, 16000, etc..., Default: 8000) \n"
                        "    -s                 [Optional] Simple configuration (0 or 1, Default: 0) \n"
                        "    -v                 [Optional] Volume (0 to 100, Default: 100) \n"
                        "    -h                 This help. \n", ap_name);
}


static void exit_process()
{
	bTerminate = 1;

	if (gpio_fdset[0].fd)
	{
		gpio_fd_close(gpio_fdset[0].fd);
	}

	pthread_mutex_lock(&_runtime_context.playback_mutex);
	pthread_cond_signal(&_runtime_context.playback_cond);
	pthread_mutex_unlock(&_runtime_context.playback_mutex);
}

static void sig_kill(int signo)
{
	printf("[%s,%s] Receive SIGNAL %d!!!\n", __FILE__, __func__, signo);
	switch(signo)
	{
		case SIGTERM:
		case SIGINT:
			exit_process();
			break;
		default:
			break;
	}
}

int main(int argc, char* argv[])
{
	int ret_val = 0;
	int opt = -1;

	// SND audio file info.
	char *snd_filename = NULL;
	FILE *snd_file_fp = NULL;
	size_t snd_readfile_size = 0;

	// threads and runtime context.
	pthread_t playback_tid = 0;
	pthread_t gpio_tid = 0;

	// Default value of configuration.
	memset(&atk_playback_config, 0, sizeof(ATK_AUDIOPLAY_CONFIG_T));
	atk_playback_config.szPcmName = "hw:0,0";
	atk_playback_config.bIsInterleaved = 1;
	atk_playback_config.eFormat = SND_PCM_FORMAT_S16_LE;
	atk_playback_config.dwChannelsCount = 2;
	atk_playback_config.dwSampleRate = 8000;
	atk_playback_config.bUseSimpleConfig = 0;
	atk_playback_config.dwPeriodsPerBuffer = 4;
	atk_playback_config.dwPeriodSizeInFrames = PERIOD_SIZE_IN_FRAMES;

	while ((opt = getopt(argc, argv, "c:f:g:h:i:p:r:s:v:")) != -1)
	{
		switch(opt)
		{
			case 'c':
				atk_playback_config.dwChannelsCount = atoi(optarg);
				break;
			case 'f':
				snd_filename = strdup(optarg);
				break;
			case 'g':
				gpio_pin_num = atoi(optarg);
				break;
			case 'h':
				print_usage(SND_PLAYBACK_APP_NAME);
				goto main_end;
				break;
			case 'i':
				atk_playback_config.bIsInterleaved = atoi(optarg);
				break;
			case 'p':
				atk_playback_config.dwPeriodsPerBuffer = atoi(optarg);
				break;
			case 'r':
				atk_playback_config.dwSampleRate = atoi(optarg);
				break;
			case 's':
				atk_playback_config.bUseSimpleConfig = atoi(optarg);
				break;
			case 'v':
				playback_volume = atoi(optarg);
				break;
			default:
				print_usage(SND_PLAYBACK_APP_NAME);
				goto main_end;
				break;
		}
	}

	if (!snd_filename)
	{
		fprintf(stderr, "[%s,%d] Please specify SND file to load by -f option\n", __func__, __LINE__);
		ret_val = -1;
		goto main_end;
	}

	// Open the SND file
	printf("[%s,%d] Loading raw data from file %s...\n", __func__, __LINE__, snd_filename);
	snd_file_fp = fopen(snd_filename, "rb");
	if (!snd_file_fp)
	{
		fprintf(stderr, "[%s,%d] Failed to load file - %s\n", __func__, __LINE__, snd_filename);
		ret_val = -1;
		goto main_end;
	}

	// Check file size
	fseek(snd_file_fp, 0L, SEEK_END);
	snd_file_size = ftell(snd_file_fp);
	fseek(snd_file_fp, 0L, SEEK_SET);

	if (snd_file_size > 0)
	{
		snd_file_buffer = (char *)malloc(sizeof(char) * snd_file_size);
		if (!snd_file_buffer)
		{
			fprintf(stderr, "[%s,%d] Failed to allocate memory for snd_file_buffer\n", __func__, __LINE__);
			ret_val = -1;
			goto main_end;
		}
	}else {
		fprintf(stderr, "[%s,%d] Failed to ftell file - %s\n", __func__, __LINE__, snd_filename);
		ret_val = -1;
		goto main_end;
	}
	
	snd_readfile_size = fread(snd_file_buffer, 1, snd_file_size, snd_file_fp);

	if (snd_readfile_size == 0) {
		fprintf(stderr, "[%s,%d] Failed to read file - %s\n", __func__, __LINE__, snd_filename);
		ret_val = -1;
		goto main_end;
	}

	printf("[%s,%d] snd_file_size %ld...\n", __func__, __LINE__, snd_file_size);
	printf("[%s,%d] snd_readfile_size %zu...\n", __func__, __LINE__, snd_readfile_size);

	signal(SIGTERM, sig_kill);
	signal(SIGINT, sig_kill);

	// Initialize ATK audio playback.
	atk_playback_handle = ATK_AudioPlay_Init(&atk_playback_config);
	if(atk_playback_handle == NULL)
	{
		fprintf(stderr, "[%s,%d] Can't initialize the audio playback.\n", __func__, __LINE__);
		ret_val = -1;
		goto main_end;
	}

	ret_val = ATK_Audio_SetPlaybackVolume(playback_volume);
	if (ret_val)
	{
		fprintf(stderr, "[%s,%d] Can't set audio playback volume: %u.\n", __func__, __LINE__, playback_volume);
	}

	// Get the total bytes of data in one period.
	period_frame_size = ATK_AudioPlay_GetPeriodFramesSize(atk_playback_handle);
	//atk_playback_bufs[0] = (unsigned char*) malloc(period_frame_size);

	pthread_mutex_init(&_runtime_context.playback_mutex, NULL);
	pthread_cond_init(&_runtime_context.playback_cond, NULL);

	pthread_create(&playback_tid, NULL, playback_thread, &_runtime_context);
	pthread_create(&gpio_tid, NULL, gpio_keypad_event_thread, &_runtime_context);

	pthread_join(gpio_tid, NULL);
	pthread_join(playback_tid, NULL);

	pthread_cond_destroy(&_runtime_context.playback_cond);
	pthread_mutex_destroy(&_runtime_context.playback_mutex);

main_end:

	/*if (atk_playback_bufs[0])
	{
		free(atk_playback_bufs[0]);
		atk_playback_bufs[0] = NULL;
	}*/
	if (atk_playback_handle)
	{
		ATK_AudioPlay_Release(atk_playback_handle);
		atk_playback_handle = NULL;
	}
	if (snd_file_buffer)
	{
		free(snd_file_buffer);
		snd_file_buffer = NULL;
	}
	if (snd_file_fp)
	{
		fclose(snd_file_fp);
		snd_file_fp = NULL;
	}
	if (snd_filename)
	{
		free(snd_filename);
		snd_filename = NULL;
	}

	return ret_val;
}

