// vi:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 */
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               Alexander Warg <warg@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include <l4/re/cap_alloc>
#include <l4/re/util/cap_alloc>
#include <l4/re/util/unique_cap>
#include <l4/re/env>
#include <l4/re/rm>
#include <l4/re/util/event_buffer>
#include <l4/sys/factory>
#include <l4/cxx/type_traits>

namespace L4Re { namespace Util {

/**
 * Convenience wrapper for getting access to an event object.
 *
 * After calling init() the class supplies the event-buffer and the
 * associated IRQ object.
 */
template< typename PAYLOAD >
class Event_t
{
public:
  /**
   * Modes of operation.
   */
  enum Mode
  {
    Mode_irq,      ///< Create an IRQ and attach, to get notifications.
    Mode_polling,  ///< Do not use an IRQ.
  };

  /**
   * Initialise an event object.
   *
   * \tparam IRQ_TYPE  Type used for handling notifications from the event
   *                   provider. This must be derived from L4::Triggerable.
   *
   * \param event   Capability to event.
   * \param env     Pointer to L4Re-Environment
   * \param ca      Pointer to capability allocator.
   *
   * \retval 0           Success
   * \retval -L4_ENOMEM  No memory to allocate required capabilities.
   * \retval <0          Other IPC errors.
   */
  template<typename IRQ_TYPE>
  int init(L4::Cap<L4Re::Event> event,
           L4Re::Env const *env = L4Re::Env::env(),
           L4Re::Cap_alloc *ca = &L4Re::Util::cap_alloc)
  {
    Unique_cap<L4Re::Dataspace> ev_ds(ca->alloc<L4Re::Dataspace>());
    if (!ev_ds.is_valid())
      return -L4_ENOMEM;

    int r;

    Unique_del_cap<IRQ_TYPE> ev_irq(ca->alloc<IRQ_TYPE>());
    if (!ev_irq.is_valid())
      return -L4_ENOMEM;

    if ((r = l4_error(env->factory()->create(ev_irq.get()))))
      return r;

    if ((r = l4_error(event->bind(0, ev_irq.get()))))
      return r;

    if ((r = event->get_buffer(ev_ds.get())))
      return r;

    long sz = ev_ds->size();
    if (sz < 0)
      return sz;

    Rm::Unique_region<void*> buf;

    if ((r = env->rm()->attach(&buf, sz,
                               L4Re::Rm::F::Search_addr | L4Re::Rm::F::RW,
                               L4::Ipc::make_cap_rw(ev_ds.get()))))
      return r;

    _ev_buffer = L4Re::Event_buffer_t<PAYLOAD>(buf.get(), sz);
    _ev_ds     = cxx::move(ev_ds);
    _ev_irq    = cxx::move(ev_irq);
    _buf       = cxx::move(buf);

    return 0;
  }

  /**
   * Initialise an event object in polling mode.
   *
   * \param event   Capability to event.
   * \param env     Pointer to L4Re-Environment
   * \param ca      Pointer to capability allocator.
   *
   * \retval 0           Success
   * \retval -L4_ENOMEM  No memory to allocate required capabilities.
   * \retval <0          Other IPC errors.
   */
  int init_poll(L4::Cap<L4Re::Event> event,
                L4Re::Env const *env = L4Re::Env::env(),
                L4Re::Cap_alloc *ca = &L4Re::Util::cap_alloc)
  {
    Unique_cap<L4Re::Dataspace> ev_ds(ca->alloc<L4Re::Dataspace>());
    if (!ev_ds.is_valid())
      return -L4_ENOMEM;

    int r;

    if ((r = event->get_buffer(ev_ds.get())))
      return r;

    long sz = ev_ds->size();
    if (sz < 0)
      return sz;

    Rm::Unique_region<void*> buf;

    if ((r = env->rm()->attach(&buf, sz,
                               L4Re::Rm::F::Search_addr | L4Re::Rm::F::RW,
                               L4::Ipc::make_cap_rw(ev_ds.get()))))
      return r;

    _ev_buffer = L4Re::Event_buffer_t<PAYLOAD>(buf.get(), sz);
    _ev_ds     = cxx::move(ev_ds);
    _buf       = cxx::move(buf);

    return 0;
  }

  /**
   * Get event buffer.
   *
   * \return Event buffer object.
   */
  L4Re::Event_buffer_t<PAYLOAD> &buffer() { return _ev_buffer; }

  /**
   * Get event IRQ.
   *
   * \return Event IRQ.
   */
  L4::Cap<L4::Triggerable> irq() const { return _ev_irq.get(); }

private:
  Unique_cap<L4Re::Dataspace> _ev_ds;
  Unique_del_cap<L4::Triggerable> _ev_irq;
  L4Re::Event_buffer_t<PAYLOAD> _ev_buffer;
  Rm::Unique_region<void*> _buf;
};

typedef Event_t<Default_event_payload> Event;

}}
