// -*- C++ -*-
/*!
 * @file  PulseAudioOutput.cpp
 * @author Yosuke Matsusaka <yosuke.matsusaka@aist.go.jp> and OpenHRI development team
 *
 * Copyright (C) 2010
 *     Intelligent Systems Research Institute,
 *     National Institute of
 *         Advanced Industrial Science and Technology (AIST), Japan
 *     All rights reserved.
 *
 * @date $Date$
 *
 * $Id$
 */

#include "PulseAudioOutput.h"

#define MIN_INTERNAL_BUFFER 16000

// Module specification
// <rtc-template block="module_spec">
static const char* pulseaudiooutput_spec[] =
{
		"implementation_id", "PulseAudioOutput",
		"type_name",         "PulseAudioOutput",
		"description",       "Audio output component using pulseaudio (C++ implementation)",
		"version",           "0.0.1",
		"vendor",            "AIST",
		"category",          "communication",
		"activity_type",     "DataFlowComponent",
		"max_instance",      "1",
		"language",          "C++",
		"lang_type",         "script",
		"conf.default.OutputSampleRate", "16000",
		"conf.default.OutputSampleByte", "int16",
		"conf.default.OutputChannelNumbers", "1",
		"conf.default.DelayCount", "0",
		"conf.__widget__.OutputSampleByte", "radio",
		"conf.__constraints__.OutputSampleByte", "(int8, int16, int24)",
		"conf.__widget__.OutputChannelNumbers", "spin",
		"conf.__constraints__.OutputChannelNumbers", "x >= 1",
		"conf.__widget__.OutputSampleRate", "spin",
		"conf.__constraints__.OutputSampleRate", "x >= 1",
		"conf.__widget__.DelayCount", "spin",
		"conf.__constraints__.DelayCount", "x >= 0",
		""
};
// </rtc-template>

/*!
 * @brief constructor
 */
DataListener::DataListener(const char *name, void* data)
{
	m_obj = data;
	m_name = name;
}

/*!
 * @brief destructor
 */
DataListener::~DataListener()
{
}

void DataListener::operator ()(const ConnectorInfo& info, const TimedOctetSeq& data)
{
	if(m_name == "ON_BUFFER_WRITE")
	{
		PulseAudioOutput *p = (PulseAudioOutput *)m_obj;
		p->RcvBuffer(data);
	}
}

/*!
 * @brief constructor
 */
PulseAudioOutput::PulseAudioOutput(RTC::Manager* manager)
// <rtc-template block="initializer">
: RTC::DataFlowComponentBase(manager),
m_in_dataIn("SampleInPort0", m_in_data),
m_out_dataOut("OutputData", m_out_data)

// </rtc-template>
{
	is_active = false;
}

/*!
 * @brief destructor
 */
PulseAudioOutput::~PulseAudioOutput()
{
}

RTC::ReturnCode_t PulseAudioOutput::onInitialize()
{
	// Registration: InPort/OutPort/Service
	// <rtc-template block="registration">
	// Set InPort buffers
	addInPort("SampleInPort0", m_in_dataIn);

	/* setting datalistener event */
	m_in_dataIn.addConnectorDataListener(ON_BUFFER_WRITE, new DataListener("ON_BUFFER_WRITE", this), false);

	// Set OutPort buffer
	registerOutPort("OutputData", m_out_dataOut);

	// Set service provider to Ports

	// Set service consumers to Ports

	// Set CORBA Service Ports

	// </rtc-template>
	bindParameter("OutputSampleRate", m_spec.rate, "16000");
	bindParameter("OutputSampleByte", m_formatstr, "int16");
	bindParameter("OutputChannelNumbers", m_channels, "1");
	bindParameter("DelayCount", m_delay, "0");

	return RTC::RTC_OK;
}

pa_sample_format PulseAudioOutput::getFormat(std::string str)
{
	if(str == "int8")
	{
		return PA_SAMPLE_U8;
	}
	else if (str == "int16")
	{
		return PA_SAMPLE_S16LE;
	}
	else if (str == "int24")
	{
		return PA_SAMPLE_S24LE ;
	}
	else
	{
		return PA_SAMPLE_S16LE;
	}
}

RTC::ReturnCode_t PulseAudioOutput::onActivated(RTC::UniqueId ec_id)
{

	try {
		m_spec.format = getFormat(m_formatstr);
		m_spec.channels = (uint8_t)m_channels;

		m_simple = pa_simple_new(
				NULL,               //!< Server name, or NULL for default
				"PulseAudioOutput", //!< A descriptive name for this client (application name, ...)
				PA_STREAM_PLAYBACK, //!< Open this stream for recording or playback?
				NULL,               //!< Sink (resp. source) name, or NULL for default
				"playback",         //!< A descriptive name for this client (application name, song title, ...)
				&m_spec,            //!< The sample type to use
				NULL,               //!< The channel map to use, or NULL for default
				NULL,               //!< Buffering attributes, or NULL for default
				&m_err );           //!< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here.

		if( m_simple == NULL )
		{
			throw m_err;
		}

	} catch (...) {
		std::cout << pa_strerror(m_err) << std::endl;
		return RTC::RTC_ERROR;
	}

	if (!m_data.empty()) {
		m_data.clear();  //!< queue buffer clear
	}
	is_active = true;
	m_waitflg = true;
	return RTC::RTC_OK;
}

RTC::ReturnCode_t PulseAudioOutput::onExecute(RTC::UniqueId ec_id)
{
	if (m_simple) {
		WriteBuffer();
	}
	return RTC::RTC_OK;
}

void PulseAudioOutput::RcvBuffer(TimedOctetSeq data)
{
	if (is_active == true) {
		//! The queuing does input data.
		m_mutex.lock();
		unsigned long i = 0;
		if(m_waitflg == true)
		{
			m_waitflg = false;
			if(m_delay > 0){
				for(i = 0; i < (unsigned long)((m_delay + 1) * 512); i++)
				{
					m_data.push_back(0);  //!< enqueue
				}
			}
		}

		unsigned long len = data.data.length();  //!< inport data length
		for (i = 0; i < len; i++) {
			m_data.push_back((unsigned char)data.data[i]);  //!< enqueue
		}
		m_mutex.unlock();
	}
	return;
}

int PulseAudioOutput::WriteBuffer(void)
{
	// get writable length from audio library
	simple_recast *psimple = (simple_recast *)m_simple;
	pa_threaded_mainloop_lock(psimple->mainloop);
	size_t len = pa_stream_writable_size(psimple->stream);

	if (len == 0) {
		pa_threaded_mainloop_unlock(psimple->mainloop);
		return 0;
	}

	//  now we copy the data from component buffer to audio device buffer
	m_mutex.lock();
	// get a data buffer from the audio device
	void *data;
	size_t nbytes = len;
	pa_stream_begin_write(psimple->stream, &data, &nbytes);

	// copy the data to the audio buffer
	unsigned char *buff = (unsigned char *)data;
	for (unsigned long i = 0; i < (unsigned long)nbytes; i++) {
		if(!m_data.empty()){
			*buff++ = m_data.front();
			m_data.pop_front();
		}
		else{
			*buff++ = 0;
          m_waitflg = true;
		}
	}

	// set the data to the outport
	m_out_data.data.length(nbytes);  //!< set outport data length
	memcpy((void *)&(m_out_data.data[0]), data, nbytes);

	m_mutex.unlock();

	// send the data to audio device
	if (pa_stream_write(psimple->stream, data, nbytes, NULL, 0, PA_SEEK_RELATIVE) < 0) {
		std::cout << m_err << std::endl;
	}
	pa_threaded_mainloop_unlock(psimple->mainloop);

	// send the data to the outport
	m_out_dataOut.write();

	return 0;
}

RTC::ReturnCode_t PulseAudioOutput::onDeactivated(RTC::UniqueId ec_id)
{
	is_active = false;
	m_mutex.lock();
	try {
		if (!m_data.empty()) {
			m_data.clear();  //!< queue buffer clear
		}

		if (m_simple) {
			pa_simple_free(m_simple);
			m_simple = NULL;
		}
	} catch (...) {
		std::cout << "onDeactivated error" << std::endl;
	}
	m_mutex.unlock();
	return RTC::RTC_OK;
}

RTC::ReturnCode_t PulseAudioOutput::onFinalize()
{
	is_active = false;
	m_mutex.lock();
	try {
		if (!m_data.empty()) {
			m_data.clear();  //!< queue buffer clear
		}
		if (m_simple)
		{
			pa_simple_free(m_simple);
			m_simple = NULL;
		}
	} catch (...) {
		std::cout << "onFinalize error" << std::endl;
	}
	m_mutex.unlock();
	return RTC::RTC_OK;
}

/*!
 * @brief constructor
 */
PulseAudioOutputManager::PulseAudioOutputManager()
{
	m_manager = RTC::Manager::init(0,NULL);
	m_manager->init(0,NULL);
	m_manager->setModuleInitProc(ModuleInit);
	m_manager->activateManager();
}

/*!
 * @brief destructor
 */
PulseAudioOutputManager::~PulseAudioOutputManager()
{
}

void PulseAudioOutputManager::start()
{
	m_manager->runManager();
}

void PulseAudioOutputManager::ModuleInit(RTC::Manager* manager)
{
	RTC::RtcBase* comp;

	PulseAudioOutputInit(manager);
	comp = manager->createComponent("PulseAudioOutput");

	if (comp == NULL)
	{
	}
	return;
}


extern "C"
{
	void PulseAudioOutputInit(RTC::Manager* manager)
	{
		coil::Properties profile(pulseaudiooutput_spec);
		manager->registerFactory(profile,
				RTC::Create<PulseAudioOutput>,
				RTC::Delete<PulseAudioOutput>);
	}

};

int main(int argc, char** argv)
{
	PulseAudioOutputManager output_manager;

	output_manager.start();
	return 0;
}

