/*
 * Copyright (C) 2006-2008 Marc Boris Duerner
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include "threadimpl.h"
#include "cxxtools/systemerror.h"
#include "cxxtools/timespan.h"
#include "config.h"
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <iostream>
#ifdef HAVE_ABI_FORCED_UNWIND
#include <cxxabi.h>
#endif

extern "C"
{
    static void* thread_entry(void* arg)
    {
        cxxtools::ThreadImpl* impl = (cxxtools::ThreadImpl*)arg;
        if( impl->cb() )
        {
            try
            {
                impl->cb()->call();
            }
            catch (const std::exception& e)
            {
                std::cerr << "exception occured: " << e.what() << std::endl;
            }
#ifdef HAVE_ABI_FORCED_UNWIND
            catch (const abi::__forced_unwind&)
            {
                throw;
            }
#endif
            catch (...)
            {
                std::cerr << "exception occured" << std::endl;
            }
        }

        return 0;
    }
}

namespace cxxtools
{

void ThreadImpl::detach()
{
    _detached = true;

    if( _id )
    {
        int ret = pthread_detach(_id);
        if(ret != 0)
            throw SystemError("pthread_detach");
    }
}


void ThreadImpl::init(const Callable<void>& cb)
{
    delete _cb;
    _cb = cb.clone();
}


void ThreadImpl::start()
{
    size_t stacksize = 0;

    pthread_attr_t attrs;
    pthread_attr_init(&attrs);
    //pthread_attr_setinheritsched(&attrs, PTHREAD_INHERIT_SCHED);

    if (stacksize > 0)
        pthread_attr_setstacksize(&attrs ,stacksize);

    if (_detached)
    {
        pthread_t id;
        int ret = pthread_create(&id, &attrs, thread_entry, this);
        pthread_attr_destroy(&attrs);

        if (ret != 0)
            throw SystemError("pthread_create");

        ret = pthread_detach(id);
        if (ret != 0)
            throw SystemError("pthread_detach");
    }
    else
    {
        int ret = pthread_create(&_id, &attrs, thread_entry, this);
        pthread_attr_destroy(&attrs);

        if (ret != 0)
            throw SystemError("pthread_create");
    }

}


void ThreadImpl::join()
{
    void* threadRet = 0;
    int ret = pthread_join(_id, &threadRet);

    if (ret != 0)
        throw SystemError("pthread_join");
}


void ThreadImpl::terminate()
{
    int ret = pthread_kill(_id, SIGKILL);
    if(ret != 0)
        throw SystemError("pthread_kill");
}


void ThreadImpl::sleep(const Timespan& t)
{
#ifdef HAVE_NANOSLEEP

    struct timespec ts;
    ts.tv_sec = t.totalUSecs() / 1000000l;
    ts.tv_nsec = t.totalUSecs() % 1000000l * 1000;

    while (::nanosleep(&ts, &ts) == -1 && errno == EINTR)
        ;

#else

    Timespan eow = Timespan::gettimeofday() + t;

    Timespan remaining;

    while ((remaining = eow - Timespan::gettimeofday()).totalUSecs() >= 1000000)
    {
        ::sleep(remaining.totalUSecs() / 1000000);
    }

    while (remaining.totalUSecs() > 0)
    {
        ::usleep(remaining.totalUSecs());
        remaining = eow - Timespan::gettimeofday();
    }

#endif
}


}
