/**
 * This file is part of Erjang - A JVM-based Erlang VM
 *
 * Copyright (c) 2009 by Trifork
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

package erjang;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

import kilim.Mailbox;
import kilim.Pausable;

/**
 * An ETask is what is common for processes and open ports
 */
public abstract class ETask<H extends EHandle> extends kilim.Task {

	protected static final EAtom am_normal = EAtom.intern("normal");
	protected static final EAtom am_java_exception = EAtom
			.intern("java_exception");
	private static final EAtom am_DOWN = EAtom.intern("DOWN");
	private static final EAtom am_process = EAtom.intern("process");

	/**
	 * @return
	 */
	public abstract H self_handle();

	protected Set<EHandle> links = new ConcurrentSkipListSet<EHandle>();
	protected Map<ERef,ETuple2> monitors = new ConcurrentHashMap<ERef, ETuple2>();

	public void unlink(EHandle handle) {
		links.remove(handle);
	}
	
	/**
	 * @param task
	 * @throws Pausable 
	 */
	public void link_to(ETask<?> task) throws Pausable {
		link_oneway(task.self_handle());
		task.self_handle().link_oneway((EHandle) self_handle());
	}

	public void link_to(EHandle handle) throws Pausable {
		link_oneway(handle);
		handle.link_oneway((EHandle) self_handle());
	}

	public void link_oneway(EHandle h) throws Pausable {
		// TODO: check if h is valid.
		
		if (h.exists()) {
			links.add(h);
		} else {
			link_failure(h);
		}
	}

	/**
	 * @param h
	 * @throws Pausable 
	 * @throws Pausable 
	 */
	protected void link_failure(EHandle h) throws Pausable {
		throw new ErlangError(ERT.am_noproc);
	}

	protected void do_proc_termination(EObject exit_reason) throws Pausable {
		this.exit_reason = null;
		H me = self_handle();
		for (EHandle handle : links) {
			handle.exit_signal(me, exit_reason);
		}
		for (Map.Entry<ERef, ETuple2> ent : monitors.entrySet()) {
			ETuple2 pid_object = ent.getValue();
			ERef ref = ent.getKey();
			
			EPID pid = (EPID) pid_object.elm(1);
			EObject object = pid_object.elm(2);
			
			if (object == null) {
				object = me;
			}
			
			pid.send(ETuple.make(am_DOWN, ref, am_process, object, exit_reason));
		}
	}
	

	/**
	 * @param self2
	 * @return
	 */
	public ERef add_monitor(EPID target, EObject object) {
		ERef ref = ERT.getLocalNode().createRef();
		monitors.put(ref, new ETuple2(target, object));
		return ref;
	}
	
	/**
	 * @param r
	 */
	public void remove_monitor(ERef r, boolean flush) {
		ETuple2 val = monitors.remove(r);
		if (flush) {
			// TODO: do we need to represent flush somehow?
		}
	}



	protected Mailbox<EObject> mbox = new Mailbox<EObject>();

	protected static enum State {
		INIT, // has not started yet
		RUNNING, // is live
		EXIT_SIG, // received exit signal
		SENDING_EXIT,
		DONE
		// done
	};

	protected State pstate = State.INIT;
	protected EObject exit_reason;

	/**
	 * @throws Pausable
	 * 
	 */
	public void mbox_wait() throws Pausable {
		mbox.untilHasMessage();
	}

	/**
	 * @param longValue
	 */
	public boolean mbox_wait(long timeoutMillis) throws Pausable {
		return mbox.untilHasMessage(timeoutMillis);
	}

	/**
	 * @param msg
	 * @throws Pausable
	 */
	public void mbox_send(EObject msg) throws Pausable {
		mbox.put(msg);
	}

	/**
	 * @return
	 * @throws Pausable
	 */
	public void mbox_remove_one() throws Pausable {
		mbox.get();
	}

	/**
	 * @param from
	 * @param reason
	 */
	public final void send_exit(EHandle from, EObject reason) throws Pausable {

		System.err.println("exit " + from.task() + " -> " + this);

		// ignore exit signals from myself
		if (from == self_handle()) {
			return;
		}
		
		// make sure we don't also send him an exit signal
		links.remove(from);

		synchronized (this) {
			switch (pstate) {

			// process is already "done", just ignore exit signal
			case DONE:
				return;

				// we have already received one exit signal, ignore
				// subsequent ones...
			case EXIT_SIG:
				// TODO: warn that this process is not yet dead. why?
				return;

				// the process is not running yet, this should not happen
			case INIT:
				throw new Error(
						"cannot receive exit signal before we're running");

			default:
				throw new Error("unknown state?");

			case RUNNING:
			}
		}

		process_incoming_exit(from, reason);

	}

	protected abstract void process_incoming_exit(EHandle from, EObject reason)
			throws Pausable;

	/**
	 * will check if this process have received an exit signal (and we're not
	 * trapping)
	 */
	public final void check_exit() {
		if (this.pstate == State.EXIT_SIG) {
			if (exit_reason != null) {
				this.pstate = State.SENDING_EXIT;
				EObject reason = exit_reason;
				throw new ErlangExitSignal(reason);
			}
		}
	}
	

	/* (non-Javadoc)
	 * @see kilim.Task#checkKill()
	 */
	@Override
	public void checkKill() {
		check_exit();
	}

	

	/**
	 * @return
	 */
	public Mailbox<EObject> mbox() {
		return mbox;
	}

	/**
	 * @return
	 */
	public boolean exists() {
		return pstate == State.INIT || pstate == State.RUNNING;
	}

}

@@ -4117,16 +4117,37 @@
 %22 + this
+ + %22, reason=%22+reason
 );%0A%0A%09%09//
@@ -5424,19 +5424,16 @@
 ();%0A%09%7D%0A%0A
-%09%0A%0A
 %09/**%0A%09 *

print more debug info on process exit