package edu.vt.realtime.testing;

import edu.vt.realtime.*;
import java.util.ArrayList;
import org.eclipse.swt.widgets.Display;

public class Task extends Thread {
	private int id;
	private TesterOptions opt;
	private long cpus = 0;
	private ArrayList<ChronosMutex> locks;
	private ArrayList<Integer> my_locks;
	private long exec_time = 0;
	private long locked_usage = 0;
	private long unlocked_usage = 0;
	private long period = 0;
	private Timespec period_ts;
	private int utility = 0;
	private int abort_utility = 0;
	private int utilization = 0;
	private int releases = 0;
	private int max_releases = 0;

	//performance stats for task
	private int aborted = 0;
	private int deadlines_met = 0;
	private int utility_accrued = 0;

	//holds the global start time of all tasks
	private Timespec start_time;
	//the deadline
	private Timespec deadline;

	private int thread_id; //the tid of the thread;
	private ChronosAborts aborts; //access for abort information
	private long lock_time; //stores the max time it should take to do lock stuff

	public Task(TesterOptions opt) {
		id = -1;
		this.opt = opt;
		period_ts = new Timespec();
		cpus = 0;
		my_locks = new ArrayList<Integer>();
		exec_time = 0;
		locked_usage = 0;
		unlocked_usage = 0;
		period = 0;
		utility = 0;
		abort_utility = 0;
		utilization = 0;
		releases = 0;
		max_releases = 0;

		//initialize counters
		aborted = 0;
		deadlines_met = 0;
		utility_accrued = 0;

		deadline = new Timespec();
	}

	public void initTask(int id, ArrayList<ChronosMutex> locks, ChronosAborts aborts, long lock_time) {
		this.id = id;
		this.locks = locks;
		this.aborts = aborts;
		this.lock_time = lock_time;
		//initialize the usage numbers
		double usage = (double)this.opt.cpu_usage/100;
		double cs = (double)this.opt.cs_len/100;
		this.unlocked_usage = (long)((double)this.exec_time * (usage - (usage * cs)));
		this.locked_usage = (long)((double)this.exec_time * usage * cs);

		//initialize the runtime to the runtime in microseconds
		long runtime = this.opt.run_time*1000000;
		for(long i = 0; i < runtime; i++) {
			if(runtime < this.period * i) {
				this.max_releases = (int)i-1;
				break;
			}
		}
	}

	private void work(long time) {
		for (long i = 0; i < time; i++) {
			if(this.aborts.wasAborted(thread_id))
				return;
			ChronosUtil.burnCpu(1, this.opt.slope);
		}
	}

	public void setStartTime(Timespec t) { start_time = t; }

	public void setPeriods(long p){
		period = p;
		long sec = p/1000000;
		long nsec = p*1000 - sec*1000000*1000;
		period_ts.setTime(sec, nsec);
	}

	public void setExecTime(long e) { exec_time = e; }
	public void addCpu(int cpu) { cpus |= (int)Math.pow(2,cpu); }
	public void addLock(int lock) { my_locks.add(new Integer(lock)); }
	public void setUtility(int u) { utility = u; }
	public void setAbortUtility(int au) { abort_utility = au; }

	public long getPeriod() { return period; }
	public int getUtility() { return utility; }
	public int getAbortUtility() { return abort_utility; }
	public int getRunReleases() { return max_releases; }
	public long getRunUtility() { return max_releases * utility; }
	public long period() { return period; }
	public long unlocked_usage() { return unlocked_usage; }
	public long locked_usage() { return locked_usage; }
	public int jobs_aborted() { return aborted; }
	public int deadlines_met() { return deadlines_met; }
	public int utility_accrued() { return utility_accrued; }

	public double utilization() {
		return ((double)exec_time)/period;
	}

	private boolean addRelease() { return ++releases <= max_releases; }

	private void threadOutput(String str, boolean accrue) {
		if(this.opt.verbose && accrue)
			this.opt.println(thread_id + str);
	}

	private void updateDeadline() {
		long offset = period * releases * 1000;

		deadline.setTime(start_time);
		deadline.add(new Timespec(0, offset));
	}

	//called by Thread superclass when ChronosTester calls Task.start()
	public void run() {
		boolean accrue = true;
		Timespec end_time = new Timespec();

		thread_id = ChronosUtil.gettid();
		ChronosUtil.setAffinity((int)cpus);

		while(accrue) {
			accrue = addRelease();
			updateDeadline();

			threadOutput("/" + id + " - Deadline: " + deadline, accrue);

			//setup the HUA aborts, if we need to
			if(this.opt.use_hua && abort_utility > 0)
				ChronosScheduler.addAbortHandler(abort_utility, this.lock_time * this.locks.size());
			ChronosScheduler.beginSegment(Priorities.TASK_RUN.getNativeId(), utility, deadline, period_ts, unlocked_usage + locked_usage);

			//lock the locks, if (and how) we are supposed to
			if (this.opt.locking && this.opt.nested_locking) {
				int last = 0;
				for(int i = 0; i < this.my_locks.size(); i++) {
					int lock_num = this.my_locks.get(i).intValue();
					threadOutput(" locking " + lock_num, accrue);
					if(this.locks.get(lock_num).lock())
						last = lock_num;
					else
						break;
					threadOutput(" locked " + lock_num, accrue);
				}

				threadOutput(" burning - locked", accrue);
				work(locked_usage);

				for(int i = last; i >=0; i--) {
					int lock_num = this.my_locks.get(i).intValue();
					threadOutput(" unlocking " + lock_num, accrue);
					this.locks.get(lock_num).unlock();
				}
			} else if (this.opt.locking) {
				for(int i=0; i < this.my_locks.size(); i++) {
					int lock_num = this.my_locks.get(i).intValue();
					threadOutput(" locking "+lock_num, accrue);
					if(!this.locks.get(lock_num).lock())
						break;
					threadOutput(" locked "+lock_num, accrue);
					threadOutput(" burning - locked", accrue);
					work(this.locked_usage/this.my_locks.size());
					threadOutput(" unlocking "+lock_num, accrue);
					this.locks.get(lock_num).unlock();
				}
			}

			threadOutput(" burning - unlocked", accrue);

			work(unlocked_usage);

			end_time.getTime();

			ChronosScheduler.endSegment(Priorities.TASK_CLEANUP.getNativeId());

			if(this.aborts.wasAborted(this.thread_id))
				aborted++;

			if(accrue && end_time.lessThan(this.deadline)) {
				deadlines_met++;
				utility_accrued += utility;

				threadOutput("/" + id + " end time: " + end_time + " - Deadline " + releases + ": " + this.deadline, accrue);
			} else
				threadOutput(" task failed (tid: "+thread_id+") end time: "+end_time+" - Deadline "+releases + ": "+deadline, accrue);

			Timespec sleeptime_ts = deadline.copy();
			sleeptime_ts.subtract(end_time);
			long sleeptime = sleeptime_ts.getSeconds()*1000000 + sleeptime_ts.getNanoseconds()/1000;
			if(sleeptime > 0)
				ChronosUtil.usleep(sleeptime);
		}
	}
}
