#ifndef FUTURE_HPP_INCLUDED
#define FUTURE_HPP_INCLUDED

// Copyright (c) 2007 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0.
// http://www.boost.org/LICENSE_1_0.txt

#include "event.hpp"
#include "exception_ptr.hpp"
#include "thread.hpp"

#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

class future_result_moved: public std::exception
{
public:

    char const * what() const throw()
    {
        return "std::future_result_moved";
    }
};

template< class R > class future_impl
{
private:

    event ready_;

    boost::optional<R> r_;
    exception_ptr e_;

    std::thread::handle th_;

    future_impl( future_impl const & );
    future_impl & operator=( future_impl const & );

public:

    future_impl()
    {
    }

    bool has_value() const // throw()
    {
        return try_join() && e_ == 0;
    }

    bool has_exception() const // throw()
    {
        return try_join() && e_ != 0;
    }

    void join() // throw( thread::cancel_exception )
    {
        int r = ready_.wait();
        if( r != 0 ) throw std::thread_error( r );
    }

    bool try_join() // throw()
    {
        return ready_.try_wait() == 0;
    }

    bool timed_join( timespec const & abstime ) // throw( thread::cancel_exception )
    {
        int r = ready_.timed_wait( abstime );

        if( r == 0 ) return true;
        if( r == ETIMEDOUT ) return false;

        throw std::thread_error( r );
    }

    std::thread::handle get_cancel_handle()
    {
        std::basic_lock< event > lock( ready_ );
        return th_;
    }

    void cancel()
    {
        std::thread::cancel( get_cancel_handle() );
    }

    R copy() // throw( thread::cancel_exception, ... )
    {
        join();

        if( e_ != 0 )
        {
            rethrow_exception( e_ );
        }

        assert( r_.is_initialized() );
        return r_.get();
    }

    R move()
    {
        join();

        if( e_ != 0 )
        {
            rethrow_exception( e_ );
        }

        assert( r_.is_initialized() );

        e_ = copy_exception( future_result_moved() );
        return r_.get(); // std::move( r_.get() )
    }

    void set_value( R const & r )
    {
        std::basic_lock< event > lock( ready_ );

        if( !ready_.unlocked_is_set() )
        {
            try
            {
                r_.reset( r );
            }
            catch( ... )
            {
                e_ = current_exception();
            }

            th_ = std::thread::handle();
            ready_.unlocked_set();
        }
    }

    void set_exception( exception_ptr p ) // throw()
    {
        std::basic_lock< event > lock( ready_ );

        if( !ready_.unlocked_is_set() )
        {
            e_ = p;
            th_ = std::thread::handle();
            ready_.unlocked_set();
        }
    }

    void set_cancel_handle( std::thread::handle const & th )
    {
        std::basic_lock< event > lock( ready_ );
        th_ = th;
    }
};

template< class R > class future
{
private:

    boost::shared_ptr< future_impl<R> > pi_;

public:

    future(): pi_( new future_impl<R>() )
    {
    }

    bool has_value() const
    {
        return pi_->has_value();
    }

    bool has_exception() const
    {
        return pi_->has_exception();
    }

    void join()
    {
        return pi_->join();
    }

    bool try_join()
    {
        return pi_->try_join();
    }

    bool timed_join( timespec const & abstime )
    {
        return pi_->timed_join( abstime );
    }

    void cancel()
    {
        pi_->cancel();
    }

    operator R() const
    {
        return pi_->copy();
    }

    R get() const
    {
        return pi_->copy();
    }

    R move()
    {
        return pi_->move();
    }

    void set_value( R const & r )
    {
        pi_->set_value( r );
    }

    void set_exception( exception_ptr p ) // throw()
    {
        pi_->set_exception( p );
    }

    void set_cancel_handle( std::thread::handle const & th )
    {
        pi_->set_thread_handle( th );
    }
};

template< class R > class future< R& >: private future< R* >
{
private:

    typedef future< R* > base_type;

public:

    using base_type::has_value;
    using base_type::has_exception;

    using base_type::join;
    using base_type::try_join;
    using base_type::timed_join;

    using base_type::cancel;

    operator R&() const
    {
        return *base_type::get();
    }

    R& get() const
    {
        return *base_type::get();
    }

    void set_value( R & r )
    {
        base_type::set_value( &r );
    }

    using base_type::set_exception;
    using base_type::set_cancel_handle;
};

/* R&&

template< class R > class future< R&& >: private future< R >
{
private:

    typedef future< R > base_type;

public:

    future()
    {
    }

    template< class R > future( future<R> const & f ): base_type( f )
    {
    }

    using base_type::has_value;
    using base_type::has_exception;

    using base_type::join;
    using base_type::try_join;
    using base_type::timed_join;

    using base_type::cancel;

    operator R()
    {
        return base_type::move();
    }

    R get()
    {
        return base_type::move();
    }

    using base_type::move;

    using base_type::set_value;
    using base_type::set_exception;
    using base_type::set_cancel_handle;
};

*/

template<> class future< void >: private future< int >
{
private:

    typedef future< int > base_type;

public:

    using base_type::has_value;
    using base_type::has_exception;

    using base_type::join;
    using base_type::try_join;
    using base_type::timed_join;

    using base_type::cancel;

    void get() const
    {
        base_type::get();
    }

    void set_value()
    {
        base_type::set_value( 0 );
    }

    using base_type::set_exception;
    using base_type::set_cancel_handle;
};

#endif // #ifndef FUTURE_HPP_INCLUDED
