package org.etsi.mts.tdl.execution.java.rt.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.etsi.mts.tdl.execution.java.tri.Argument;
import org.etsi.mts.tdl.execution.java.tri.Connection;
import org.etsi.mts.tdl.execution.java.tri.Data;
import org.etsi.mts.tdl.execution.java.tri.StopException;
import org.etsi.mts.tdl.execution.java.tri.SystemAdapter;

public class ReceiverHub {

	private Thread thread;
	private boolean stopped = false;

	private SystemAdapter systemAdapter;
	private Connection connection;

	private List<Expectable> expecting = Collections.synchronizedList(new ArrayList<>());
	private ExceptionalBehaviour anyReceiver;

	public ReceiverHub(SystemAdapter systemAdapter, Connection connection) {
		this.systemAdapter = systemAdapter;
		this.connection = connection;

		this.thread = new Thread(() -> run(), "Receive from SystemAdapter on " + connection.getName());
		this.thread.setDaemon(true);
		this.thread.start();
	}

	public ExceptionalBehaviour getAnyReceiver() {
		return anyReceiver;
	}

	public void setAnyReceiver(ExceptionalBehaviour anyReceiver) {
		this.anyReceiver = anyReceiver;
	}

	private synchronized void run() {
		int currentlyExpectingIndex = 0;
		while (!stopped) {
			Expectable currentlyExpecting = null;
			synchronized (expecting) {
				while (expecting.size() <= currentlyExpectingIndex)
					try {
						expecting.wait();
					} catch (InterruptedException e) {
						return;
					}
				currentlyExpecting = expecting.get(currentlyExpectingIndex);
				currentlyExpectingIndex++;
			}
			try {
				Data data = systemAdapter.receive(currentlyExpecting.expected, connection);
				if (data != null) {
					currentlyExpectingIndex = 0;
					if (currentlyExpecting.anyReceiver) {
						// Switch to the first one for reporting
						synchronized (expecting) {
							for (Expectable e: expecting)
								if (!e.ignoreUnmatched) {
									currentlyExpecting = expecting.get(0);
									break;
								}
						}
					}
					// XXX this is not correct
					expecting.clear();
					synchronized (currentlyExpecting) {
						currentlyExpecting.received = data;
						currentlyExpecting.notifyAll();
					}
				}
			} catch (InterruptedException e) {

			} catch (AssertionError e) {
				if (!currentlyExpecting.ignoreUnmatched)
					currentlyExpecting.error = e;
			}
		}
	}

	public void stop() {
		this.stopped = true;
		this.thread.interrupt();
	}

	public Data receive(Data expected, boolean ignoreUntil) {
		Expectable expectable = new Expectable(expected, ignoreUntil);
		synchronized (expecting) {
			expecting.add(expectable);
			expecting.notifyAll();
		}
		try {
			synchronized (expectable) {
				try {
					expectable.wait();
				} catch (InterruptedException e) {
					return null;
				}
				if (expectable.error != null)
					throw expectable.error;
				return expectable.received;
			}
		} finally {
			expecting.remove(expectable);
		}
	}

	public Data call(Object operation, Argument[] arguments, Data expectedReturn, Data expectedException) {
		// XXX
		return null;
	}

	class Expectable {
		Data expected;
		Data received;
		AssertionError error;
		boolean ignoreUnmatched = false;
		boolean anyReceiver = false;

		public Expectable(Data expected, boolean ignoreUnmatched) {
			this.expected = expected;
			this.ignoreUnmatched = ignoreUnmatched;
			this.anyReceiver = expected == null;
		}
	}
}
