/*
 * \brief  Audio_out-out driver for Linux
 * \author Christian Helmuth
 * \author Sebastian Sumpf
 * \author Josef Soentgen
 * \date   2010-05-11
 *
 * FIXME session and driver shutdown not implemented (audio_drv_stop)
 */

/*
 * Copyright (C) 2010-2017 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/log.h>
#include <base/sleep.h>
#include <base/heap.h>
#include <root/component.h>
#include <audio_out_session/rpc_object.h>
#include <util/misc_math.h>
#include <timer_session/connection.h>

/* local includes */
#include <alsa.h>


using namespace Genode;

enum Channel_number { LEFT, RIGHT, MAX_CHANNELS, INVALID = MAX_CHANNELS };


namespace Audio_out
{
	class  Session_component;
	class  Out;
	class  Root;
	struct Root_policy;
	struct Main;

	static Session_component *channel_acquired[MAX_CHANNELS];
};


class Audio_out::Session_component : public Audio_out::Session_rpc_object
{
	private:

		Channel_number _channel;

	public:

		Session_component(Genode::Env &env, Channel_number channel, Signal_context_capability data_cap)
		:
			Session_rpc_object(env, data_cap),
			_channel(channel)
		{
			Audio_out::channel_acquired[_channel] = this;
		}

		~Session_component()
		{
			Audio_out::channel_acquired[_channel] = 0;
		}
};


static bool channel_number_from_string(const char     *name,
                                       Channel_number *out_number)
{
	static struct Names {
		const char    *name;
		Channel_number number;
	} names[] = {
		{ "left", LEFT }, { "front left", LEFT },
		{ "right", RIGHT }, { "front right", RIGHT },
		{ 0, INVALID }
	};

	for (Names *n = names; n->name; ++n)
		if (!strcmp(name, n->name)) {
			*out_number = n->number;
			return true;
		}

	return false;
}


/*
 * Root component, handling new session requests.
 */
class Audio_out::Out
{
	private:

		Genode::Env                            &_env;
		Genode::Signal_handler<Audio_out::Out>  _data_avail_dispatcher;
		Genode::Signal_handler<Audio_out::Out>  _timer_dispatcher;

		Timer::Connection _timer { _env };

		bool _active() {
			return  channel_acquired[LEFT] && channel_acquired[RIGHT] &&
			        channel_acquired[LEFT]->active() && channel_acquired[RIGHT]->active();
		}

		Stream *left()  { return channel_acquired[LEFT]->stream(); }
		Stream *right() { return channel_acquired[RIGHT]->stream(); }

		void _advance_position(Packet *l, Packet *r)
		{
			bool full_left = left()->full();
			bool full_right = right()->full();

			left()->pos(left()->packet_position(l));
			right()->pos(right()->packet_position(r));

			left()->increment_position();
			right()->increment_position();

			Session_component *channel_left  = channel_acquired[LEFT];
			Session_component *channel_right = channel_acquired[RIGHT];

			if (full_left)
				channel_left->alloc_submit();

			if (full_right)
				channel_right->alloc_submit();

			channel_left->progress_submit();
			channel_right->progress_submit();
		}

		bool _play_packet()
		{
			Packet *p_left  = left()->get(left()->pos());
			Packet *p_right = right()->get(left()->pos());

			/* convert float to S16LE */
			static short data[2 * PERIOD];

			if (p_left->valid() && p_right->valid()) {

				for (unsigned i = 0; i < 2 * PERIOD; i += 2) {
					data[i] = p_left->content()[i / 2] * 32767;
					data[i + 1] = p_right->content()[i / 2] * 32767;
				}

				p_left->invalidate();
				p_right->invalidate();

				/* blocking-write packet to ALSA */
				while (audio_drv_play(data, PERIOD)) {
					/* try to restart the driver silently */
					audio_drv_stop();
					audio_drv_start();
				}

				p_left->mark_as_played();
				p_right->mark_as_played();
			}

			_advance_position(p_left, p_right);

			return true;
		}

		void _handle_data_avail() { }

		void _handle_timer()
		{
			if (_active()) _play_packet();
		}

	public:

		Out(Genode::Env &env)
		:
			_env(env),
			_data_avail_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_data_avail),
			_timer_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_timer)
		{
			_timer.sigh(_timer_dispatcher);

			uint64_t const us = (Audio_out::PERIOD * 1000 / Audio_out::SAMPLE_RATE)*1000;
			_timer.trigger_periodic(us);
		}

		Signal_context_capability data_avail_sigh() { return _data_avail_dispatcher; }
};


/**
 * Session creation policy for our service
 */
struct Audio_out::Root_policy
{
	void aquire(const char *args)
	{
		size_t ram_quota =
			Arg_string::find_arg(args, "ram_quota"  ).ulong_value(0);
		size_t session_size =
			align_addr(sizeof(Audio_out::Session_component), 12);

		if ((ram_quota < session_size) ||
		    (sizeof(Stream) > ram_quota - session_size)) {
			Genode::error("insufficient 'ram_quota', got ", ram_quota,
			              " need ", sizeof(Stream) + session_size);
			throw Genode::Insufficient_ram_quota();
		}

		char channel_name[16];
		Channel_number channel_number;
		Arg_string::find_arg(args, "channel").string(channel_name,
		                                             sizeof(channel_name),
		                                             "left");
		if (!channel_number_from_string(channel_name, &channel_number))
			throw Genode::Service_denied();
		if (Audio_out::channel_acquired[channel_number])
			throw Genode::Service_denied();
	}

	void release() { }
};


namespace Audio_out {
	typedef Root_component<Session_component, Root_policy> Root_component;
}


class Audio_out::Root : public Audio_out::Root_component
{
	private:

		Genode::Env &_env;

		Signal_context_capability _data_cap;

	protected:

		Session_component *_create_session(const char *args) override
		{
			char channel_name[16];
			Channel_number channel_number = INVALID;
			Arg_string::find_arg(args, "channel").string(channel_name,
			                                             sizeof(channel_name),
			                                             "left");
			channel_number_from_string(channel_name, &channel_number);

			return new (md_alloc())
				Session_component(_env, channel_number, _data_cap);
		}

	public:

		Root(Genode::Env &env, Allocator &md_alloc,
		     Signal_context_capability data_cap)
		: Root_component(env.ep(), md_alloc), _env(env), _data_cap(data_cap)
		{ }
};


struct Audio_out::Main
{
	Genode::Env  &env;
	Genode::Heap  heap { env.ram(), env.rm() };

	Genode::Attached_rom_dataspace config { env, "config" };

	Main(Genode::Env &env) : env(env)
	{
		char dev[32] = { 'h', 'w', 0 };
		try {
			config.xml().attribute("alsa_device").value(dev, sizeof(dev));
		} catch (...) { }

		/* init ALSA */
		int err = audio_drv_init(dev);
		if (err) {
			if (err == -1) {
				Genode::error("could not open ALSA device ", Genode::Cstring(dev));
			} else {
				Genode::error("could not initialize driver error ", err);
			}

			throw -1;
		}
		audio_drv_start();

		static Audio_out::Out  out(env);
		static Audio_out::Root root(env, heap, out.data_avail_sigh());
		env.parent().announce(env.ep().manage(root));
		Genode::log("--- start Audio_out ALSA driver ---");
	}
};


/***************
 ** Component **
 ***************/

void Component::construct(Genode::Env &env) { static Audio_out::Main main(env); }
