/*
  The mediastreamer library aims at providing modular media processing and I/O
	for linphone, but also for any telephony application.
  Copyright (C) 2001  Simon MORLAT simon.morlat@linphone.org
  										
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that 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 library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "ms.h"
#include "sndcard.h"
#include "mscodec.h"

#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef VIDEO_ENABLED
extern void ms_video_source_register_all();
#endif
#ifdef HAVE_ILBC
extern void ms_ilbc_codec_init();
#endif

/**
 * ms_init:
 *
 *
 * Initialize the mediastreamer. This must be the first function called in a program
 * using the mediastreamer library.
 *
 *
 */
void ms_init()
{
	if (!g_thread_supported()) g_thread_init (NULL);
#ifdef HAVE_GLIB
	if (!g_module_supported()){
		g_error("GModule is not supported.");
	}
#endif
	/* initialize the oss subsystem */
	snd_card_manager_init(snd_card_manager);
	/* register the statically linked codecs */
	ms_codec_register_all();
#ifdef VIDEO_ENABLED
	ms_video_source_register_all();
#endif
#ifdef HAVE_ILBC
	ms_ilbc_codec_init();
#endif
}


static gint compare(gconstpointer a, gconstpointer b)
{
	MSFilter *f1=(MSFilter*)a,*f2=(MSFilter*)b;
	if (f1->klass<f2->klass) return -1;
	if (f1->klass==f2->klass) return 0;
	/* if f1->klass>f2->klass ....*/
	return 1;
}

static GList *g_list_append_if_new(GList *l,gpointer data)
{
	GList *res=l;
	if (g_list_find(res,data)==NULL)
		res=g_list_append(res,data);
	return(res);
}

static GList *get_nexts(MSFilter *f,GList *l)
{
	int i;
	MSFifo *fifo;
	MSQueue *q;
	GList *res=l;
	
	/* check fifos*/
	for (i=0;i	<f->klass->max_foutputs;i++)
	{
		fifo=f->outfifos[i];
		if (fifo!=NULL) res=g_list_append_if_new(res,(gpointer)fifo->next_data);
	}
	/* check queues*/
	for (i=0;i	<f->klass->max_qoutputs;i++)
	{
		q=f->outqueues[i];
		if (q!=NULL) res=g_list_append_if_new(res,(gpointer)q->next_data);
	}
	return(res);
}
	
/* compile graphs attached to a sync source*/
int ms_compile(MSSync *sync)
{
	int i;
	GList *list1=NULL,*list2=NULL,*elem;
	GList *proc_chain=NULL;
	MSFilter *f;
	
	/* first free the old list if we are just updating*/
	if (sync->execution_list!=NULL) g_list_free(sync->execution_list);
	/* get the list of filters attached to this sync*/
	for (i=0;i<sync->filters;i++)
	{
		/* //printf("found filter !\n"); */
		list1=g_list_append(list1,sync->attached_filters[i]);
	}
	/* find the processing chain */
	while (list1!=NULL)
	{
		list2=NULL;
		/* sort the list by types of filter*/
		list1=g_list_sort(list1,compare);
		/* save into the processing chain list*/
		/* //printf("list1 :%i elements\n",g_list_length(list1)); */
		proc_chain=g_list_concat(proc_chain,list1);
		/* get all following filters. They are appended to list2*/
		elem=list1;
		while (elem!=NULL)
		{
			f=(MSFilter*)(elem->data);
			/* check if filter 's status */
			if (f->klass->attributes & FILTER_CAN_SYNC)
			{
				sync->samples_per_tick=0;
			}
			list2=get_nexts(f,list2);
			elem=g_list_next(elem);
		}
		list1=list2;
	}
	sync->execution_list=proc_chain;
	sync->flags&=~MS_SYNC_NEED_UPDATE;
	ms_trace("%i filters successfully compiled in a processing chain.",g_list_length(sync->execution_list));
	return 0;
}

/*execute the processing chain attached to a sync source. It is called as a thread by ms_main()*/
void *ms_thread_run(void *sync_ptr)
{
	MSSync *sync=(MSSync*) sync_ptr;
	GList *filter;
	MSFilter *f;
	
	
	ms_sync_lock(sync);  
	while(sync->run)
	{
		/* //g_message("sync->run=%i",sync->run); */
		if (sync->samples_per_tick==0) ms_sync_suspend(sync);
		if (sync->flags & MS_SYNC_NEED_UPDATE){
			ms_compile(sync);
			ms_sync_setup(sync);
		}
		filter=sync->execution_list;
		ms_sync_unlock(sync);
		/* //ms_trace("Calling synchronisation"); */
		ms_sync_synchronize(sync);
		while(filter!=NULL)
		{
			f=(MSFilter*)filter->data;
			if (MS_FILTER_GET_CLASS(f)->attributes & FILTER_IS_SOURCE)
			{
				/* execute it once */
				ms_trace("Running source filter %s.",f->klass->name);
				ms_filter_process(f);
			}
			else
			{
				/* make the filter process its input data until it has no more */
				while ( ms_filter_fifos_have_data(f) || ms_filter_queues_have_data(f) )
				{
					ms_trace("Running filter %s.",f->klass->name);
					ms_filter_process(f);
				}
			}
			filter=g_list_next(filter);
		}
		ms_sync_lock(sync);  
	}
	g_cond_signal(sync->stop_cond);	/* signal that the sync thread has finished */
	ms_sync_unlock(sync);
	g_message("Mediastreamer processing thread is exiting.");
	return NULL;
}

/* stop the processing chain attached to a sync source.*/
void ms_thread_stop(MSSync *sync)
{
	if (sync->thread!=NULL)
	{
		if (sync->samples_per_tick==0)
		{
			/* to wakeup the thread */
			/* //g_cond_signal(sync->thread_cond); */
		}
		g_mutex_lock(sync->lock);
		sync->run=0;
		sync->thread=NULL;
		g_cond_wait(sync->stop_cond,sync->lock);
		g_mutex_unlock(sync->lock);
	}
	/* //g_message("ms_thread_stop() finished."); */
}

/**
 * ms_start:
 * @sync: A synchronisation source to be started.
 *
 * Starts a thread that will shedule all processing chains attached to the synchronisation source @sync.
 *
 *
 */
void ms_start(MSSync *sync)
{
	if (sync->run==1) return; /*already running*/
	ms_compile(sync);  
	ms_sync_setup(sync);
	/* this is to avoid race conditions, for example:
							ms_start(sync);
							ms_oss_write_start(ossw);
							here tge ossw filter need to be compiled to run ms_oss_write_start()
							*/
	ms_trace("ms_start: creating new thread.");
	sync->run=1;
	sync->thread=g_thread_create((GThreadFunc)ms_thread_run,(gpointer)sync,TRUE,NULL);
	if (sync->thread==NULL){
		g_warning("Could not create thread !");
	}
}

/**
 * ms_stop:
 * @sync: A synchronisation source to be stopped.
 *
 * Stop the thread that was sheduling the processing chains attached to the synchronisation source @sync.
 * The processing chains are kept unchanged, no object is freed. The synchronisation source can be restarted using ms_start().
 *
 *
 */
void ms_stop(MSSync *sync)
{
	ms_thread_stop(sync);
	ms_sync_unsetup(sync);
}


gint ms_load_plugin(gchar *path)
{
#ifdef HAVE_GLIB
	g_module_open(path,0);
#endif
	return 0;
}

gchar * ms_proc_get_param(gchar *parameter)
{
	gchar *file;
	int fd;
	int err,len;
	gchar *p,*begin,*end;
	gchar *ret;
	fd=open("/proc/cpuinfo",O_RDONLY);
	if (fd<0){
		g_warning("Could not open /proc/cpuinfo.");
		return NULL;
	}
	file=g_malloc(1024);
	err=read(fd,file,1024);
	file[err-1]='\0';
	/* find the parameter */
	p=strstr(file,parameter);
	if (p==NULL){
		/* parameter not found */
		g_free(file);
		return NULL;		
	}
	/* find the following ':' */
	p=strchr(p,':');
	if (p==NULL){
		g_free(file);
		return NULL;
	}
	/* find the value*/
	begin=p+2;
	end=strchr(begin,'\n');
	if (end==NULL) end=strchr(begin,'\0');
	len=end-begin+1;
	ret=g_malloc(len+1);
	snprintf(ret,len,"%s",begin);
	/* //printf("%s=%s\n",parameter,ret); */
	g_free(file);
	return ret;
}

gint ms_proc_get_type()
{
	static int proc_type=0;
	gchar *value;
	if (proc_type==0){
		value=ms_proc_get_param("cpu family");
		if (value!=NULL) {
			proc_type=atoi(value);
			g_free(value);
		}else return -1;
	}
	return proc_type;
}

gint ms_proc_get_speed()
{
	char *value;
	static int proc_speed=0;
	if (proc_speed==0){
		value=ms_proc_get_param("cpu MHz");
		if (value!=NULL){
			proc_speed=atoi(value);
			g_free(value);
		}else return -1;
	}
	/* //printf("proc_speed=%i\n",proc_speed); */
	return proc_speed;
}
