/*
 * (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: task.cc,v 1.6 2006/01/10 19:19:47 kutzner Exp $
 *
 */


// libc

#include <assert.h>

// vdr

#include <timers.h>
#include <device.h>


// vidtrans
#include "task.h"
#include "clientthread.h"


/**
 * Create a new task. A task is something to do. The user programs something
 * with the VDR-Interface. These information are copyied into our plugin by
 * ctasks::copy_vdr_timers(). This method also calls this constructor. Since
 * only tasks with existing cChannel* can be constructed, we have a minimal
 * protection against illegal leechers that want to fetch TV stuff they can't
 * receive otherwise.
 * \param start Start time of the user wish. In seconds since the unix epoch.
 * May be rounded to a full minute.
 * \param stop Stop time of the user wish. In seconds since the unix epoch. May
 * be rounded to a full minute.
 * \param channel Which channel the user wants to record.
 * \param count The running number of this task if part of a larger download.
 * \param total The total number of tasks in this download.
 * \param dlstat Remember the download status of this task on disk.
 */
task::task(time_t start, time_t stop, const cChannel * channel, int count, int total, downloadstatus * /*dlstat*/, const char * name){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	_start = start;
	_stop = stop;
	_channel = channel;
	_last_request = 0;
	_clientthread = NULL;
	_lastqueried = 0;
	_count = count;
	_total = total;
	_name = name;
	_state = requesting_sources;
	printf("created task %s, %ld-%ld (%d of %d) - state: %d (requesting sources)\n", name, start, stop, count, total, _state);
}

void
task::startthread(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	if (_sources.size()<1){
		//dsyslog("refuse to start client without sources");
		return;
	}
	printf("starting clientthread number %d\n", _count);
	cvideosource * vs;
	vs = _sources.front(); ///\bug randomize me! to distribute upstream load
	_clientthread = new clientthread(_start, _stop, _channel, *vs, _count, _total, _name);
	//dsyslog("%s: created clientthread: %ld %ld %p %d %d %s", __PRETTY_FUNCTION__, _start, _stop, _channel, _count, _total, _name);
	_clientthread->Start();
	//dsyslog("%s: started clientthread", __PRETTY_FUNCTION__);
}

/**
 * Delete the client thread.
 * \bug document when this function can be called. May the thread still run?
 */
void
task::deletethread(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	///\bug Check that the thread is dead!
	delete _clientthread;
	_clientthread = NULL;
}

/**
 * Send out queries for this task.
 * It is no bug that only the first only the first minute is covered since a task consists of only one minute.
 * \param sd The socket descriptor for the igor interface.
 */
void
task::sendquery(int sd){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	_lastqueried = time(NULL);
	vtmsg msg;
	msg.type = vtmsg::REQ;
	msg.dest = aggr_dest(_start, _channel);
	string dest;
	dest += VT_APPTYPE;
	dest += msg.dest.tostring();
	msg.hdst.address = dest.c_str();
	msg.hdst.len = dest.size();
	msg.hdst.mustbehashed = true;
	string smsg = msg.tostring();
	bool upcall = true;
	///\todo check result and report back
	printf("Querying sources...\n");
	igor_sendto(sd, smsg.c_str(), smsg.size(), upcall, &(msg.hdst));
	// prevent free()ing by sa_igor dtor (this is ugly!)
	msg.hdst.address = NULL;
	msg.hdst.len = 0;
}

ctasks::ctasks() : _client_threads_max(5) , singletask_request_interval(20) , requests_per_housekeeping(20) {
}

/**
 * Scans through the list of timers in VDR and copies the ones we are interested in. We are interested in timers the VDR can't record itself anymore, everything else we leave to VDR.
 */
void
ctasks::copy_vdr_timers(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	cTimer * c ;
	c = Timers.First();
	while (c){
		if (c->Channel()->Ca()){ // we don't touch encrypted channels
			c = Timers.Next(c);
			continue;
		}
		if (!(cDevice::GetDevice(c->Channel()))){ // we can't record it at all
			c = Timers.Next(c);
			continue;
		}
		if (c->Expired() && c->HasFlags(tfActive)) { // vdr can not record this itself (but timer is active)
			if (!have(c->StartTime(), c->StopTime(), c->Channel())){
				add(c->StartTime(), c->StopTime(), c->Channel(), 1, c->File());
				///\bug this keeps on adding recordings to the list.
			}
		}
		c = Timers.Next(c);
	}
}

/** 
 * We successful retrieved a recording from the net, tell the VDR about this (ie delete the entry in VDR's list).
 * This method is called by vdrrecordings::done().
 */

void
ctasks::mark_done(time_t start, time_t stop, const cChannel * channel){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	cTimer * c;
	c = Timers.First();
	while (c){
		if (c->StartTime()==start && c->StopTime()==stop && c->Channel()==channel && c->HasFlags(tfActive)){
			c->ClrFlags(tfActive);
			c = Timers.Next(c);
		} else {
			c = Timers.Next(c);
		}
	}
}

/**
 * Create a new thing to do.  Internally this method
 * probably adds a bunch of new tasks, one for each minute
 * we want to receive.
 * \todo how can these different tasks communicate?
 * \param start Time of start in seconds. Rounded down to
 * the full minute.
 * \param start End time in seconds. This is not rounded,
 * but for every full minute between the (rounded) start
 * value and stop a task for this full minute is started.
 * \param stop The end time. How surprising.
 * \param channel The channel to record, who would have
 * guessed that.
 * \param count (default: 1) Which minute of the recording to 
 * get (sets the clientthreadnumber). Needed for rescheduling
 * missing blocks (cutter found gaps between two consecutive 
 * blocks)
 */
void
ctasks::add(time_t start, time_t stop, const cChannel * channel, int count, const char * name){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	assert(channel);
	_mutex.Lock();
	int total = ( ( stop - stop%60 ) - ( start - start%60 ) ) / 60;
	downloadstatus * dlstat = new downloadstatus; ///\bug who deletes this thingy?
	
	for (time_t t = start - start%60 ; t < stop; t+=60){
		if (!have(t, t+60, channel))
			tasklist.push_back(new task(t, t+60, channel, count++, total, dlstat, name));
	}
	_mutex.Unlock();
}

/**
 * Do all regular stuff for tasks.
 * This includes polling client threads and check their status.
 * \param sd socket descriptor of the igor socket. \bug It is not nice to pass this descriptor around.
 * \param videosources A reference to the large list of video sources. We need this to query the list and fill tasks with the required information.
 * \return true if housekeeping should continue
 * \todo The current implementation looks fishy. Implement it from scratch.
 *

	for each task
		if state == client_thread_running
			switch state of thread
				running: noop
				success: delete thread; delete task
				failure: set local state back to no_sources_yet
		if state != no_sources_yet
			next task
		if state == no_sources_yet and sources in local sources list
			assign sources to this tasks
			change state of this task to ready_to_spawn
			next task
		if state == no_sources_yet and sources not in local sources store
			if (a interval check to avoid excessive request())
				send out request message
		if limit request_per_call_to_this_function reached
			return
		send_requests()

 */
bool
ctasks::housekeeping(int sd, cvideosources & videosources){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	// check if there is something new
	copy_vdr_timers();
	int counter = 0;
	int helpcounter = 0;
	for(tasklist_t::iterator i = tasklist.begin(); i!=tasklist.end(); i++){
		task* singletask = *i;
		helpcounter++;
		if (singletask->state() != 0) printf("state of task %d: %d\n", helpcounter, singletask->state());
		if (singletask->state() == task::clientthreadrunning){
			switch(singletask->getclientthread()->state()){
				case clientthread::running:
					printf("found a running clientthread - breaking the loop\n");
					break;
				case clientthread::successful:
					//singletask->mark_done();
					///\todo potentially start cutter?
					singletask->deletethread();
					delete singletask;
					tasklist.erase(i);
					i = tasklist.begin();
					continue;
				case clientthread::failure:
					singletask->deletethread();
					singletask->changestate(task::requesting_sources);
					printf("found a failed clientthread - breaking the loop\n");
					break;
				default:
					assert(0);
			}
		}
		if (singletask->state() != task::requesting_sources){
			continue;
		}
		// Now we have a task which is looking for sources (Because clientthreadrunning already handled and readytospawn is not our business). 
		// Lets have a look in our store.
		aggr_dest target(singletask->getstart(), singletask->getchannel());
		if (videosources.have(&target)){
			singletask->sourcesref() = videosources.get(&target);
			singletask->changestate(task::readytospawn);
			printf("found sources for task %d - setting to %d (readytospawn)\n", helpcounter, singletask->state());
			continue;
		}
		// we don't know anything about this target
		if (time(NULL) - singletask->lastqueried() > singletask_request_interval){ // we didn't query this thingy recently
			singletask->sendquery(sd);
			counter++;
		}
		if (counter > requests_per_housekeeping){ // we did our work for that round, lets have a rest
			break;
		}
	}
	// now we have probably filled some tasks with sources, so let's start one or more clientthreads to fetch the stuff
	potentially_start_client_threads();
	return true;
}

/**
 * Determine how many client threads are running right now.
 * This is done by checking the state of all tasks;
 * \return The number of clientthreads which are running.
 */
int
ctasks::clients_running(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	list<task*>::const_iterator i;
	int count = 0;
	for (i=tasklist.begin(); i!=tasklist.end(); i++){
		clientthread * t = (*i)->getclientthread();
		if (t && t->Active())
			count++;
	}
	return count;
}

/**
 * Checks how many clients threads are currently running.
 * Start more if limit permits.
 */

void
ctasks::potentially_start_client_threads(){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	int running = clients_running();
	if (running > 0 ) printf("there are %d clientthreads running (%d max)\n", running, _client_threads_max);
	if (running >= _client_threads_max) // there are already a lot of threads
		return;
	list<task*>::const_iterator i;
	for (i=tasklist.begin(); i!=tasklist.end(); i++){
		if ((*i)->state() != task::readytospawn) continue;
		clientthread * t = (*i)->getclientthread();
		if (!t){
			(*i)->startthread();
			running++;
			if (running >= _client_threads_max) return;
		}
	}
}

void
ctasks::set_client_threads_max(int num){
	//dsyslog("%s: begin", __PRETTY_FUNCTION__);
	if (num >= 0){
		_client_threads_max = num;
	}
}


/**
 * Check whether this particular task exists. The task is identified by
 * \param start Start time
 * \param stop Stop time and
 * \param channel The channel the task should record.
 */

bool
ctasks::have(time_t start, time_t stop, const cChannel * channel){
	for(tasklist_t::iterator i = tasklist.begin(); i!=tasklist.end(); i++){
		if (((*i)->getstart() == start) && ((*i)->getstop() == stop) && ((*i)->getchannel() == channel))
			return true;
	}
	return false;
}

