/*
 * module-agl-audio -- PulseAudio module for providing audio routing support
 * (forked from "module-murphy-ivi" - https://github.com/otcshare )
 * Copyright (c) 2012, Intel Corporation.
 * Copyright (c) 2016, IoT.bzh
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St - Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 */
#include "tracker.h"
#include "discover.h"
#include "router.h"
#include "utils.h"

struct pa_card_hooks {
    pa_hook_slot    *put;
    pa_hook_slot    *unlink;
    pa_hook_slot    *profchg;
};

struct pa_port_hooks {
    pa_hook_slot    *avail;
};

struct pa_sink_hooks {
    pa_hook_slot    *put;
    pa_hook_slot    *unlink;
    pa_hook_slot    *portchg;
};

struct pa_source_hooks {
    pa_hook_slot    *put;
    pa_hook_slot    *unlink;
    pa_hook_slot    *portchg;
};

struct pa_sink_input_hooks {
    pa_hook_slot    *neew;
    pa_hook_slot    *put;
    pa_hook_slot    *unlink;
};

struct pa_source_output_hooks {
    pa_hook_slot    *neew;
    pa_hook_slot    *put;
    pa_hook_slot    *unlink;
};

struct agl_tracker {
    pa_card_hooks           card;
    pa_port_hooks           port;
    pa_sink_hooks           sink;
    pa_source_hooks         source;
    pa_sink_input_hooks     sink_input;
    pa_source_output_hooks  source_output;
};


static pa_hook_result_t card_put (void *, void *, void *);
static pa_hook_result_t card_unlink (void *, void *, void *);
static pa_hook_result_t card_profile_changed (void *, void *, void *);

static pa_hook_result_t port_available_changed (void *, void *, void *);

static pa_hook_result_t sink_put (void *, void *, void *);
static pa_hook_result_t sink_unlink (void *, void *, void *);
static pa_hook_result_t sink_port_changed (void *, void *, void *);

static pa_hook_result_t source_put (void *, void *, void *);
static pa_hook_result_t source_unlink (void *, void *, void *);
static pa_hook_result_t source_port_changed (void *, void *, void *);

static pa_hook_result_t sink_input_new (void *, void *, void *);
static pa_hook_result_t sink_input_put (void *, void *, void *);
static pa_hook_result_t sink_input_unlink (void *, void *, void *);

static pa_hook_result_t source_output_new (void *, void *, void *);
static pa_hook_result_t source_output_put (void *, void *, void *);
static pa_hook_result_t source_output_unlink (void *, void *, void *);


agl_tracker *agl_tracker_init (struct userdata *u)
{
	pa_core *core;
	pa_hook *hooks;
	agl_tracker *tracker;
	pa_card_hooks *card;
	pa_port_hooks *port;
	pa_sink_hooks *sink;
	pa_source_hooks *source;
	pa_sink_input_hooks *sinp;
	pa_source_output_hooks *sout;

	pa_assert (u);
	pa_assert_se (core = u->core);
	pa_assert_se (hooks = core->hooks);

	tracker = pa_xnew0 (agl_tracker, 1);
	card = &tracker->card;
	port = &tracker->port;
	sink = &tracker->sink;
	source = &tracker->source;
	sinp = &tracker->sink_input;
	sout = &tracker->source_output;

	 /* card */
	card->put = pa_hook_connect (hooks + PA_CORE_HOOK_CARD_PUT,
	                             PA_HOOK_LATE, card_put, u);
	card->unlink = pa_hook_connect (hooks + PA_CORE_HOOK_CARD_UNLINK,
	                                PA_HOOK_LATE, card_unlink, u);
	card->profchg = pa_hook_connect (hooks + PA_CORE_HOOK_CARD_PROFILE_CHANGED,
	                                 PA_HOOK_LATE, card_profile_changed, u);

	 /* port */
	port->avail = pa_hook_connect (hooks + PA_CORE_HOOK_PORT_AVAILABLE_CHANGED,
	                               PA_HOOK_LATE, port_available_changed, u);

	 /* sink */
	sink->put = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_PUT,
	                             PA_HOOK_LATE, sink_put, u);
	sink->unlink = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_UNLINK,
	                                PA_HOOK_LATE, sink_unlink, u);
	sink->portchg = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_PORT_CHANGED,
	                                 PA_HOOK_LATE, sink_port_changed, u);

	 /* source */
	source->put = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_PUT,
		                       PA_HOOK_LATE, source_put, u);
	source->unlink = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_UNLINK,
		                          PA_HOOK_LATE, source_unlink, u);
	source->portchg = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_PORT_CHANGED,
		                          PA_HOOK_LATE, source_port_changed, u);

	 /* sink-input */
	sinp->neew = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_INPUT_NEW,
		                      PA_HOOK_EARLY, sink_input_new, u);
	sinp->put = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_INPUT_PUT,
		                     PA_HOOK_LATE, sink_input_put, u);
	sinp->unlink = pa_hook_connect (hooks + PA_CORE_HOOK_SINK_INPUT_UNLINK,
		                        PA_HOOK_LATE, sink_input_unlink, u);

	 /* source-output */
	sout->neew = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
		                      PA_HOOK_EARLY, source_output_new, u);
	sout->put = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_OUTPUT_PUT,
				     PA_HOOK_LATE, source_output_put, u);
	sout->unlink = pa_hook_connect (hooks + PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK,
		                        PA_HOOK_LATE, source_output_unlink, u);

	return tracker;	
}

void agl_tracker_done (struct userdata *u)
{
	agl_tracker *tracker;	
	pa_card_hooks *card;
	pa_port_hooks *port;
	pa_sink_hooks *sink;
	pa_source_hooks *source;
	pa_sink_input_hooks *sinp;

	if (u && (tracker = u->tracker)) {

		card = &tracker->card;
		pa_hook_slot_free (card->put);
		pa_hook_slot_free (card->unlink);
		pa_hook_slot_free (card->profchg);

		port = &tracker->port;
		pa_hook_slot_free (port->avail);

		sink = &tracker->sink;
		pa_hook_slot_free (sink->put);
		pa_hook_slot_free (sink->unlink);
		pa_hook_slot_free (sink->portchg);

		source = &tracker->source;
		pa_hook_slot_free (source->put);
		pa_hook_slot_free (source->unlink);
		pa_hook_slot_free (source->portchg);

		sinp = &tracker->sink_input;
		pa_hook_slot_free (sinp->neew);
		pa_hook_slot_free (sinp->put);
		pa_hook_slot_free (sinp->unlink);

		pa_xfree(tracker);
        
		u->tracker = NULL;
	}
}

 /* main logic initialization function */
void agl_tracker_synchronize (struct userdata *u)
{
	pa_core *core;
	pa_card *card;
	pa_sink *sink;
	pa_source *source;
	pa_sink_input *sinp;
	pa_source_output *sout;
	uint32_t index;

	pa_assert (u);
	pa_assert_se (core = u->core);

	 /* discover.c : add each valid USB/PCI/Platform ALSA sound card */
	PA_IDXSET_FOREACH (card, core->cards, index) {
        	agl_discover_add_card (u, card);
	}

	PA_IDXSET_FOREACH (sink, core->sinks, index) {
		agl_discover_add_sink (u, sink, false);
	}

	PA_IDXSET_FOREACH (source, core->sources, index) {
		agl_discover_add_source (u, source);
	}

	PA_IDXSET_FOREACH(sinp, core->sink_inputs, index) {
		agl_discover_register_sink_input (u, sinp);
	}

	PA_IDXSET_FOREACH(sout, core->source_outputs, index) {
		agl_discover_register_source_output (u, sout);
	}

	agl_router_make_routing (u);
}


 /* HOOK IMPLEMENTATIONS */
static pa_hook_result_t card_put (void *hook_data,
                                  void *call_data,
                                  void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t card_unlink (void *hook_data,
                                     void *call_data,
                                     void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t card_profile_changed (void *hook_data,
                                              void *call_data,
                                              void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t port_available_changed (void *hook_data,
                                                void *call_data,
                                                void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t sink_put (void *hook_data,
                                  void *call_data,
                                  void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t sink_unlink (void *hook_data,
                                     void *call_data,
                                     void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t sink_port_changed (void *hook_data,
                                           void *call_data,
                                           void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t source_put (void *hook_data,
                                    void *call_data,
                                    void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t source_unlink (void *hook_data,
                                       void *call_data,
                                       void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t source_port_changed (void *hook_data,
                                             void *call_data,
                                             void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t sink_input_new (void *hook_data,
                                        void *call_data,
                                        void *slot_data)
{
	/* main hook, called by each client in its 1st phase */
	pa_sink_input_new_data *data = (pa_sink_input_new_data *)call_data;
	struct userdata *u = (struct userdata *)slot_data;
	bool success;

	success = agl_discover_preroute_sink_input (u, data);

	return success ? PA_HOOK_OK : PA_HOOK_CANCEL;
}

static pa_hook_result_t sink_input_put (void *hook_data,
                                        void *call_data,
                                        void *slot_data)
{
	/* called by each client in its 2nd phase */
	pa_sink_input *sinp = (pa_sink_input *)call_data;
	struct userdata *u = (struct userdata *)slot_data;

	agl_discover_add_sink_input (u, sinp);

	return PA_HOOK_OK;
}

static pa_hook_result_t sink_input_unlink (void *hook_data,
                                           void *call_data,
                                           void *slot_data)
{
	/* called by each client when stopping sound */
	pa_sink_input *sinp = (pa_sink_input *)call_data;
	struct userdata *u = (struct userdata *)slot_data;

	agl_discover_remove_sink_input (u, sinp);

	return PA_HOOK_OK;
}

static pa_hook_result_t source_output_new (void *hook_data,
                                           void *call_data,
                                           void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t source_output_put (void *hook_data,
                                           void *call_data,
                                           void *slot_data)
{
	return PA_HOOK_OK;
}

static pa_hook_result_t source_output_unlink (void *hook_data,
                                              void *call_data,
                                              void *slot_data)
{
	return PA_HOOK_OK;
}