/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Stefan Denk <denk@ira.uka.de>
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * $Id: vtmain.cc,v 1.4 2006/01/05 10:31:44 denk Exp $
 *
 */

// libc

#include <assert.h>

// vidtrans

#include "vtmain.h"
#include "dvdrvidtrans.h" // remove after no more logging to file is needed

vtmain * vtmain::_thethread;
FILE * logfp;

vtmain::vtmain(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
  	param_videosource_age_max = 1000; // about 20 minutes
	videosourcecleanup_interval = 100; // about 2 minutes
	publish_interval = 10; // sends a lot of messages
	_publications_per_round = 10; // sends a lot of messages
	housekeeping_interval = 100; // don't know whether this is a good choice
	videosources = new cvideosources(&transport);
	_q.clear();
	receiver = NULL;
	_s = -1;
	downloaddir[0]='\0';
	_thethread = this;
}

const vtmain * 
vtmain::getthread(){
	return _thethread;
}


bool vtmain::SetupParse(const char *Name, const char *Value)
{
	//dsyslog("%s: with name '%s' and val '%s'", __PRETTY_FUNCTION__, Name, Value);
	if (!strcasecmp(Name, "videosource_age_max"))
		param_videosource_age_max = atoi(Value);
	else if (!strcasecmp(Name, "videosourcecleanup_interval"))
		videosourcecleanup_interval = atoi(Value);
	else if (!strcasecmp(Name, "publish_interval"))
		publish_interval = atoi(Value);
	else if (!strcasecmp(Name, "publications_per_round"))
		_publications_per_round = atoi(Value);
	else if (!strcasecmp(Name, "client_threads_max"))
		set_client_threads_max(atoi(Value));
	else if (!strcasecmp(Name, "housekeeping_interval"))
		housekeeping_interval = atoi(Value);
	else if (!strcasecmp(Name, "transport"))
		transport = string(Value);
	else if (!strcasecmp(Name, "downloaddir"))
		snprintf(downloaddir, MAXPATHLENGTH, "%s", Value);
	else if (!strcasecmp(Name, "speedlimit"))
		speedlimit = atoi(Value);
	else
		return false;
	return true;
}

void vtmain::Action(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	logfp = fopen("/home/denk/vdrlog.txt", "a");
	/** start the speed controller */
	_wd = new watchdog(speedlimit);
	_wd->Start();
	/** start local operations */
	_local = new vdrrecordings(&_tasks, videosources);
	_local->Start();
	/** start the serverthread */
	_serverthread = new serverthread(transport, _wd, _local);
	_serverthread->Start();
	// open and bind igor socket
	igor_init();
	_s = igor_socket();
	if (_s < 0){
		// socket call failed
		esyslog("%s ends because of socket %i. PANIC", __PRETTY_FUNCTION__, _s);
		return;
	}
	igor_apptype at;
	at.apptype = VT_APPTYPE;
	at.len = strlen (VT_APPTYPE);
	at.mustbehashed = 1;
	int bindres = igor_bind(_s, &at);
	if (bindres < 0){
		// bind call failed
		esyslog("%s ends because of bindres %i. PANIC", __PRETTY_FUNCTION__, bindres);
		return;
	}
	// Start the listening thread
	receiver = new igorinterface(&_q, &_w, &_s);
	receiver->Start();

	// Kick start periodic actions	
	_timerlist.add(ctimerlist::PUBLISH, publish_interval);
	_timerlist.add(ctimerlist::CTASKS_HOUSEKEEPING, housekeeping_interval);
	_timerlist.add(ctimerlist::CLEANUP_VIDEOSOURCES, videosourcecleanup_interval);
	// Start the main loop
	while (true){
		int mstowait = 1000 * _timerlist.getnextwhen(); // getnext returns seconds
		/**
		 * \todo Remove the following uglyness:
		 * When there are no timers, getnextwhen() returns -1. If the next timer should occur now, it returns 0.
		 * However cMutex::Wait(0) does not return without external events.
		 * Therefore we have to remap the values.
		 */
		if (mstowait == 0)
			mstowait = 1;
		if (mstowait < 0)
			mstowait = 0;
		//dsyslog("going to wait %i ms", mstowait);
		bool waitres = _w.Wait(mstowait);
		switch (waitres){
			case true: // msg ready
				//dsyslog("%s: handling message", __PRETTY_FUNCTION__);
				handle_msg();
				break;
			case false: // timer
				handle_timer();
				break;
		}
	}
	esyslog("%s ends. PANIC", __PRETTY_FUNCTION__);
}

/**
 * Select something to publish and send it out.
 * \return true if publishing should continue.
 */

bool
vtmain::publish_single(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	vtmsg m;
	if (!videosources->next2publish(&(m.dest), &(m.sources))){
		/**
		 * \todo What to do when currently there is nothing to publish?
		 * Try again?
		 * Return false so we are never scheduled again?
		 */
		return true;
	}
	// prepare other parameters of the message
	m.type = m.DATA;
	m.upcall = true;
	string s = m.dest.tostring();
	m.hdst.len = s.size();
	m.hdst.address = s.c_str();
	m.hdst.mustbehashed = true;
	string rawmsg = m.tostring();
	//printf("publishing %s\n", rawmsg.substr(0,80).c_str());
	igor_sendto(_s, rawmsg.c_str(), rawmsg.size(), m.upcall, &m.hdst);
	// prevent automatic free()ing of hdst
	m.hdst.len = 0;
	m.hdst.address = NULL;
	return true;
	///\todo do we need to free any memory?
}

/**
 * Do _publications_per_round calls to publish_single.
 * \return false as soon as publish_single() returned false.
 */

bool
vtmain::publish(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	for(int i = 1 ; i < _publications_per_round ; i++){
		if (!publish_single())
			return false;
	}
	return true;
}

/**
 * The Action method said a timer fired. Fetch the first timer from the timer
 * list and do the appropriate thing. The function called has to return true if
 * it want to be scheduled again.
 */
void
vtmain::handle_timer(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	ctimerlist::etimer what = _timerlist.getnextwhat();
	// this also removed the timer from the list.
	switch (what){
		case ctimerlist::NONE: // there was no timer
			return;
		case ctimerlist::PUBLISH:
			if (publish()){
				_timerlist.add(ctimerlist::PUBLISH, publish_interval);
			}
			return;
		case ctimerlist::CTASKS_HOUSEKEEPING:
			if(_tasks.housekeeping(_s, *videosources)){
				_timerlist.add(ctimerlist::CTASKS_HOUSEKEEPING, housekeeping_interval);
			}
			return;
		case ctimerlist::CLEANUP_VIDEOSOURCES:
			if(videosources->cleanup(param_videosource_age_max)){
				_timerlist.add(ctimerlist::CLEANUP_VIDEOSOURCES, videosourcecleanup_interval);
			}
			return;
		default:
			// XXX tell the user that the programmer is stupid
			return;
	}
}

/**
 * The condition variable _w said we received a message. Lets check our queue
 * of messages. Be careful about locking the queue. Until there is no message
 * left in the queue, dispatch them to their own handler function.
 */
void
vtmain::handle_msg(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	_q.Lock();
	bool empty = _q.empty();
	_q.Unlock();
	while (!empty){
		_q.Lock();
		vtmsg * m = _q.front();
		_q.pop_front();
		_q.Unlock();
		switch(m->type){
			case vtmsg::REQ:
				handle_req(m);
				break;
			case vtmsg::DATA:
				handle_data(m);
				break;
			default:
				assert(0);
		}
		_q.Lock();
		empty = _q.empty();
		_q.Unlock();
	}
}

/** The received message is a request.
 * Try to answer that request.
 * If that is not possible, try to forward the request upstream.
 * If that is not possible, delete the message (sorry).
 */

void
vtmain::handle_req(vtmsg *m){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	if (videosources->have(&(m->dest))){
		// we can help, so lets do it
		vtmsg answer;
		answer.dest = m->dest;
		answer.type = answer.DATA;
		answer.upcall = false; ///\todo Think of whether the answer should be cached.
		answer.hdst = m->src; // These pointers are temporary and only valid till the end of this function.
		answer.sources = videosources->get(&(m->dest));
		string rawmsg = answer.tostring();
		//printf("sending answer to request: %s\n", rawmsg.c_str());
		igor_sendto(_s, rawmsg.c_str(), rawmsg.size(), answer.upcall, &answer.hdst);
		delete m;
		return;
	}
	// we can not answer, but maybe someone else can
	if (m->upcall){
		igor_sendon(_s);
		delete m;
		return;
	}
	//dsyslog("nobody can answer, sorry");
	delete m;
	return;
}

/** The received message is from type DATA.
 * Add all the information contained in the message to our data store (videosources).
 * \bug Shall we wake up someone else waiting for that information?
 */
void
vtmain::handle_data(vtmsg *m){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	// add the information contained in that msg into our data store
	videosources->add(m);
	///\todo Maybe check if someone is waiting for that information?
	delete m;
}

/** Add a new timer to the list of pending things.
 * \param what What kind of thing should be done when the timer fires.
 * \param when When the timer should fire. In seconds from now.
 */
void
vtmain::ctimerlist::add(etimer what, int when){
	//dsyslog("%s: begin, what %i when %i", __PRETTY_FUNCTION__, what, when);
	struct timer_t t;
	t.what = what;
	t.when = when + time(NULL);
	_list.push_back(t);
	for (list<timer_t>::const_iterator i = _list.begin(); i!=_list.end(); i++){
		//dsyslog("list element: when %i what %i", i->when, i->what);
	}
}

/** Retrieve when the next timer should fire.
 * \return The number of seconds till the next event.
 * If there is no next event, -1 is returned.
 */
int
vtmain::ctimerlist::getnextwhen(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	int tmin = -1;
	bool found = false;
	for (list<timer_t>::const_iterator i = _list.begin(); i!=_list.end(); i++){
	    //dsyslog("considering list element: when %i what %i", i->when, i->what);
		if (!found){
			tmin = i->when;
			found = true;
			//dsyslog("taken");
		}
		if (tmin > i->when){
			tmin = i->when;
			//dsyslog("taken");
		}
	}
	if (!found)
		return -1;
	tmin -= time(NULL);
	if (tmin < 0)
		tmin = 0;
	return tmin;
}

/** Retrieve what kind the next timer is.
 * This also removes the timer from the list.
 * \return The kind of the next timer if there is a next timer, -1 otherwise.
 */
vtmain::ctimerlist::etimer
vtmain::ctimerlist::getnextwhat(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	int tmin = -1;
	list<timer_t>::iterator ptr;
	for (list<timer_t>::iterator i = _list.begin(); i!=_list.end(); i++){
		if (tmin == -1){
			tmin = i->when;
			ptr = i;
		}
		if (tmin > i->when){
			tmin = i->when;
			ptr = i;
		}
	}
	if (tmin == -1){
		return NONE;
	}
	etimer what = ptr->what;
	_list.erase(ptr);
	//dsyslog("%s: begin; what %i", __PRETTY_FUNCTION__, what);
	return what;
}

