/* closure.cc - Cooperative multitasking
 * Copyright 2008 Bas Wijnen <wijnen@debian.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "closure.hh"
#include "error.hh"
#include "debug.hh"
#include <unistd.h>
#include <errno.h>

namespace shevek
{
	closure *closure::current = NULL;

	void closure::do_write (int *fds)
	{
		errno = 0;
		char c = 0;
		while (::write (fds[1], &c, 1) != 1)
		{
			if (errno == EINTR)
			{
				errno = 0;
				continue;
			}
			shevek_error_errno ("error writing for closure");
			throw "error writing for closure";
		}
	}

	void closure::do_read (int *fds)
	{
		char c = 0;
		errno = 0;
		while (::read (fds[0], &c, 1) != 1)
		{
			if (errno == EINTR)
			{
				errno = 0;
				continue;
			}
			shevek_error_errno ("error reading for closure");
			throw "error reading for closure";
		}
	}

	closure::closure ()
	{
		closure *real_current = current;
		if (::pipe (blocking_pipe) || ::pipe (waking_pipe))
		{
			shevek_error_errno
				("unable to create pipes for closure");
			throw "unable to create pipe for closure";
		}
		state = EMPTY;
		if (::pthread_create (&thread, NULL, &start_wrapper, this))
		{
			shevek_error_errno
				("unable to create thread for closure");
			throw "unable to create thread for closure";
		}
		do_read (blocking_pipe);
		current = real_current;
	}

	closure::~closure ()
	{
		if (::pthread_cancel (thread) || ::pthread_join (thread, NULL))
		{
			shevek_warning_errno ("unable to kill closure");
		}
		::close (waking_pipe[0]);
		::close (waking_pipe[1]);
		::close (blocking_pipe[0]);
		::close (blocking_pipe[1]);
	}

	void *closure::start_wrapper (void *d)
	{
		// Allow killing at any time.
		::pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
		current = reinterpret_cast <closure *> (d);
		while (true)
		{
			block ();
			// Someone told us to start running with a function.
			// Do so.
			current->function ();
			// When done, set state to empty and call the callback
			// (if it exists).
			current->state = EMPTY;
			current->function = sigc::slot0 <void> ();
			if (current->callback)
			{
				current->callback ();
				current->callback = sigc::slot0 <void> ();
			}
		}
		return NULL;
	}

	void closure::set_function (sigc::slot0 <void> func, bool run,
			sigc::slot0 <void> cb)
	{
		if (!empty ())
		{
			shevek_error
				("changing function of non-empty closure");
			throw "changing function of non-empty closure";
		}
		function = func;
		state = BLOCKING;
		callback = cb;
		if (run)
			wake ();
	}

	void closure::block ()
	{
		if (!current)
		{
			shevek_error ("trying to block non-closure");
			throw "trying to block non-closure";
		}
		closure *c = current;
		// Set state to blocking, unless it was EMPTY.
		if (c->state == RUNNING)
			c->state = BLOCKING;
		c->do_write (c->blocking_pipe);
		c->do_read (c->waking_pipe);
		c->state = RUNNING;
	}

	void closure::wake ()
	{
		if (state != BLOCKING)
		{
			shevek_error ("trying to wake closure which isn't "
					"blocking");
			throw "trying to wake closure which isn't blocking";
		}
		Glib::RefPtr <closure> keep_object_alive = refptr_this <closure> ();
		(void)&keep_object_alive;
		closure *previous = current;
		current = this;
		do_write (waking_pipe);
		do_read (blocking_pipe);
		current = previous;
	}
}
