/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.8 $
 *********************************************************************/

#include "Runnable.h"
#include <pthread.h>
#include <iostream>
#include <stdio.h>
#include <errno.h>

namespace util
{
	Process::Process()
	{
	}

	void Process::start() throw (RunException&)
	{
		switch (fork())
			{
			case -1: //error
				throw RunException("Process::start(): Cannot start process.");
			case 0: //child
				run();
				exit(0);
				break;
			default:
				break;
			}
	}

	Daemon::Daemon()
	{
	}

	void Daemon::start() throw (RunException&)
	{
		//cerr << "Starting ...\n";

		switch (fork())
			{
			case -1: //error
				//cerr << "Start(): cannot start daemon (fork1)\n";
				throw RunException("Daemon::start(): Cannot start daemon (fork1).");
			case 0:
				break;
			default: //parent
				return; //return to caller
			}


		struct rlimit resourceLimit = {0};
		resourceLimit.rlim_max = 0;
		//get max number of fd's
		switch (getrlimit(RLIMIT_NOFILE,&resourceLimit))
			{
			case -1: //error
				//cerr << "Start(): getrlimit failed\n";
				throw RunException("Daemon::start(): getrlimit failed.");
			}

		//cerr << "Going silent ...\n";
		//close all files (including std*)
		for (unsigned int i=0;i<resourceLimit.rlim_max;i++)
			close(i);

		//new process group, no controlling terminal
		if (setsid() == -1)
			{
				//cerr << "Start(): setsid failed\n";
				throw RunException("Daemon::start(): setsid failed.");
			}

		//Fork again -> first child for the new process group
		switch (fork())
			{
			case -1: //error
				//cerr << "Start(): cannot start daemon (fork2)\n";
				throw RunException("Daemon::start(): Cannot start daemon (fork2).");
			case 0:
				break;
			default: //parent
				exit(0); //exit parent
			}

		chdir("/");
		umask(0);
		int fd = open("/dev/null", O_RDWR); //fd = 0 -> stdin
		dup(fd); //stdout
		dup(fd); //stderr

		run();
		exit(0);
	}


	Thread::Thread(int detachState) : _bRunning(false)
	{
		pthread_attr_init(&_attr);
		pthread_attr_setdetachstate(&_attr,detachState);
	}

	Thread::~Thread()
	{
		pthread_attr_destroy(&_attr);
	}

	void Thread::kill(int signal) throw (RunException&)
	{
		switch (pthread_kill(_tid,signal))
			{
			case 0: break;
			case ESRCH: throw RunException("Thread::kill(int): Invalid thread id (thread may not be running).");
			case EINVAL: throw RunException("Thread::kill(int): Unsupported signal number.");
			}
	}

	void Thread::sleep(unsigned int secs, unsigned int nanosecs) throw (RunException&)
	{
		struct timespec t;
		t.tv_sec = secs;
		t.tv_nsec = nanosecs;
		switch (nanosleep(&t,NULL))
			{
			case 0: return;
			case EINVAL: throw RunException("Thread::sleep(unsigned int, unsigned int): Invalid argument.");
			case EINTR: throw RunException("Thread::sleep(unsigned int, unsigned int): Sleep interrupted.");
			}
	}
	
	void Thread::join(void) throw (RunException&)
	{
		switch(pthread_join(_tid,NULL))
			{
			case 0: break;
			case EINVAL: throw RunException("Thread::join(): Thread is not joinable.");
			case ESRCH: throw RunException("Thread::join(): Invalid thread id (thread may not be running).");
			case EDEADLK: throw RunException("Thread::join(): Recursive deadlock detected.");
			}
	}

	void Thread::detach(void) throw (RunException&)
	{
		switch(pthread_detach(_tid))
			{
			case 0: break;
			case EINVAL: throw RunException("Thread::detach(): Thread is not joinable (detachable)");
			case ESRCH: throw RunException("Thread::detach(): Invalid thread id (thread may not be running).");
			}
	}

	void Thread::start(void) throw (RunException&)
	{
		int result;
		try
			{
				_syncMutex.lock();
				if (_bRunning)
					throw RunException("Thread::start(): Thread already running.");
				_bRunning = true;
				result = pthread_create(&_tid,&_attr,starter,this);
				_syncMutex.unlock();
			}
		catch (MutexException& me)
			{
				result = -1;
			}

		switch(result)
			{
			case 0: break;
			case ENOMEM: throw RunException("Thread::start(): Not enough resources.");
			case EINVAL: throw RunException("Thread::start(): Invalid thread attributes.");
			case EPERM: throw RunException("Thread::start(): Thread creation not allowed.");
			default: throw RunException("Thread::start(): Thread cannot be created. Did you forget the -lpthread linker flag?");
			}
	}

	void *Thread::starter(void *arg)
	{
		Thread* thread = (Thread*)arg;
		thread->run();
		thread->_bRunning = false;
		int i;
		AllListItems(i,thread->_lstListeners)
			thread->_lstListeners[i]->threadDied(thread);

		pthread_exit(NULL);
		return NULL;
	}	
}
