// Copyright (c) 2011, Jens Peter Secher <jpsecher@gmail.com>
// Copyright (c) 2012, Darren Salt <linux@youmustbejoking.demon.co.uk>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// Structure for iterating over a Process' output.

class ProcessTwoStreamIterator
implements Iter<String>
{
	public function new( streamA : haxe.io.Input, streamB : haxe.io.Input, ? process : sys.io.Process )
	{
		lock = new neko.vm.Lock ();
		pending = new neko.vm.Lock ();
		pendingClaimed = true;
		running = true;
		this.process = process;
		stream = [streamA, streamB];
		thread = [neko.vm.Thread.create (runStreamA),
				  neko.vm.Thread.create (runStreamB)];
		lines = new List<String> ();
		lock.release ();
	}

	// First stream, usually stdout
	private function runStreamA ()
	{
		runStream (0);
	}

	// Second stream, usually stderr
	private function runStreamB ()
	{
		runStream (1);
	}

	// Read from a stream, a line at a time, and queue the output
	private function runStream (id : Int)
	{
		while (true)
		{
			try
			{
				var line = stream[id].readLine ();
				lock.wait ();
				lines.add (line);
				pendingRelease ();
				lock.release ();
			}
			catch( e : haxe.io.Eof )
			{
				// Assume that EOF on one stream is EOF on both
				lock.wait ();
				running = false;
				pendingRelease ();
				lock.release ();
				return;
			}
			catch( e : haxe.io.Error )
			{
				// Wait 10 ms for more input to arrive.
				Sys.sleep( 0.01 );
			}
		}
	}

	// Release pending: input available and/or process terminated
	private function pendingRelease ()
	{
		if (pendingClaimed)
		{
			pendingClaimed = false;
			pending.release ();
		}
	}

	// Implementation of Iterable<String>;
	public function iterator() : Iterator<String>
	{
		return this;
	}

	// Implementation of Iterator<String>.
	public function next() : String
	{
		lock.wait ();
		var line = lines.pop ();
		lock.release ();
		return line;
	}

	// Implementation of Iterator<String>.
	public function hasNext() : Bool
	{
		var length = 0;

		do
		{
			// Get the length. If it's non-zero, return.
			lock.wait ();
			length = lines.length;

			if (length > 0)
			{
				lock.release ();
				return true;
			}

			// Otherwise, wait for input or process termination...
			lock.release ();
			pending.wait ();
			pendingClaimed = true;
			// ... and return if the process has terminated.
		} while (running);

		return length != 0;
	}
	
	var stream : Array<haxe.io.Input>;
	var thread : Array<neko.vm.Thread>;
	var lines : List<String>;
	var lock : neko.vm.Lock;
	var pending : neko.vm.Lock;
	var pendingClaimed : Bool;
	var process : sys.io.Process;
	var running : Bool;
}
