/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Johannes Franz
 *
 * $Id: threads.cpp,v 1.24 2006/02/05 15:40:24 kutzner Exp $
 *
 */


#include <math.h>
#include "threads.h"
#include <iostream>
#include "globals.h"
#include <string>
#include <timers.h>
#include "igorinterface.h"

using namespace std;

/*
 * @brief a node is an inner node for a message msg 
 * if there exists an entry created from an rc message 
 * 
 */
bool cMainThread::isInnerNode(const cSchedMsg& msg) {
    for ( list<cJob *>::iterator i = jobs.begin(); i != jobs.end(); i++) {
	if ( ( (*i)->getChannel() == msg.getChannel()) && ( (*i)->getStartTime() == msg.getStartTime()) ) {
	    return true; // !(*i)->isLeaf();
    } 
    }
    return false;
}

bool cMainThread::isLeafNode(const cSchedMsg& msg) {
    return listoftimerjobs.exists(msg.getStartTime(), msg.getChannel());
}

void cMainThread::Action() 
{
    time_t t;
    COUT("create connection");

    // create connection
    sd = igor_socket();
    if (sd < 0) {
	cerr << "igor_socket error:" << igor_strerror(sd) << endl;
	return;
    }

    igor_apptype sa = { IGOR_APPTYPE , strlen(IGOR_APPTYPE), true }; 

    COUT("bind socket ");
    if ( igor_bind(sd, &sa) != IGOR_ERR_NOERR) {
	cerr << "igor_bind error: " <<  igor_strerror(sd) << endl;
	return;
    }

    // start cTimerSearch thread
    COUT("start cTimerSearch thread");
    cTimerSearch tsearch(sd);
    tsearch.Start();

    // start reciever thread
    queue = new cSchedMsgQueue();
    COUT("start reciever thread");
    cIgorRecv igorrecv(queue , sd, &w);
    igorrecv.Start();


    // main loop
    while (true){

	// get current time ??
	t = time(NULL);

	list<timer> next_timer_list = listoftimers.getnext();

	int wait; 
	if (next_timer_list.empty()) {
	    // wait forever (no timer)
	    wait = 0;
	} else {
	    // wait is messured in milliseconds
	    wait = 1000 * (next_timer_list.front().when - time(NULL));
	}
	
	COUT("wait set to: " << wait);
	bool result = w.Wait(wait);

	// check whether cond var was signaled or timer expired
	if (!result){ // timer expired
	    COUT("timer expired");
	    handle_timer(next_timer_list);
	}
	else {
	    COUT("new message arrived in queue");
	    handle_message();
	}

    }
}

void cMainThread::handle_rc(const cSchedMsgRC& m){
    // traverse jobs datastructure
    for (list<cJob*>::iterator i = jobs.begin(); i!= jobs.end(); i++){
	cJob *j = *i;
	if (j->getStartTime() == m.getStartTime()) {
	    if (j->getChannel() == m.getChannel()) {
		for (list<cJob::cDDON *>::iterator k = j->neighbours.begin(); k != j->neighbours.end(); k++) {
		    if (igor_sa_cmp(&((*k)->id), &(m.getSrc()))) {
			// replace old values
			(*k)->r = m.getR();
			(*k)->c = m.getC();
			return;
		    }
		}
		cJob::cDDON *neighbour = new cJob::cDDON(m.getR(), m.getC(), m.getSrc());
		j->neighbours.push_back(neighbour);
		return;
	    }
	}
    }
    // nothing exits, create jobentry
    cJob *job = new cJob();
    jobs.push_back(job);
    job->setHash(m.getDst());
    job->setChannel(m.getChannel());
    job->setStartTime(m.getStartTime());
    cJob::cDDON *neighbour = new cJob::cDDON(m.getR(), m.getC(), m.getSrc() );
    job->neighbours.push_back(neighbour);


    // set Timer T2
    srand(time(NULL));
    time_t newtimer = time(NULL) + 6 + (rand() % 10);

    listoftimers.add(timer(newtimer,m.getChannel(), m.getStartTime(), m.isFinal() ));
    COUT("timer T2 set to: " << newtimer - time(NULL) << " sec." );


}


void cMainThread::handle_suppress(const cSchedMsgSuppress& m){

    srand(time(NULL));
    // if inner node
    if (isInnerNode(m)) {
	// traverse jobs datastructure
	COUT("Handle suppress as inner node");
	float reqsum = 0, comsum = 0;
	for (list<cJob*>::iterator i = jobs.begin(); i!= jobs.end(); i++){
	    cJob *j = *i;
	    if (j->getStartTime() == m.getStartTime()) {
		if (j->getChannel() == m.getChannel()) {
		    reqsum = j->aggregateR();
		    comsum = j->aggregateC();
		    float suppress_calc, suppress_mean;
		    if (comsum != 0) {
			suppress_calc = log2(1 + reqsum) / comsum;
			suppress_mean = (suppress_calc + m.getSuppress())/2;
		    } else {
			// infinity
			suppress_mean = -1;
		    }
		    cSchedMsgSuppress msg_s(suppress_mean);
		    msg_s.setChannel(m.getChannel());
		    msg_s.setStartTime(m.getStartTime());
		    for (list<cJob::cDDON *>::const_iterator k = j->neighbours.begin(); k != j->neighbours.end(); k++) {
			msg_s.sendto(sd, (*k)->id);
		    }
		    break;
		}
	    }
	}


	// reset Timer T2
	time_t newtimer = time(NULL) + 6 + (rand() % 10);
	listoftimers.resettimer(timer(newtimer, m.getChannel(), m.getStartTime(), m.isFinal() ) );
    } 
    if (isLeafNode(m)) {
	// if leaf node
	//
	COUT("Handle suppress as leaf node");
	float r = (rand() + 1) / (RAND_MAX + 1);
	if (r < m.getSuppress()) {
	    // stick to current committment
	    // send rc message
	    cSchedMsgRC msg_rc(1.0,1.0);
	    msg_rc.setChannel(m.getChannel());
	    msg_rc.setStartTime(m.getStartTime());
	    msg_rc.sendto(sd);
	} else {
	    // stick to next job
	    //
	    //
	    
	    // revoke committment for all jobs till some mins in the future
	    // and commit to new job
	    int now = time(NULL);
	    // change all entries which won't be changed by timersearch thread no more
	    for(int i = 0;m.getStartTime() + i < now + 600; i +=60) {

		if (listoftimerjobs.exists(m.getStartTime() + i, m.getChannel() )) {
		    // get the job which will be recorded
		    cTimerjob* rjobp = listoftimerjobs.gethighestprio(m.getStartTime() + i);
		    cTimerjob rjob = *rjobp;
		    // restore the original Priority in vdr
		    rjob.getTimerp()->SetPriority(rjob.getOriginalPrio());

		    // stick to next job
		    cTimerjob jobn = listoftimerjobs.getnextprio(m.getStartTime() + i);
		    // no next job
		    if (jobn.getPrio() == 0) { 
			continue;
		    }
		    jobn.getTimerp()->SetPriority(99);
		    jobn.setPrio(99);

		    // revoke committment
		    cSchedMsgRC msg_rc(1/rjob.getOriginalPrio(), 0.0);
		    msg_rc.setChannel(m.getChannel());
		    msg_rc.setStartTime(m.getStartTime());
		    msg_rc.sendto(sd);

		    // send rc message to commit to new job
		    cSchedMsgRC msg_rcc(1.0,1.0);
		    msg_rcc.setChannel(jobn.getChannel());
		    msg_rcc.setStartTime(m.getStartTime() + i);
		    msg_rcc.sendto(sd);
		}
	    }

	}
    }
}

void cMainThread::handle_timer(list<timer> timerlist) {
    timer t;
    // for each timer do
    for ( list<timer>::iterator it = timerlist.begin(); it != timerlist.end(); it++) {
	t = *it;
	// create message
	float request = 0, commit = 0;
	// aggreate values
	for ( list<cJob *>::const_iterator i = jobs.begin();	i != jobs.end(); i++) {
	    if ( (t.starttime == (*i)->getStartTime()) && (t.channel == (*i)->getChannel()) ) {
		request = (*i)->aggregateR();
		commit = (*i)->aggregateC();

		if (!t.final) {
		    // send aggregated igor rc message
		    cSchedMsgRC msg(request, commit);
		    msg.setChannel(t.channel);
		    msg.setStartTime(t.starttime);
		    msg.sendto(sd);
		    COUT("rc message sent for: " << msg.serialize() );
		}

		// send suppress messages
		cSchedMsgSuppress msg_s;
		msg_s.setChannel(t.channel);
		msg_s.setStartTime(t.starttime);
		if (commit != 0) { 
		    msg_s.setSuppress(log(1 + request) / commit);
		} else { 
		    msg_s.setSuppress(-1);
		}		
		for (list<cJob::cDDON *>::const_iterator j = (*i)->neighbours.begin(); j != (*i)->neighbours.end(); j++) {
		    msg_s.sendto(sd, (*j)->id);
		    COUT("suppress message sent");
		}
		break;
	    }	

	}	

    }
    assert(t.when != 0);
    //listoftimers.removeallat(t.when);
    //is allready done
}

void cMainThread::handle_message() {
    queue->lock();
    cSchedMsg *m = queue->get();
    queue->unlock();

    cSchedMsgRC *mrc = dynamic_cast<cSchedMsgRC *>(m);
    cSchedMsgSuppress *ms = dynamic_cast<cSchedMsgSuppress *>(m);

    //  if (typeid(*m) == typeid(cSchedMsgRC)) {
    //      handle_rc(static_cast<cSchedMsgRC>(*m));
    //  } else if (typeid(*m) == typeid(cSchedMsgSuppress)) {
    //      handle_suppress(static_cast<cSchedMsgSuppress>(*m));

    if (mrc) {
	handle_rc(*mrc);
    } else if (ms) {
	handle_suppress(*ms);
    } else {
	assert(0);
    } 
}


// receive messages from the network and put them into the queue
void cIgorRecv::Action(){
    while (true){
	string msg;
	char * tmp_msg = NULL;
	size_t len;
	bool final;
	sa_igor src, dst;
	int recvres = igor_recvfromto(socketd, (void **) &tmp_msg, &len, &final, &src, &dst);
	if (recvres != IGOR_ERR_NOERR) {
	    cout << "igor recvfromto error: " << igor_strerror(socketd) << endl;
	}
	COUT("message recieved: " << tmp_msg);


	msg = tmp_msg;
	igor_free(tmp_msg);
	cSchedMsg *m; 
	if (msg[0] == 'R') {
	    m = new cSchedMsgRC;
	} else {
	    m = new cSchedMsgSuppress;
	}
	if (final) {
	    m->setFinal();
	}
	m->setSrc(src);
	m->setDst(dst);
	// XXX check and act upon recvres
	m->createFromRaw(msg);
	queue->lock();
	queue->put(m);
	queue->unlock();
	w->Signal();
	// alternate:
	//		jjhandlemsg(m);
    }
}


cTimerSearch::cTimerSearch(int sd) 
{
    this->sd = sd;
}



void cTimerSearch::Action(){


    cTimerjob job;
    time_t scheduletime, diff;

    // adjust to minute of local time
    while ( (time(NULL) % 60) != 0) {
	usleep(500000);
    }

    while (true) {

	time_t t = time(NULL);
	assert(t%60 == 0);

	COUT("t = " << t );
	    
	// traverse Timers and put focused interval into ListOfJobs
	//		Timers.Sort();

	// now + 10 min 
	scheduletime = ( (t+600) ) ;
	listoftimerjobs.clean(scheduletime);

	cTimer *curTimer = Timers.First();
	while (curTimer != NULL) {
	    diff = curTimer->StartTime() - t;
	    COUT("diff = " << diff);
	    // timer is within 10 to 11 mins
	    if ( (diff < (DISTANCE + INTERVAL)*60) && (diff >= DISTANCE*60) ) {
		job.setChannel(curTimer->Channel()->Name()); 
		job.setOriginalPrio(curTimer->Priority());
		job.setPrio(curTimer->Priority());
		listoftimerjobs.correctOriginalPrio(job, scheduletime);
		job.setTimerp(curTimer);

		listoftimerjobs.insert(job, scheduletime);	
		COUT("timer added");
	    } 
	    curTimer = (cTimer *) curTimer->Next();
	}


	// send RC messages
	listoftimerjobs.sendRCMessages(scheduletime, sd);

	//	sleep(time(NULL) - t);
	sleep(t + 60 - time(NULL) );
	
    }
}

//void cFired::Action() {
//
//}

