/*
 *******************************************************************************
 *  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 <string.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <list>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>

#include <audiotk/audio_common.h>
#include <audiotk/audio_playback_mmap.h>
#include <MsgBroker/msg_broker.h>
#include <SyncRingBuffer/sync_ring_buffer.h>

#include <alac/ALACAudioTypes.h>
#include <alac/ALACDecoder.h>
#include <alac/ALACBitUtilities.h>

//#define DUMP_PCM_DATA

#define CMD_PLAY_FIFO_PATH "/tmp/playback/c0/command.fifo"

typedef struct {
	pthread_mutex_t data_mutex;
	pthread_cond_t data_cond;

	//pthread_mutex_t play_mutex;
	pthread_cond_t play_cond;
	STATUS process_status;

	ATK_AUDIOPLAY_HANDLE_T **p_playback_handle;
	ATK_AUDIOPLAY_CONFIG_T *p_config;
} user_data_t;


#ifdef DUMP_PCM_DATA
static int audio_fd_ = -1;

static int open_pcm_file()
{
	const char *filename = "./playback_alac_debug_audio.pcm";
	audio_fd_ = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
	if (audio_fd_ == -1)
	{
		fprintf(stderr, "[%s, %s]: Open file error: %s\n", __FILE__, __func__, strerror(errno));
		return -1;
	}

	return 0;
}

static void close_pcm_file()
{
	if(audio_fd_ >= 0)
		close(audio_fd_);
}

static int write_pcm_data_to_file(unsigned char* const* audio_bufs, size_t data_bytes)
{
	ssize_t ret = write(audio_fd_, audio_bufs[0], data_bytes);
	if (ret < 0)
	{
		fprintf(stderr, "[%s, %s]: Write error. %s\n", __FILE__, __func__, strerror(errno));
		return -1;
	}
	else if ((size_t) ret != data_bytes)
	{
		fprintf(stderr, "[%s, %s]: Data loss ..............\n", __FILE__, __func__);
		return -1;
	}
	return 0;
}
#endif // DUMP_PCM_DATA

static int is_terminate_ = 0;

// The handle of ring buffer to receive audio data.
static srb_handle_t* audio_srb_handle_ = NULL;

static void exit_process()
{
	is_terminate_ = 1;
	if(audio_srb_handle_)
	{
		// Notify the reader of ring buffer to exit.
		SRB_WakeupReader(audio_srb_handle_);
	}
}

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;
	}
}

static void dump_trace(int /*signo*/)
{
	printf(" ===== Segmentation fault. ===== \n");
	exit(EXIT_FAILURE);
}

static void print_usage(const char *ap_name)
{
	fprintf(stderr, "Usage:\n"
			"    %s -d PCM_device_name -r sample_rate -c channels [-b bit_depth] [-R name] [-D]\n"
			"Options:\n"
			"    -b                 Bit depth for audio (Default: 16 -> S16_LE).\n"
			"    -d                 PCM device name for ALSA (ex: hw:0,0).\n"
			"    -r                 Sample rate for audio.\n"
			"    -c                 The number of audio channels.\n"
			"    -R                 The name of ring buffer for receiving data.\n"
			"    -D                 Run as Daemon.\n"
			"    -h                 This help.\n"
			, ap_name);
	fprintf(stderr, "ex:\n"
			"%s -d \"hw:0,0\" -r 8000 -c 2\n"
			"%s -d \"hw:0,0\" -r 8000 -c 2 -B -R audio_backchannel_srb_1\n", ap_name, ap_name);
}

static void send_cmd(const char *cmd)
{
	// Send the command to the audio encoder.
	MsgContext msg_context;
	pid_t connect_pid = getpid();
	msg_context.bHasResponse = 0;
	msg_context.pszHost = "encoder";
	msg_context.dwHostLen = strlen(msg_context.pszHost) + 1;
	msg_context.pszCmd = cmd;
	msg_context.dwCmdLen = strlen(msg_context.pszCmd) + 1;
	msg_context.dwDataSize = sizeof(pid_t);
	msg_context.pbyData = (unsigned char *) &connect_pid;
	MsgBroker_SendMsg(CMD_FIFO_PATH, &msg_context);
}

static void* play_interleaved_data(void* args) {
	unsigned int* values = NULL;
	unsigned char* temp_buf = NULL;

	user_data_t *temp_data = (user_data_t*) args;
	ATK_AUDIOPLAY_HANDLE_T *playback_handle = *(temp_data->p_playback_handle);

#ifdef DUMP_PCM_DATA
	// Get total bytes of data in one period.
	size_t period_frame_size = ATK_AudioPlay_GetPeriodFramesSize(playback_handle);
#endif
	// Audio output buffer.
	unsigned char *bufs[ATK_AUDIO_MAX_CHANNELS] = {NULL};

	// Prepare the buffer pointer for the ring buffer.
	srb_buffer_t srb_buf;
	memset(&srb_buf, 0, sizeof(srb_buffer_t));

	ALACDecoder* decoder = NULL;
	AudioFormatDescription outputFormat;
	uint8_t* theMagicCookie = NULL;
    uint32_t theMagicCookieSize = 0;

	// Tell the audio encoder to send the configuration data.
	send_cmd("forceCI");

	outputFormat.mFormatID = kALACFormatLinearPCM;
	outputFormat.mSampleRate =  temp_data->p_config->dwSampleRate;
	outputFormat.mBitsPerChannel = 16;

	switch (temp_data->p_config->eFormat)
	{
		case SND_PCM_FORMAT_S16_LE:
			outputFormat.mBitsPerChannel = 16;
			break;
		case SND_PCM_FORMAT_S24_LE:
			outputFormat.mBitsPerChannel = 24;
			break;
		case SND_PCM_FORMAT_S32_LE:
			outputFormat.mBitsPerChannel = 32;
			break;
		default:
			break;
	}

	outputFormat.mFramesPerPacket = 1;
	outputFormat.mChannelsPerFrame = temp_data->p_config->dwChannelsCount;
	outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame = outputFormat.mBitsPerChannel != 20 
		? temp_data->p_config->dwChannelsCount * ((outputFormat.mBitsPerChannel) >> 3) : (int32_t)(temp_data->p_config->dwChannelsCount * 2.5 + .5);
	outputFormat.mFormatFlags = kALACFormatFlagsNativeEndian;
	outputFormat.mReserved = 0;
	while (!is_terminate_)
	{
		if (temp_data->process_status == STOP) {
			pthread_mutex_lock(&(temp_data->data_mutex));	
			pthread_cond_signal(&(temp_data->data_cond));	
			pthread_mutex_unlock(&(temp_data->data_mutex));	
			
			pthread_mutex_lock(&(temp_data->data_mutex));	
			pthread_cond_wait(&(temp_data->play_cond), &(temp_data->data_mutex));	
			pthread_mutex_unlock(&(temp_data->data_mutex));	
		}

#ifdef DUMP_PCM_DATA
		if(bufs[0] != NULL)
		{
			write_pcm_data_to_file(bufs, period_frame_size);
		}
#endif
		// Output the current buffer and get the next output buffer.
		if(ATK_AudioPlay_PlayPeriodFrames(playback_handle, bufs) < 0)
		{
			fprintf(stderr, "[%s,%s] Can't get the audio output buffer..\n", __FILE__, __func__);
			break;
		}

		// Get the buffer with audio data from the audio encoder.
		if(SRB_ReturnReceiveReaderBuff(audio_srb_handle_, &srb_buf) == 0)
		{
			// We check whether the data is audio data or not.
			values = (unsigned int*)srb_buf.buffer;

			if(values[0] != FOURCC_CONF)
			{
				// This is audio data.
				// Check whether the data is ALAC or not.
				if(values[0] == FOURCC_ALAC && decoder)
				{
					// This is the payload.
					BitBuffer input_bit_buffer;
					uint32_t data_bytes = values[3];
					uint32_t frames = 0;
					
					temp_buf = srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE;
					BitBufferInit(&input_bit_buffer, (uint8_t*)temp_buf, data_bytes);
					decoder->Decode(&input_bit_buffer, bufs[0], 1, 2, &frames);
					//uint32_t numBytes = frames * outputFormat.mBytesPerFrame;
					//printf("ALAC decoded %u bytes, %u frames, mBytesPerFrame = %u\n", numBytes, numFrames, outputFormat.mBytesPerFrame);
				}
			}
			else
			{
				// This is configuration data.
				if(values[2] == FOURCC_ALAC)
				{
					temp_buf = srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE;
					theMagicCookieSize = values[5];
					theMagicCookie = (uint8_t *) calloc(theMagicCookieSize, 1);
					memcpy(theMagicCookie, &values[6], theMagicCookieSize);
					if (decoder) delete decoder;
					decoder = new ALACDecoder;
					decoder->Init(theMagicCookie, theMagicCookieSize);
					free(theMagicCookie);
				}

				// Tell the audio encoder to start to encode data.
				send_cmd("start");
			}
		}
	}

	// Return the last buffer to the ring buffer.
	SRB_ReturnReaderBuff(audio_srb_handle_, &srb_buf);

	// Tell the audio encoder to stop encoding data.
	send_cmd("stop");

	if (decoder) delete decoder;

	return NULL;
}


static void msg_callback(MsgContext* msg, void* args)
{
	user_data_t *temp_data = (user_data_t*) args;
	if (!strcasecmp(msg->pszHost, SR_MODULE_NAME)) {
		if (!strcasecmp(msg->pszCmd, SUSPEND_CMD)) {
			pthread_mutex_lock(&(temp_data->data_mutex));
			SRB_WakeupReader(audio_srb_handle_);
			temp_data->process_status = STOP;
			pthread_cond_wait(&(temp_data->data_cond), &(temp_data->data_mutex));
			pthread_mutex_unlock(&(temp_data->data_mutex));
			if(*(temp_data->p_playback_handle)) ATK_AudioPlay_Release(*(temp_data->p_playback_handle));
			MsgBroker_SuspendAckMsg();
		}else if(!strcasecmp(msg->pszCmd, RESUME_CMD) ) {
			*(temp_data->p_playback_handle) = ATK_AudioPlay_Init(temp_data->p_config);
			pthread_mutex_lock(&(temp_data->data_mutex));
			temp_data->process_status = START;
			pthread_cond_signal(&(temp_data->play_cond));
			pthread_mutex_unlock(&(temp_data->data_mutex));
		}
	}
	if (msg->bHasResponse) msg->dwDataSize = 0;
}


int main(int argc, char **argv)
{
	int opt;
	bool is_daemon = false;
	int bit_depth = 16;
	pthread_t playback_tid = 0;
	ATK_AUDIOPLAY_HANDLE_T *playback_handle = NULL;
	ATK_AUDIOPLAY_CONFIG_T config;

	std::string pcm_name = "hw:0,0";
	std::string ring_buf_name;

	// Default value of configuration.
	memset(&config, 0, sizeof(ATK_AUDIOPLAY_CONFIG_T));
	config.szPcmName = pcm_name.c_str();
	config.bIsInterleaved = 1;
	config.eFormat = SND_PCM_FORMAT_S16_LE;
	config.dwChannelsCount = 2;
	config.dwSampleRate = 8000;
	config.bUseSimpleConfig = 0;
	config.dwPeriodsPerBuffer = 8;
	config.dwPeriodSizeInFrames = PERIOD_SIZE_IN_FRAMES;

	while ((opt = getopt(argc, argv, "b:c:d:Dh:r:R:")) != -1)
	{
		switch(opt)
		{
			case 'b':
				bit_depth = atoi(optarg);
				break;
			case 'c':
				config.dwChannelsCount = atoi(optarg);
				break;
			case 'd':
				pcm_name = optarg;
				config.szPcmName = pcm_name.c_str();
				break;
			case 'h':
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
			case 'r':
				config.dwSampleRate = atoi(optarg);
				break;
			case 'D':
				is_daemon = true;
				break;
			case 'R':
				ring_buf_name = optarg;
				break;
			default:
				print_usage(argv[0]);
				exit(EXIT_FAILURE);
		}
	}

	switch (bit_depth)
	{
		case 16:
			config.eFormat = SND_PCM_FORMAT_S16_LE;
			break;
		case 24:
			config.eFormat = SND_PCM_FORMAT_S24_LE;
			break;
		case 32:
			config.eFormat = SND_PCM_FORMAT_S32_LE;
			break;
		default:
			print_usage(argv[0]);
			exit(EXIT_FAILURE);
	}

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

	if (is_daemon)
	{
		daemon(1,1);
	}

	user_data_t user_data;
	memset(&user_data, 0, sizeof(user_data_t));
	pthread_mutex_init(&(user_data.data_mutex), NULL); 
	pthread_cond_init(&(user_data.data_cond), NULL);
	pthread_cond_init(&(user_data.play_cond), NULL);
	
	user_data.process_status = START;
	user_data.p_playback_handle = &playback_handle;
	user_data.p_config = &config;

	// Initialize audio playback.
	playback_handle = ATK_AudioPlay_Init(&config);
	if(playback_handle == NULL)
	{
		fprintf(stderr, "[%s,%s] Can't initialize the audio playback.\n", __FILE__, __func__);
		goto main_end;
	}

	// Initialize the reader of ring buffer.
	if(ring_buf_name.empty())
	{
		ring_buf_name = "aenc_srb_1";
	}
	audio_srb_handle_ = SRB_InitReader(ring_buf_name.c_str());
	if(audio_srb_handle_ == NULL)
	{
		fprintf(stderr, "[%s,%s] Can't initialize the reader of ring buffer.\n", __FILE__, __func__);
		goto main_end;
	}

#ifdef DUMP_PCM_DATA
	if(open_pcm_file() < 0)
	{
		goto main_end;
	}
#endif

	pthread_create(&playback_tid, NULL, play_interleaved_data, &user_data);

	MsgBroker_RegisterMsg(CMD_PLAY_FIFO_PATH);
	MsgBroker_Run(CMD_PLAY_FIFO_PATH, msg_callback, (void *)&user_data, (int *)&is_terminate_);
	MsgBroker_UnRegisterMsg();

main_end:
	pthread_join(playback_tid, NULL);

#ifdef DUMP_PCM_DATA
		close_pcm_file();
#endif

	// Release audio playback.
	if (playback_handle) ATK_AudioPlay_Release(playback_handle);

	pthread_mutex_destroy(&(user_data.data_mutex));
	pthread_cond_destroy(&(user_data.data_cond));
	pthread_cond_destroy(&(user_data.play_cond));

	// Release the reader of ring buffer.
	if(audio_srb_handle_) SRB_Release(audio_srb_handle_);

	return 0;
}

