// vi:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 * \brief Basic IO stream
 */
/*
 * (c) 2009 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

namespace L4 {

  /**
   * \brief Modifier class for the IO stream.
   *
   * An IO Modifier can be used to change properties of an IO stream
   * for example the number format.
   */
  class IOModifier
  {
  public:
    IOModifier(int x) : mod(x) {}
    bool operator == (IOModifier o) { return mod == o.mod; }
    bool operator != (IOModifier o) { return mod != o.mod; }
    int mod;
  };

  /**
   * \internal
   * \brief Backend to write or read stream data.
   */
  class IOBackend
  {
  public:
    typedef int Mode;

  protected:
    friend class BasicOStream;

    IOBackend()
    : int_mode(10)
    {}

    virtual ~IOBackend() {}

    virtual void write(char const *str, unsigned len) = 0;

  private:
    void write(IOModifier m);
    void write(long long int c, int len);
    void write(long long unsigned c, int len);
    void write(long long unsigned c, unsigned char base = 10,
               unsigned char len = 0, char pad = ' ');

    Mode mode() const
    { return int_mode; }

    void mode(Mode m)
    { int_mode = m; }

    int int_mode;
  };

  /**
   * \internal
   * \brief Write-only backend for stream data.
   */
  class BasicOStream
  {
  public:
    BasicOStream(IOBackend *b)
    : iob(b)
    {}

    void write(char const *str, unsigned len)
    {
      if (iob)
        iob->write(str, len);
    }

    void write(long long int c, int len)
    {
      if (iob)
        iob->write(c, len);
    }

    void write(long long unsigned c, unsigned char base = 10,
               unsigned char len = 0, char pad = ' ')
    {
      if (iob)
        iob->write(c, base, len, pad);
    }

    void write(long long unsigned c, int len)
    {
      if (iob)
        iob->write(c, len);
    }

    void write(IOModifier m)
    {
      if (iob)
        iob->write(m);
    }

    IOBackend::Mode be_mode() const
    {
      if (iob)
        return iob->mode();
      return 0;
    }

    void be_mode(IOBackend::Mode m)
    {
      if (iob)
        iob->mode(m);
    }

  private:
    IOBackend *iob;
  };

  /**
   * \internal
   * \brief Container class describing a the number format.
   */
  class IONumFmt
  {
  public:
    IONumFmt(unsigned long long n, unsigned char base = 10,
             unsigned char len = 0, char pad = ' ')
      : n(n), base(base), len(len), pad(pad)
    {}

  BasicOStream &print(BasicOStream &o) const;

  private:
    unsigned long long n;
    unsigned char base, len;
    char pad;
  };

  inline IONumFmt n_hex(unsigned long long n) { return IONumFmt(n, 16); }

  /**
   * \brief Modifies the stream to print numbers as hexadecimal values.
   */
  extern IOModifier const hex;

  /**
   * \brief Modifies the stream to print numbers as decimal values.
   */
  extern IOModifier const dec;

  inline
  BasicOStream &IONumFmt::print(BasicOStream &o) const
  {
    o.write(n, base, len, pad);
    return o;
  }
}


// Implementation

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, char const * const str)
{
  if (!str)
    {
      s.write("(NULL)", 6);
      return s;
    }

  unsigned l = 0;
  for (; str[l] != 0; l++)
    ;
  s.write(str, l);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, signed short u)
{
  s.write(static_cast<long long signed>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, signed u)
{
  s.write(static_cast<long long signed>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, signed long u)
{
  s.write(static_cast<long long signed>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, signed long long u)
{
  s.write(u, -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, unsigned short u)
{
  s.write(static_cast<long long unsigned>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, unsigned u)
{
  s.write(static_cast<long long unsigned>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, unsigned long u)
{
  s.write(static_cast<long long unsigned>(u), -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, unsigned long long u)
{
  s.write(u, -1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, void const *u)
{
  long unsigned x = reinterpret_cast<long unsigned>(u);
  L4::IOBackend::Mode mode = s.be_mode();
  s.write(L4::hex);
  s.write(static_cast<long long unsigned>(x), -1);
  s.be_mode(mode);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, L4::IOModifier m)
{
  s.write(m);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &s, char c)
{
  s.write(&c, 1);
  return s;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &o, L4::IONumFmt const &n)
{ return n.print(o); }
