package OAuth_Scheduler2;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

// ================= SCHEDULER CODE ==================

interface Guard {
	boolean test(Object o);
}

abstract class Process implements Runnable {

	public Scheduler scheduler;
	protected String id;
	protected Request my_req = null;
	Request some_req = null;
	protected String chosen;

	public synchronized void pause() {
		while (some_req == null) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
			// some_req == my_req when we come out of wait()
		}
		some_req = null;
	}

	public synchronized void wakeup(Request d) {
		some_req = d;
		notify();
	}

	public int num_waiting(String cmd, String channel) {
		return scheduler.count(cmd, channel);
	}

	public boolean is_waiting(String cmd, String channel) {
		return scheduler.count(cmd, channel) > 0;
	}

	public void await(String cmd, String channel1, String channel2) {
		boolean b;
		if (cmd.equals("send"))
			b = true;
		else
			b = false;
		my_req = new Request(this, id, b, "await", channel1, channel2);
		scheduler.enqueue(my_req);
		pause();
	}

	public void await(Guard g) {
		my_req = new Request(this, id, "await_guard", g);
		scheduler.enqueue(my_req);
		pause();
	}

	public void send(String channel) {
		// true = send, false = receive
		my_req = new Request(this, id, true, "unary", channel);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void send_data(String channel, Object data) {
		// true = send, false = receive
		my_req = new Request(this, id, true, "unary", channel, data);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void send(String channel1, String channel2) {
		// true = send, false = receive
		my_req = new Request(this, id, true, "binary", channel1, channel2);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void send_data(String channel1, String channel2, Object data) {
		// true = send, false = receive
		my_req = new Request(this, id, true, "binary", channel1, channel2, data);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive(String channel) {
		// true = send, false = receive
		my_req = new Request(this, id, false, "unary", channel);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive_guard(Guard g, String channel) {
		// true = send, false = receive
		my_req = new Request(this, id, false, "unary", channel, g);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive_guard(Guard g, String channel1, String channel2) {
		// true = send, false = receive
		my_req = new Request(this, id, false, "binary", channel1, channel2, g);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive_guard(Guard g1, String channel1, Guard g2, String channel2) {
		// true = send, false = receive
		my_req = new Request(this, id, false, "binary", channel1, channel2, g1, g2);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive(String channel1, String channel2) {
		// true = send, false = receive
		my_req = new Request(this, id, false, "binary", channel1, channel2);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void send_or_receive(String channel1, String channel2) {
		// true = send, false = receive
		my_req = new Request(this, id, true, "mixed", channel1, channel2);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive_or_send(String channel1, String channel2) {
		// true = send, false = receive
		// channel1 is send and channel2 is receive
		my_req = new Request(this, id, true, "mixed", channel2, channel1);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void send_or_receive_data(String channel1, Object data1, String channel2) {
		// true = send, false = receive
		// channel1 is send and channel2 is receive
		my_req = new Request(this, id, true, "mixed", channel1, channel2, data1);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public void receive_or_send_data(String channel1, String channel2, Object data1) {
		// true = send, false = receive
		// channel1 is send and channel2 is receive
		my_req = new Request(this, id, true, "mixed", channel2, channel1, data1);
		scheduler.enqueue(my_req);
		pause();
		// my_req.process = null;
	}

	public Object get_data() {
		return my_req.data1;
	}

	public void chosen(String s) {
		chosen = s;
	}
}

//---------------------------------------------

class Request {

	public Request(Process p, String id, boolean cmd, String type, String channel1) {
		this.process = p;
		this.id = id;
		this.cmd = cmd;
		this.type = type;
		this.channel1 = channel1; // one channel, no data
		this.channel2 = "";
	}

	public Request(Process p, String id, boolean cmd, String type, String channel1, Guard g) {
		this.process = p;
		this.id = id;
		this.cmd = cmd;
		this.type = type;
		this.channel1 = channel1; // one channel, no data
		this.channel2 = "";
		this.guard1 = g;
	}

	public Request(Process p, String id, boolean cmd, String type, String channel1, Object data1) {
		this.process = p;
		this.id = id;
		this.cmd = cmd;
		this.type = type;
		this.channel1 = channel1; // one channel, with data
		this.channel2 = "";
		this.data1 = data1;
	}

	// type can be await
	public Request(Process p, String id, boolean cmd, String type, String channel1, String channel2) {
		this.process = p;
		this.id = id;
		this.cmd = cmd; // for mixed, it is the cmd of the first channel
		this.type = type; // could be two receives, or two sends, or mixed
		this.channel1 = channel1; // but no data
		this.channel2 = channel2;
	}

	public Request(Process p, String id, boolean cmd, String type, String channel1, String channel2, Object data1) {
		this.process = p;
		this.id = id;
		this.cmd = cmd;
		this.type = type; // "mixed"
		this.channel1 = channel1;
		this.channel2 = channel2;
		this.data1 = data1; // only one data field for mixed
	}

	public Request(Process p, String id, String type, Guard g) {
		this.process = p;
		this.id = id;
		this.type = type;
		this.guard1 = g;
		this.channel1 = "";
		this.channel2 = "";
	}

	public Request(Process p, String id, boolean cmd, String type, String channel1, String channel2, Guard g) {
		this.process = p;
		this.id = id;
		this.cmd = cmd; // cmd = false when there is a guard on channel1
		this.type = type;
		this.channel1 = channel1;
		this.channel2 = channel2;
		this.guard1 = g;
	}

	public Request(Process p, String id, boolean cmd, String type, String channel1, String channel2, Guard g1,
			Guard g2) {
		this.process = p;
		this.id = id;
		this.cmd = cmd; // cmd = false when there is a guard on channel1
		this.type = type;
		this.channel1 = channel1;
		this.channel2 = channel2;
		this.guard1 = g1;
		this.guard2 = g2;
	}

	String id; // e.g., Reader, Writer, or RWController
	boolean cmd; // true = send, false = receive
	Process process; // e.g., Reader, Writer, or RWController
	String type; // unary or binary or mixed
	String channel1; // channel name
	String channel2; // channel name or "" (for unary)
	Object data1;
	Guard guard1;
	Guard guard2;

	// Object data2 = null;
	// ArrayList<Object> list = null; // for data-based request processing; not yet
	// complete
}

//-------------------------------------------------------------

class Waiting {
	boolean cmd;
	String chan;
	int count;

	public Waiting(boolean cm, String ch, int c) {
		cmd = cm;
		chan = ch;
		count = c;
	}
}

class Scheduler extends Thread {

	String scheduled;
	int count = 0;
	String waitlist = "";
	ArrayList<Waiting> wl = new ArrayList<Waiting>();
	Random ran = new Random();
	boolean debug;
	boolean matched = false;
	LinkedList<Request> l = new LinkedList<Request>();
	//Inserted by jevitha
//	ArrayList<Thread> processThreads = new ArrayList<Thread>();
//	Thread pt;
//	
	public Scheduler(ArrayList<Process> alp, boolean debug) {
		this.debug = debug;
		for (Process p : alp) {
			p.scheduler = this;
			(new Thread(p)).start();
			//Inserted by jevitha
//			pt = new Thread(p);
//			processThreads.add(pt);
//			pt.start();
		}
	}

	public void run() {
		while (true)
			schedule();
	}

	synchronized void enqueue(Request r) {
		l.addLast(r);
		boolean found = false;
		for (Waiting w : wl) {
			switch (r.type) {
			case ("unary"):
				if (w.cmd == r.cmd && w.chan.equals(r.channel1)) {
					w.count = count + 1;
					found = true;
					break;
				}
				break;
			case ("binary"):
				if (w.cmd == r.cmd && (w.chan.equals(r.channel1) || w.chan.equals(r.channel2))) {
					w.count = count + 1;
					found = true;
					break;
				}
				break;
			case ("mixed"):
				if ((w.cmd == r.cmd && r.cmd && w.chan.equals(r.channel1))
						|| (w.cmd == r.cmd && !r.cmd && w.chan.equals(r.channel2))
						|| (w.cmd != r.cmd && r.cmd && w.chan.equals(r.channel2))
						|| (w.cmd != r.cmd && !r.cmd && w.chan.equals(r.channel1))) {
					w.count = count + 1;
					found = true;
					break;
				}
			}
		}
		if (!found)
			wl.add(new Waiting(r.cmd, r.channel1, 1));
		notifyAll();
	}

	void dequeue(Request r) {
		l.remove(r);
		for (Waiting w : wl)
			if (w.cmd == r.cmd && w.chan.equals(r.channel1)) {
				w.count--;
			}
	}

	public synchronized int count(String cmd, String chan) {
		int sum = 0;
		for (Request r : l) {
			String c;
			if (r.cmd)
				c = "send";
			else
				c = "receive";
			if (r.type.equals("unary")) {
				if (c.equals(cmd) && r.channel1.equals(chan)) {
					sum++;
				}
			}
			if (r.type.equals("binary")) {
				if (c.equals(cmd) && (r.channel1.equals(chan) || r.channel2.equals(chan))) {
					sum++;
				}
			}
			// consider mixed also
		}
		return sum;
	}

	boolean check_await() {
		boolean found = false;
		Request r2 = null;
		for (Request r : l) {
			if (r.type.equals("await")) {
				String c;
				if (r.cmd)
					c = "send";
				else
					c = "receive";
				int c1 = count(c, r.channel1);
				int c2 = count(c, r.channel2);
				if (c1 > 0 || c2 > 0) {
					r.process.wakeup(r);
					r2 = r;
					found = true;
					if (c1 > 0)
						r.process.chosen = r.channel1;
					else
						r.process.chosen = r.channel2;
					break;
				}
			}
		}
		if (found)
			dequeue(r2);
		return found;
	}

	boolean check_await_guard() {
		boolean found = false;
		Request r2 = null;
		for (Request r : l) {
			if (r.type.equals("await_guard")) {
				if (r.guard1.test(null)) {
					r.process.wakeup(r);
					r2 = r;
					found = true;
				}
			}
		}
		if (found)
			dequeue(r2);
		return found;
	}

	synchronized void schedule() {

		Request rem_r1 = null; // requests to be removed
		Request rem_r2 = null;

		if (debug)
			print_wait_list();

		matched = false;

		check_await();

		check_await_guard();

		for (Request r1 : l) {

			if (r1.type.equals("await") || r1.type.equals("await_guard"))
				continue;

			for (Request r2 : l) {

				String r1_cmd, r2_cmd;

				if (r2.type.equals("await") || r2.type.equals("await_guard"))
					continue;

				if (r1 == r2)
					continue;

				if (r1.type.equals("unary") && r2.type.equals("unary")) {

					if (r1.cmd == r2.cmd)
						continue;

					if (r1.cmd) {
						r1_cmd = "send";
						r2_cmd = "receive";
					} else {
						r2_cmd = "send";
						r1_cmd = "receive";
					}

					if (r1.channel1.equals(r2.channel1)) {
						if (!r1.cmd && r1.guard1 != null || !r2.cmd && r2.guard1 != null) { // guarded receive
							if (!r1.cmd && r1.guard1.test(r2.data1) || !r2.cmd && r2.guard1.test(r1.data1)) {
								rem_r1 = r1; // must remove r1 and r2 outside for
								rem_r2 = r2;
								matched = true;
								unary_unary(r1, r2, r1_cmd, r2_cmd);
							}
						} else {
							rem_r1 = r1; // must remove r1 and r2 outside for
							rem_r2 = r2;
							matched = true;
							unary_unary(r1, r2, r1_cmd, r2_cmd);
						}
					}
				}

				else if (r1.type.equals("unary") && r2.type.equals("mixed")) {
					// Note: first channel of "mixed" does a send
					if (r1.cmd)
						r1_cmd = "send";
					else
						r1_cmd = "receive";

					if (r1.cmd) // send
						if (!r1.channel1.equals(r2.channel2))
							continue;
						else { // send-receive pair found
							rem_r1 = r1; // must remove r1 and r2
							rem_r2 = r2;
							matched = true;
							unary_mixed(r1, r2, "send", "receive", r1.channel1);
						}
					else // r1 is a receive
					if (r1.channel1.equals(r2.channel1)) {
						rem_r1 = r1; // must remove r1 and r2
						rem_r2 = r2;
						matched = true;
						unary_mixed(r1, r2, "receive", "send", r1.channel1);
					}
				}

				else if (r1.type.equals("unary") && r2.type.equals("binary")) {

					if (r1.cmd == r2.cmd)
						continue;

					if (r1.cmd) {
						r1_cmd = "send";
						r2_cmd = "receive";
					} else {
						r2_cmd = "send";
						r1_cmd = "receive";
					}

					if (r1.channel1.equals(r2.channel1) || r1.channel1.equals(r2.channel2)) {
						if (!r2.cmd)
							if (r1.channel1.equals(r2.channel1)) // guarded binary receive
								if (r2.guard1 != null)
									if (r2.guard1.test(r1.data1)) {
										rem_r1 = r1;
										rem_r2 = r2;
										matched = true;
										unary_binary(r2, r1, r2_cmd, r1_cmd, r1.channel1);
									} else
										continue;
								else {
									rem_r1 = r1;
									rem_r2 = r2;
									matched = true;
									unary_binary(r2, r1, r2_cmd, r1_cmd, r1.channel1);
								}
							else { // r1.channel1.equals(r2.channel2)
								if (r2.guard2 != null)
									if (r2.guard2.test(r1.data1)) {
										rem_r1 = r1;
										rem_r2 = r2;
										matched = true;
										unary_binary(r2, r1, r2_cmd, r1_cmd, r1.channel1);
									} else
										continue;
								else {
									rem_r1 = r1;
									rem_r2 = r2;
									matched = true;
									unary_binary(r2, r1, r2_cmd, r1_cmd, r1.channel1);
								}
							}
						else {
							rem_r1 = r1;
							rem_r2 = r2;
							matched = true;
							unary_binary(r2, r1, r2_cmd, r1_cmd, r1.channel1);
						}
					}
				}

				else if (r1.type.equals("binary") && r2.type.equals("binary")) {

					if (r1.cmd == true) {
						r1_cmd = "send";
						r2_cmd = "receive";
					} else {
						r2_cmd = "send";
						r1_cmd = "receive";
					}

					if (r1.channel1.equals(r2.channel1) && r1.channel2.equals(r2.channel2)
							|| r1.channel1.equals(r2.channel2) && r2.channel1.equals(r1.channel2)) {
						int r = ran.nextInt(9999) % 2;
						rem_r1 = r1; // must remove r1 and r2 outside for
						rem_r2 = r2;
						matched = true;
						if (r == 0)
							binary_binary(r1, r2, r1_cmd, r2_cmd, r1.channel1);
						else
							binary_binary(r1, r2, r1_cmd, r2_cmd, r1.channel2);
					}

					// no need for random selection since there is only one
					// match

					else if (r1.channel1.equals(r2.channel1) || r1.channel1.equals(r2.channel2)) {
						rem_r1 = r1; // must remove r1 and r2 outside for
						rem_r2 = r2;
						matched = true;
						binary_binary(r1, r2, r1_cmd, r2_cmd, r1.channel1);
					}

					else if (r2.channel1.equals(r1.channel2) || r2.channel2.equals(r1.channel2)) {
						rem_r1 = r1; // must remove r1 and r2 outside for
						rem_r2 = r2;
						matched = true;
						binary_binary(r1, r2, r1_cmd, r2_cmd, r1.channel2);
					}
				}
				if (rem_r1 != null)
					break;
			}
			if (rem_r1 != null)
				break;
		}
		if (rem_r1 != null)
			dequeue(rem_r1);
		if (rem_r2 != null)
			dequeue(rem_r2);

		if (!matched || (l.size() < 2))
			try {
				wait();
			} catch (InterruptedException e) {
			}
	}

	void unary_unary(Request r1, Request r2, String r1_cmd, String r2_cmd) {
		count = count + 1;

		if (debug)
			System.out.println("	Schedule " + count + " ==> " + r1_cmd + "-" + r1.channel1 + "  and  " + r2_cmd + "-"
					+ r2.channel1 + "\n");

		if (r1_cmd.equals("send") && r1.data1 != null)
			r2.data1 = r1.data1;
		else if (r2.data1 != null)
			r1.data1 = r2.data1;

		r1.process.wakeup(r1);
		r2.process.wakeup(r2);
		scheduled = r1.channel1;
		return;
	}

	void unary_binary(Request r1, Request r2, String r1_cmd, String r2_cmd, String chan) {
		// r1 is binary and r2 is unary
		count = count + 1;

		if (debug)
			System.out.println("	Schedule " + count + " ==> " + r2_cmd + "-" + r2.channel1 + "  and  " + (r1_cmd)
					+ "-(" + r1.channel1 + "|" + r1.channel2 + ")\n");

		if (r1_cmd.equals("send"))
			r2.data1 = r1.data1;
		else // r1_cmd.equals("receive"
			r1.data1 = r2.data1;

		r1.process.chosen(chan);
		r1.process.wakeup(r1);
		r2.process.wakeup(r2);
		scheduled = chan;
	}

	void unary_mixed(Request r1, Request r2, String r1_cmd, String r2_cmd, String chan) {
		// r1 is unary and r2 is mixed
		count = count + 1;

		if (debug)
			System.out.println("	Schedule " + count + " ==> " + r1_cmd + "-" + r1.channel1 + "  and  " + "("
					+ "send:" + r2.channel1 + "|" + "receive:" + r2.channel2 + ")");

		if (r1_cmd.equals("send"))
			r2.data1 = r1.data1;
		if (r1_cmd.equals("receive"))
			r1.data1 = r2.data1;

		r2.process.chosen(chan);
		r1.process.wakeup(r1);
		r2.process.wakeup(r2);
		scheduled = chan;
	}

	void binary_binary(Request r1, Request r2, String r1_cmd, String r2_cmd, String chan) {
		count = count + 1;

		if (debug)
			System.out.println("	Schedule " + count + " ==> " + (r1_cmd) + "-(" + r1.channel1 + "|" + r1.channel2
					+ ")  and  " + (r2_cmd) + "-(" + r2.channel1 + "|" + r2.channel2 + ")\n");
		r1.process.chosen(chan);
		r2.process.chosen(chan);
		r1.process.wakeup(r1);
		r2.process.wakeup(r2);
		scheduled = chan;
	}

	void print_wait_list() {
		waitlist = "";
		for (Request r : l) {
			String r_cmd;

			if (r.cmd)
				r_cmd = "send";
			else
				r_cmd = "receive";
			if (r.type.equals("await")) {
				waitlist = waitlist + ("await_" + r_cmd) + "-" + r.channel1 + "-" + r.channel2 + ";   ";
				continue;
			}
			if (r.type == "unary")
				waitlist = waitlist + (r_cmd) + "-" + r.channel1 + ";   ";
			else if (r.type == "mixed")
				waitlist = waitlist + "(send:" + r.channel1 + "|" + "receive:" + r.channel2 + ");   ";
			else
				waitlist = waitlist + (r_cmd) + "-(" + r.channel1 + "|" + r.channel2 + ");   ";
			r = null;
		}

		System.out.println(waitlist + "\n");
	}

}
