package bndtools.launch.bnd;

import java.io.IOException;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IStreamsProxy2;

import aQute.bnd.build.ProjectLauncher;
import aQute.bnd.build.RunSession;

public class BndStreamsProxy implements IStreamsProxy2 {
	final ProjectLauncher	projectLauncher;
	final RunSession		session;
	Job						job;
	StreamMonitor			stdout;
	StreamMonitor			stderr;

	public BndStreamsProxy(ProjectLauncher pl, RunSession session) {
		this.projectLauncher = pl;
		this.session = session;

		try {
			session.stdout(stdout = new StreamMonitor());
			session.stderr(stderr = new StreamMonitor());
		} catch (Exception e) {
			e.printStackTrace();
			// ignore
		}
	}

	class StreamMonitor implements IStreamMonitor, Appendable {
		final StringBuffer									sb			= new StringBuffer();
		final ConcurrentHashMap<IStreamListener, Integer>	listeners	= new ConcurrentHashMap<>();

		@Override
		public void addListener(IStreamListener listener) {
			listeners.put(listener, 0);
		}

		@Override
		public void removeListener(IStreamListener listener) {
			listeners.remove(listener);
		}

		@Override
		public String getContents() {
			return sb.toString();
		}

		@Override
		public Appendable append(char ch) throws IOException {
			sb.append(ch);
			trigger();
			return this;
		}

		@Override
		public Appendable append(CharSequence text) throws IOException {
			sb.append(text);
			trigger();
			return this;
		}

		@Override
		public Appendable append(CharSequence text, int start, int length) throws IOException {
			sb.append(text, start, length);
			trigger();
			return this;
		}

		public void flush() {
			for (Entry<IStreamListener, Integer> listener : listeners.entrySet()) {
				int start = listener.getValue();
				int end = sb.length();
				listener.setValue(end);
				listener.getKey()
					.streamAppended(sb.substring(start, end), this);
			}
		}
	}

	synchronized void trigger() {
		if (job == null) {
			job = new Job("Stream trigger") {

				@Override
				protected IStatus run(IProgressMonitor arg0) {
					flush();
					return Status.OK_STATUS;
				}
			};
		}
		job.schedule(200);
	}

	void flush() {
		job = null;
		if (stdout != null) {
			stdout.flush();
			stderr.flush();
		}
	}

	@Override
	public IStreamMonitor getErrorStreamMonitor() {
		return stderr;
	}

	@Override
	public IStreamMonitor getOutputStreamMonitor() {
		return stdout;
	}

	@Override
	public void write(String input) throws IOException {
		try {
			session.stdin(input);
		} catch (Exception e) {
			// ignore
		}
	}

	@Override
	public void closeInputStream() throws IOException {
		// ignore
	}

	public void close() {
		stdout = null;
		stderr = null;
	}

}
