//
// reactor_op_queue.hpp
// ~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_ASIO_DETAIL_REACTOR_OP_QUEUE_HPP
#define BOOST_ASIO_DETAIL_REACTOR_OP_QUEUE_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include <boost/asio/detail/push_options.hpp>

#include <boost/asio/error.hpp>
#include <boost/asio/detail/hash_map.hpp>
#include <boost/asio/detail/noncopyable.hpp>
#include <boost/asio/detail/op_queue.hpp>
#include <boost/asio/detail/reactor_op.hpp>

namespace boost {
namespace asio {
namespace detail {

template <typename Descriptor>
class reactor_op_queue
  : private noncopyable
{
public:
  // Constructor.
  reactor_op_queue()
    : operations_()
  {
  }

  // Add a new operation to the queue. Returns true if this is the only
  // operation for the given descriptor, in which case the reactor's event
  // demultiplexing function call may need to be interrupted and restarted.
  bool enqueue_operation(Descriptor descriptor, reactor_op* op)
  {
    typedef typename operations_map::iterator iterator;
    typedef typename operations_map::value_type value_type;
    std::pair<iterator, bool> entry =
      operations_.insert(value_type(descriptor, operations()));
    entry.first->second.op_queue_.push(op);
    return entry.second;
  }

  // Cancel all operations associated with the descriptor. Any operations
  // pending for the descriptor will be notified that they have been cancelled
  // next time perform_cancellations is called. Returns true if any operations
  // were cancelled, in which case the reactor's event demultiplexing function
  // may need to be interrupted and restarted.
  bool cancel_operations(Descriptor descriptor, op_queue<operation>& ops,
      const boost::system::error_code& ec =
        boost::asio::error::operation_aborted)
  {
    typename operations_map::iterator i = operations_.find(descriptor);
    if (i != operations_.end())
    {
      while (reactor_op* op = i->second.op_queue_.front())
      {
        op->ec_ = ec;
        i->second.op_queue_.pop();
        ops.push(op);
      }
      operations_.erase(i);
      return true;
    }

    return false;
  }

  // Whether there are no operations in the queue.
  bool empty() const
  {
    return operations_.empty();
  }

  // Determine whether there are any operations associated with the descriptor.
  bool has_operation(Descriptor descriptor) const
  {
    return operations_.find(descriptor) != operations_.end();
  }

  // Perform the operations corresponding to the descriptor. Returns true if
  // there are still unfinished operations queued for the descriptor.
  bool perform_operations(Descriptor descriptor, op_queue<operation>& ops)
  {
    typename operations_map::iterator i = operations_.find(descriptor);
    if (i != operations_.end())
    {
      while (reactor_op* op = i->second.op_queue_.front())
      {
        if (op->perform())
        {
          i->second.op_queue_.pop();
          ops.push(op);
        }
        else
        {
          return true;
        }
      }
      operations_.erase(i);
    }
    return false;
  }

  // Fill a descriptor set with the descriptors corresponding to each active
  // operation. The op_queue is used only when descriptors fail to be added to
  // the descriptor set.
  template <typename Descriptor_Set>
  void get_descriptors(Descriptor_Set& descriptors, op_queue<operation>& ops)
  {
    typename operations_map::iterator i = operations_.begin();
    while (i != operations_.end())
    {
      Descriptor descriptor = i->first;
      ++i;
      if (!descriptors.set(descriptor))
      {
        boost::system::error_code ec(error::fd_set_failure);
        cancel_operations(descriptor, ops, ec);
      }
    }
  }

  // Perform the operations corresponding to the ready file descriptors
  // contained in the given descriptor set.
  template <typename Descriptor_Set>
  void perform_operations_for_descriptors(
      const Descriptor_Set& descriptors, op_queue<operation>& ops)
  {
    typename operations_map::iterator i = operations_.begin();
    while (i != operations_.end())
    {
      typename operations_map::iterator op_iter = i++;
      if (descriptors.is_set(op_iter->first))
      {
        while (reactor_op* op = op_iter->second.op_queue_.front())
        {
          if (op->perform())
          {
            op_iter->second.op_queue_.pop();
            ops.push(op);
          }
          else
          {
            break;
          }
        }

        if (op_iter->second.op_queue_.empty())
          operations_.erase(op_iter);
      }
    }
  }

  // Get all operations owned by the queue.
  void get_all_operations(op_queue<operation>& ops)
  {
    typename operations_map::iterator i = operations_.begin();
    while (i != operations_.end())
    {
      typename operations_map::iterator op_iter = i++;
      ops.push(op_iter->second.op_queue_);
      operations_.erase(op_iter);
    }
  }

private:
  struct operations
  {
    operations() {}
    operations(const operations&) {}
    void operator=(const operations&) {}

    // The operations waiting on the desccriptor.
    op_queue<reactor_op> op_queue_;
  };

  // The type for a map of operations.
  typedef hash_map<Descriptor, operations> operations_map;

  // The operations that are currently executing asynchronously.
  operations_map operations_;
};

} // namespace detail
} // namespace asio
} // namespace boost

#include <boost/asio/detail/pop_options.hpp>

#endif // BOOST_ASIO_DETAIL_REACTOR_OP_QUEUE_HPP
