package aQute.libg.remote.source;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import aQute.libg.remote.Area;
import aQute.libg.remote.Event;
import aQute.libg.remote.Sink;
import aQute.libg.remote.Source;
import aQute.libg.remote.Welcome;

/**
 * Controls a different file system trough a Sink. It can translate files with
 * local absolute file paths to remote absolute file paths (also if it is a
 * windows system). It also detects changes in the local file system and will
 * update the remote. Updates are checked for SHAs so we only transfer the files
 * when they really are not there, even allowing a remote SHA cache to do its
 * magic.
 */
public class RemoteSource implements Source {

	private Sink			sink;
	private Appendable		stdout;
	private Appendable		stderr;
	private Thread			thread;
	volatile AtomicBoolean	running	= new AtomicBoolean();
	Welcome					welcome;
	SourceFS				fsync;
	String					areaId;
	File					cwd;

	public void open(Sink sink, File cwd, String areaId) {
		this.sink = sink;
		this.cwd = cwd;
		this.areaId = areaId;
		this.welcome = sink.getWelcome(Sink.version);
		this.fsync = new SourceFS(welcome.separatorChar, cwd, sink, areaId);
	}

	/**
	 * Check for all files in our scope if they have changed or been referred to
	 * recently. Remote files will be deleted or updated when necessary.
	 */

	/**
	 * Called from the remote sink to get the data when it lacks the given sha.
	 */
	@Override
	public byte[] getData(String sha) throws Exception {
		return fsync.getData(sha);
	}

	/**
	 * Close
	 * 
	 * @throws IOException
	 */
	public void close() throws IOException {}

	@Override
	public void event(Event e, Area area) throws Exception {
		switch (e) {

			case created :
				break;
			case deleted :
				break;
			case exited :
				exit();
				break;
			case launching :
				break;
			case restarted :
				break;
			case running :
				break;
			case started :
				break;
			case virginal :
				break;
			default :
				break;

		}
	}

	private void exit() {
		if (running.getAndSet(false)) {
			this.thread.interrupt();
			this.stdout = null;
			this.stderr = null;
		} else
			;// TODO log
	}

	@Override
	public void output(String areaId, CharSequence text, boolean err) throws IOException {
		if (running.get()) {
			if (err)
				this.stderr.append(text);
			else
				this.stdout.append(text);
		}
	}

	public Sink getSink() {
		return sink;
	}

	public void launch(Map<String,String> env, List<String> args, final InputStream stdin, Appendable stdout,
			Appendable stderr) throws Exception {
		if (!running.getAndSet(true)) {
			for (int i = 0; i < args.size(); i++) {
				args.set(i, fsync.transform(args.get(i)));
			}
			for (Map.Entry<String,String> e : env.entrySet()) {
				e.setValue(fsync.transform(e.getValue()));
			}

			fsync.sync();

			this.stdout = stdout;
			this.stderr = stderr;
			this.thread = new Thread("source::" + areaId) {
				public void run() {
					byte[] data = new byte[10000];
					while (!Thread.currentThread().isInterrupted() && running.get())
						try {
							int length = stdin.read(data);
							if (length < 0)
								cancel();
							else {
								if (length > 0) {
									getSink().input(areaId, new String(data));
								}
							}
						} catch (Exception e) {
							e.printStackTrace();
						}
				}
			};
			if (sink.launch(areaId, env, args)) {
				this.thread.start();
			} else {
				exit();
			}
		} else
			throw new IllegalStateException("Already running " + areaId);
	}

	public void cancel() throws Exception {
		getSink().cancel(areaId);
	}

	public void update(File f) throws Exception {
		fsync.markTransform(f);
	}

	public void sync() throws Exception {
		fsync.sync();
	}

	public void add(File file) throws Exception {
		fsync.add(file);
	}

	public void join() throws InterruptedException {
		while (running.get()) {
			Thread.sleep(500);
		}
	}
}
