/*
 *******************************************************************************
 *  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 <string>

#include <signal.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include <audiotk/audio_capture_mmap.h>
#include <audiotk/audio_vol_ctrl.h>
#include <MsgBroker/msg_broker.h>
#include <SyncRingBuffer/sync_ring_buffer.h>

#include <alac/ALACAudioTypes.h>
#include <alac/ALACEncoder.h>

#define MAX_CONNECT_NUM (5)
#define DEFAULT_PID (0xffff)

// Flag for message broker (main loop).
static int is_terminate_ = 0;

// Adapted from CoreAudioTypes.h
enum
{
    kTestFormatFlag_16BitSourceData    = 1,
    kTestFormatFlag_20BitSourceData    = 2,
    kTestFormatFlag_24BitSourceData    = 3,
    kTestFormatFlag_32BitSourceData    = 4
};

typedef struct
{
	//! connect pid
	pid_t connect_pid;
	//! Flag to indicate whether we need to encode data or not.
	unsigned int do_encoding;
} connect_info_t;

connect_info_t g_atconnect_info[MAX_CONNECT_NUM];

typedef struct
{
	// Flag to indicate whether we need to send configuration about each encoder or not.
	bool send_conf;

	// The handles for SynRingBuf.
	srb_handle_t* srb_handle;
	// The buffers for SynRingBuf.
	srb_buffer_t srb_buf;

	unsigned int enc_type; // FourCC of encoder.
	unsigned int seq_num;

	pthread_mutex_t data_mutex;
	pthread_cond_t data_cond;
	STATUS process_status;

	ATK_AUDIOCAP_CONFIG_T	*p_audiocap_config;
	ATK_AUDIOCAP_HANDLE_T **p_cap_handle;

	AudioFormatDescription inputFormat;
	AudioFormatDescription outputFormat;

	// encoder handle
	void *enc_handle;
} user_data_t;

//#define DUMP_TIMESTAMP
//#define DUMP_PCM_DATA

#ifdef DUMP_PCM_DATA
static int audio_fd_ = -1;

static int open_pcm_file(/* int is_interleaved, unsigned int channels */)
{
	const char *filename = "alacaenc_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(int /*is_interleaved*/, unsigned int /*channels*/, 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 pid_default(int dwStart)
{	
	unsigned int i;
	for (i = 0; i < MAX_CONNECT_NUM; ++i)
	{
		if (dwStart) {
			//! set default pid and start encoding 
			if (!g_atconnect_info[i].connect_pid) {
				g_atconnect_info[i].connect_pid = DEFAULT_PID;
				++g_atconnect_info[i].do_encoding;
				break;
			} else if (g_atconnect_info[i].connect_pid == DEFAULT_PID){
				++g_atconnect_info[i].do_encoding;
				break;
			} else {
				printf("[%s] Error: Something wrong \n", __func__);
				break;
			}
		} else {
			//! set default pid and stop encoding
			if (g_atconnect_info[i].connect_pid == DEFAULT_PID) {
				--g_atconnect_info[i].do_encoding;
				if (!g_atconnect_info[i].do_encoding) {
					g_atconnect_info[i].connect_pid = 0;
				}
				break;
			}
		}
	}

	if(i == MAX_CONNECT_NUM){
		printf("[%s] Error: AENC Connect number to MAX\n",__func__);
		return 1;
	}
	return 0;
}	

static int pid_start (pid_t new_pid)
{
	unsigned int i = 0;
	for (i = 0; i < MAX_CONNECT_NUM; i++) 
	{
		if (g_atconnect_info[i].connect_pid == 0 && !g_atconnect_info[i].do_encoding) {	
			g_atconnect_info[i].connect_pid = new_pid;
			g_atconnect_info[i].do_encoding++;
			break;
		} else if (g_atconnect_info[i].connect_pid == new_pid) {
			g_atconnect_info[i].do_encoding++;
			break;
		}
	}

	if(i == MAX_CONNECT_NUM){
		printf("[%s] Error: AENC Connect number to MAX\n",__func__);
		return 1;
	}
		
	return 0;
}

static int pid_stop(pid_t new_pid)
{
	for (size_t i = 0; i < MAX_CONNECT_NUM; i++) 
	{
		if (g_atconnect_info[i].connect_pid != 0
			&& g_atconnect_info[i].connect_pid == new_pid
			&& g_atconnect_info[i].do_encoding) {	
			g_atconnect_info[i].do_encoding--;
			if (!g_atconnect_info[i].do_encoding)
				g_atconnect_info[i].connect_pid = 0;
			break;
		} 
	}

	return 0;
}

static int pid_do_encoding(void)
{
	for (size_t i = 0; i < MAX_CONNECT_NUM; i++) 
	{
		if (g_atconnect_info[i].connect_pid != 0 && g_atconnect_info[i].do_encoding) 
			return 1;
	}
	return 0;
}

static void audiocap_callback(const ATK_AUDIO_NOTIFY_DATA_INFO_T *audio_info, void* user_data)
{
	//Input pcm data should be interleaved
	user_data_t *temp_data = (user_data_t*) user_data;

#ifdef DUMP_TIMESTAMP
	printf("time = %lu.%lu\n", audio_info->tDataTimestamp.tv_sec, audio_info->tDataTimestamp.tv_usec);
#endif // DUMP_TIMESTAMP

#ifdef DUMP_PCM_DATA
	write_pcm_data_to_file(audio_info->bIsInterleaved, audio_info->dwChannels, audio_info->ppbyAudioBufs, audio_info->dwDataBytes);
#endif
	pthread_mutex_lock(&(temp_data->data_mutex));	

	ALACEncoder* theEncoder = (ALACEncoder*) temp_data->enc_handle;
	if (temp_data->send_conf)
	{
		// Send configuration of encoder.
		printf("send conf .............\n");
		if(FOURCC_ALAC == temp_data->enc_type) {
			unsigned int *values = (unsigned int*) temp_data->srb_buf.buffer;
			uint32_t theMagicCookieSize = theEncoder->GetMagicCookieSize(temp_data->outputFormat.mChannelsPerFrame);
		    uint8_t* theMagicCookie = (uint8_t *)calloc(theMagicCookieSize, 1);

		    theEncoder->GetMagicCookie(theMagicCookie, &theMagicCookieSize);
			values[0] = FOURCC_CONF;
			values[1] = 16 + theMagicCookieSize;
			values[2] = FOURCC_ALAC;
			values[3] = temp_data->outputFormat.mSampleRate;
			values[4] = temp_data->outputFormat.mChannelsPerFrame;
			values[5] = theMagicCookieSize;
			memcpy(values + 6, theMagicCookie, theMagicCookieSize);
			free(theMagicCookie);
		}
		SRB_SendGetWriterBuff(temp_data->srb_handle, &temp_data->srb_buf);
		temp_data->send_conf = false;
	}
	
	if (temp_data->process_status == STOP) {
		pthread_cond_signal(&(temp_data->data_cond));	
		pthread_mutex_unlock(&(temp_data->data_mutex));	
		return;
	}
	pthread_mutex_unlock(&(temp_data->data_mutex));	
	
	if (pid_do_encoding())
	{
		if (FOURCC_ALAC == temp_data->enc_type)
		{
			int32_t encode_data_bytes = audio_info->dwDataBytes;
			int32_t status = -1;

			status = theEncoder->Encode(temp_data->inputFormat, temp_data->outputFormat, 
				audio_info->ppbyAudioBufs[0], temp_data->srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE, &encode_data_bytes);
			if (0 == status && encode_data_bytes > 0)
			{
				unsigned int* buf_ptr = (unsigned int*) temp_data->srb_buf.buffer;
				buf_ptr[0] = temp_data->enc_type;
				buf_ptr[1] = audio_info->tDataTimestamp.tv_sec;
				buf_ptr[2] = audio_info->tDataTimestamp.tv_usec;
				buf_ptr[3] = encode_data_bytes;
				buf_ptr[4] = temp_data->seq_num;
				SRB_SendGetWriterBuff(temp_data->srb_handle, &temp_data->srb_buf);
			}
			else
			{
				fprintf(stderr, "[%s, %s]: Encode error !!! status(%d), encode_data_bytes(%d)\n", __FILE__, __func__, status, encode_data_bytes);
			}

			++(temp_data->seq_num);
		}
	}
}

static void msg_callback(MsgContext* msg, void* user_data)
{
	user_data_t *temp_data = (user_data_t*) user_data;

	if (!strncasecmp(msg->pszHost, "encoder", 7))
	{
		if (!strcasecmp(msg->pszCmd, "start"))
		{
			if (msg->dwDataSize) {
				pid_t *ptNew_pid =  (pid_t *)msg->pbyData;
				pid_start(*ptNew_pid);
			} else {
				pid_default(1);
			}
			
			printf("start .............. \n");
		}
		else if (!strcasecmp(msg->pszCmd, "stop"))
		{
			
			if (msg->dwDataSize) {
				pid_t *ptNew_pid =  (pid_t *)msg->pbyData;
				pid_stop(*ptNew_pid);
			} else {
				pid_default(0);
			}
			
			printf("stop .............. \n");
		}
		else if (!strcasecmp(msg->pszCmd, "forceCI"))
		{
			printf("forceCI ..............\n");
			temp_data->send_conf = true;
		}
	}
	else if (!strcasecmp(msg->pszHost, SR_MODULE_NAME)) {
		if (!strcasecmp(msg->pszCmd, SUSPEND_CMD)) {
			pthread_mutex_lock(&(temp_data->data_mutex));
			temp_data->process_status = STOP;
			pthread_cond_wait(&(temp_data->data_cond), &(temp_data->data_mutex));
			pthread_mutex_unlock(&(temp_data->data_mutex));
			ATK_AudioCap_Release(*(temp_data->p_cap_handle));
			MsgBroker_SuspendAckMsg();
		} else if(!strcasecmp(msg->pszCmd, RESUME_CMD)) {
			*(temp_data->p_cap_handle) = ATK_AudioCap_Init(temp_data->p_audiocap_config);
			pthread_mutex_lock(&(temp_data->data_mutex));
			temp_data->process_status = START;
			pthread_mutex_unlock(&(temp_data->data_mutex));
		}
	}

	if (msg->bHasResponse)
		msg->dwDataSize = 0;
}

static void exit_process()
{
	is_terminate_ = 1;
}

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

static void print_usage(const char *ap_name)
{
	fprintf(stderr, "Usage:\n"
			"    %s [-b bit_depth] [-c channels] [-d PCM_device_name] [-f command_FIFO_path] [-h] [-i input_type] [-r sample_rate] [-D]\n"
			"Options:\n"
			"    -b                 Bit depth for audio (Default: 16 -> S16_LE).\n"
			"    -c                 Channel number (Default: 2).\n"
			"    -d                 PCM device name for ALSA (Default: hw:0,0).\n"
			"    -f                 The path of command FIFO (Default: /tmp/aenc/c0/command.fifo).\n"
			"    -h                 This help.\n"
			"    -i                 Input type of audio (0: MicIn, 1: LineIn. Default: 1 -> LineIn).\n"
			"    -r                 Sample rate for audio (Default: 8000).\n"
			"    -D                 Run as Daemon.\n"
			, ap_name);
}

int main(int argc, char **argv)
{
	int opt;
	bool is_daemon = false;
	// Default setting.
	int input_type = 1; //0: MicIn, 1: LineIn
	int bit_depth = 16;
	std::string srb_name = "aenc_srb_1";
	std::string pcm_name = "hw:0,0";
	std::string cmd_fifo_path = CMD_FIFO_PATH;
	
	ATK_AUDIOCAP_CONFIG_T audiocap_config;
	ATK_AUDIOCAP_HANDLE_T *cap_handle = NULL;
	memset(&audiocap_config, 0, sizeof(ATK_AUDIOCAP_CONFIG_T));

	audiocap_config.szPcmName = pcm_name.c_str();
	audiocap_config.bIsInterleaved = 1;
	audiocap_config.eFormat = SND_PCM_FORMAT_S16_LE;
	audiocap_config.dwChannelsCount = 2;
	audiocap_config.dwSampleRate = 8000;
	audiocap_config.dwPeriodsPerBuffer = 8;
	audiocap_config.dwPeriodSizeInFrames = PERIOD_SIZE_IN_FRAMES;
	audiocap_config.bUseSimpleConfig = 0;

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

	//audiocap_config.dwPeriodSizeInFrames *= (bit_depth >> 4);

	switch (bit_depth) 
	{
		case 16:
			audiocap_config.eFormat = SND_PCM_FORMAT_S16_LE;
			break;
		case 24:
			audiocap_config.eFormat = SND_PCM_FORMAT_S24_LE;
			break;
		case 32:
			audiocap_config.eFormat = SND_PCM_FORMAT_S32_LE;
			break;
		default:
			print_usage(argv[0]);
			exit(EXIT_FAILURE);
	}
	
	signal(SIGTERM, sig_kill);
	signal(SIGINT, sig_kill);

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

	if (input_type == 1)
	{
		ATK_Audio_InputSelection(kTKAudioLineIn);

		//set audio volume to 90
		ATK_Audio_SetCaptureVolume(90);

	}
	else
	{
		ATK_Audio_InputSelection(kTKAudioMicIn);

		//set audio volume to 90
		ATK_Audio_SetCaptureVolume(90);
	}

	// Callbacks for audio capture and the private user data for callback.
	user_data_t user_data;
	memset(&user_data, 0, sizeof(user_data_t));
	user_data.send_conf = true;
	pthread_mutex_init(&(user_data.data_mutex), NULL); 
	pthread_cond_init(&(user_data.data_cond), NULL);
	user_data.process_status = START;
	user_data.p_audiocap_config = &audiocap_config;
	user_data.p_cap_handle = &cap_handle;

	audiocap_config.pfnCallback = audiocap_callback;
	audiocap_config.pUserData = (void*) (&user_data);

	// TODO: init ALAC encoder
	{
		ALACEncoder* theEncoder = new ALACEncoder;
		
		memset(&user_data.inputFormat, 0, sizeof(AudioFormatDescription));
		memset(&user_data.outputFormat, 0, sizeof(AudioFormatDescription));

		// setup input format
		user_data.inputFormat.mFormatID = kALACFormatLinearPCM;
		user_data.inputFormat.mChannelsPerFrame = audiocap_config.dwChannelsCount;
		user_data.inputFormat.mSampleRate = audiocap_config.dwSampleRate;
		user_data.inputFormat.mBitsPerChannel = bit_depth;
		user_data.inputFormat.mFormatFlags = kALACFormatFlagIsSignedInteger | kALACFormatFlagIsPacked; // always little endian
		user_data.inputFormat.mBytesPerPacket 
			= user_data.inputFormat.mBytesPerFrame 
			= (user_data.inputFormat.mBitsPerChannel >> 3) * user_data.inputFormat.mChannelsPerFrame;
		user_data.inputFormat.mFramesPerPacket = 1;
		user_data.inputFormat.mReserved = 0;

		// setup output format
		user_data.outputFormat.mFormatID = kALACFormatAppleLossless;
		user_data.outputFormat.mSampleRate = audiocap_config.dwSampleRate;
		switch(bit_depth)
		{
			case 16:
				user_data.outputFormat.mFormatFlags = kTestFormatFlag_16BitSourceData;
				break;
			case 24:
				user_data.outputFormat.mFormatFlags = kTestFormatFlag_24BitSourceData;
				break;
			case 32:
				user_data.outputFormat.mFormatFlags = kTestFormatFlag_32BitSourceData;
				break;
			default:
				return -1;
				break;
		}
		user_data.outputFormat.mFramesPerPacket = kALACDefaultFramesPerPacket;
        user_data.outputFormat.mChannelsPerFrame = audiocap_config.dwChannelsCount;
		user_data.outputFormat.mBytesPerPacket 
			= user_data.outputFormat.mBytesPerFrame 
			= user_data.outputFormat.mBitsPerChannel 
			= user_data.outputFormat.mReserved 
			= 0;
		theEncoder->SetFrameSize(user_data.outputFormat.mFramesPerPacket);
		theEncoder->InitializeEncoder(user_data.outputFormat);
		user_data.enc_type = FOURCC_ALAC;
		user_data.enc_handle = (void *) theEncoder;
	}

	//SynRingBuffer
	user_data.srb_handle = SRB_InitWriter(srb_name.c_str(), MAX_RING_BUF_SIZE, 4);
	memset(&user_data.srb_buf, 0, sizeof(srb_buffer_t));
	// Get first buffer from SyncRingBuf.
	SRB_SendGetWriterBuff(user_data.srb_handle, &user_data.srb_buf);

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

#ifdef DUMP_PCM_DATA
	if(open_pcm_file() < 0)
	{
		goto main_end;
	}
#endif
	MsgBroker_RegisterMsg(cmd_fifo_path.c_str());
	// Enter the main message loop.
	MsgBroker_Run(cmd_fifo_path.c_str(), msg_callback, &user_data, &is_terminate_);
	MsgBroker_UnRegisterMsg();

main_end:

#ifdef DUMP_PCM_DATA
		close_pcm_file();
#endif
	ATK_AudioCap_Release(cap_handle);
	delete (ALACEncoder*)user_data.enc_handle;
	SRB_Release(user_data.srb_handle);
	pthread_mutex_destroy(&(user_data.data_mutex));
	pthread_cond_destroy(&(user_data.data_cond));

	return 0;
}
