// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2008-2011 Alexander Warg <warg@os.inf.tu-dresden.de>,
 *               Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
 *               Adam Lackorzynski <adam@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/sys/types.h>
#include <l4/sys/vcon>
#include <l4/sys/cxx/ipc_legacy>
#include <l4/cxx/minmax>

namespace L4Re { namespace Util {

/**
 * \brief Console server template class.
 * \ingroup api_l4re_util
 *
 * This template uses vcon_write() and vcon_read() to get and deliver data
 * from the implementor.
 *
 * vcon_read() needs to update the status argument with the
 * L4_vcon_read_stat flags, especially the L4_VCON_READ_STAT_DONE flag
 * to indicate that there's nothing more to read for the other end.
 *
 * vcon_write() gets the live data from the UTCB. Make sure to copy out the
 * data before using the UTCB again.
 *
 * The size parameter of both functions is given in bytes.
 */
template< typename SVR >
class Vcon_svr
{
public:
  L4_RPC_LEGACY_DISPATCH(L4::Vcon);

  l4_msgtag_t op_dispatch(l4_utcb_t *utcb, l4_msgtag_t tag, L4::Vcon::Rights)
  {
    l4_msg_regs_t *m = l4_utcb_mr_u(utcb);
    L4::Opcode op = m->mr[0];

    switch (op)
      {
      case L4_VCON_WRITE_OP:
        if (tag.words() < 3)
          return l4_msgtag(-L4_ENOREPLY, 0, 0, 0);

        this_vcon()->vcon_write(reinterpret_cast<char const *>(&m->mr[2]),
                                m->mr[1]);
        return l4_msgtag(-L4_ENOREPLY, 0, 0, 0);

      case L4_VCON_SET_ATTR_OP:
        {
          if (tag.words() < 4)
            return l4_msgtag(-L4_EINVAL, 0, 0, 0);

          auto attr = reinterpret_cast<l4_vcon_attr_t const *>(&m->mr[1]);
          return l4_msgtag(this_vcon()->vcon_set_attr(attr), 0, 0, 0);
        }
      case L4_VCON_GET_ATTR_OP:
        {
          auto attr = reinterpret_cast<l4_vcon_attr_t *>(&m->mr[1]);
          return l4_msgtag(this_vcon()->vcon_get_attr(attr), 4, 0, 0);
        }

      default:
        break;
      }

    unsigned const max_size = sizeof(l4_utcb_mr()->mr) - sizeof(l4_utcb_mr()->mr[0]);
    char buf[max_size];

    unsigned size = cxx::min<unsigned>(op >> 16, max_size);

    // Hmm, could we avoid the double copy here?
    l4_umword_t v = this_vcon()->vcon_read(buf, size);
    unsigned bytes = v & L4_VCON_READ_SIZE_MASK;

    if (bytes <= size)
      v |= L4_VCON_READ_STAT_DONE;

    m->mr[0] = v;
    __builtin_memcpy(&m->mr[1], buf, bytes);

    return l4_msgtag(0, l4_bytes_to_mwords(bytes) + 1, 0, 0);
  }

  unsigned vcon_read(char *buf, unsigned size) noexcept;
  void vcon_write(const char *buf, unsigned size) noexcept;
  int vcon_set_attr(l4_vcon_attr_t const *) noexcept
  { return -L4_EOK; }
  int vcon_get_attr(l4_vcon_attr_t *attr) noexcept
  {
    attr->l_flags = attr->o_flags = attr->i_flags = 0;
    return -L4_EOK;
  }

private:
  SVR const *this_vcon() const { return static_cast<SVR const *>(this); }
  SVR *this_vcon() { return static_cast<SVR *>(this); }
};

}}
