// File: crn_dynamic_stream.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once
#include "crn_data_stream.h"

namespace crnlib {
class dynamic_stream : public data_stream {
 public:
  dynamic_stream(uint initial_size, const char* pName = "dynamic_stream", uint attribs = cDataStreamSeekable | cDataStreamWritable | cDataStreamReadable)
      : data_stream(pName, attribs),
        m_ofs(0) {
    open(initial_size, pName, attribs);
  }

  dynamic_stream(const void* pBuf, uint size, const char* pName = "dynamic_stream", uint attribs = cDataStreamSeekable | cDataStreamWritable | cDataStreamReadable)
      : data_stream(pName, attribs),
        m_ofs(0) {
    open(pBuf, size, pName, attribs);
  }

  dynamic_stream()
      : data_stream(),
        m_ofs(0) {
    open();
  }

  virtual ~dynamic_stream() {
  }

  bool open(uint initial_size = 0, const char* pName = "dynamic_stream", uint attribs = cDataStreamSeekable | cDataStreamWritable | cDataStreamReadable) {
    close();

    m_opened = true;
    m_buf.clear();
    m_buf.resize(initial_size);
    m_ofs = 0;
    m_name.set(pName ? pName : "dynamic_stream");
    m_attribs = static_cast<attribs_t>(attribs);
    return true;
  }

  bool reopen(const char* pName, uint attribs) {
    if (!m_opened) {
      return open(0, pName, attribs);
    }

    m_name.set(pName ? pName : "dynamic_stream");
    m_attribs = static_cast<attribs_t>(attribs);
    return true;
  }

  bool open(const void* pBuf, uint size, const char* pName = "dynamic_stream", uint attribs = cDataStreamSeekable | cDataStreamWritable | cDataStreamReadable) {
    if (!m_opened) {
      m_opened = true;
      m_buf.resize(size);
      if (size) {
        CRNLIB_ASSERT(pBuf);
        memcpy(&m_buf[0], pBuf, size);
      }
      m_ofs = 0;
      m_name.set(pName ? pName : "dynamic_stream");
      m_attribs = static_cast<attribs_t>(attribs);
      return true;
    }

    return false;
  }

  virtual bool close() {
    if (m_opened) {
      m_opened = false;
      m_buf.clear();
      m_ofs = 0;
      return true;
    }

    return false;
  }

  const crnlib::vector<uint8>& get_buf() const { return m_buf; }
  crnlib::vector<uint8>& get_buf() { return m_buf; }

  void reserve(uint size) {
    if (m_opened) {
      m_buf.reserve(size);
    }
  }

  virtual const void* get_ptr() const { return m_buf.empty() ? NULL : &m_buf[0]; }

  virtual uint read(void* pBuf, uint len) {
    CRNLIB_ASSERT(pBuf && (len <= 0x7FFFFFFF));

    if ((!m_opened) || (!is_readable()) || (!len))
      return 0;

    CRNLIB_ASSERT(m_ofs <= m_buf.size());

    uint bytes_left = m_buf.size() - m_ofs;

    len = math::minimum<uint>(len, bytes_left);

    if (len)
      memcpy(pBuf, &m_buf[m_ofs], len);

    m_ofs += len;

    return len;
  }

  virtual uint write(const void* pBuf, uint len) {
    CRNLIB_ASSERT(pBuf && (len <= 0x7FFFFFFF));

    if ((!m_opened) || (!is_writable()) || (!len))
      return 0;

    CRNLIB_ASSERT(m_ofs <= m_buf.size());

    uint new_ofs = m_ofs + len;
    if (new_ofs > m_buf.size())
      m_buf.resize(new_ofs);

    memcpy(&m_buf[m_ofs], pBuf, len);
    m_ofs = new_ofs;

    return len;
  }

  virtual bool flush() {
    if (!m_opened)
      return false;

    return true;
  }

  virtual uint64 get_size() {
    if (!m_opened)
      return 0;

    return m_buf.size();
  }

  virtual uint64 get_remaining() {
    if (!m_opened)
      return 0;

    CRNLIB_ASSERT(m_ofs <= m_buf.size());

    return m_buf.size() - m_ofs;
  }

  virtual uint64 get_ofs() {
    if (!m_opened)
      return 0;

    return m_ofs;
  }

  virtual bool seek(int64 ofs, bool relative) {
    if ((!m_opened) || (!is_seekable()))
      return false;

    int64 new_ofs = relative ? (m_ofs + ofs) : ofs;

    if (new_ofs < 0)
      return false;
    else if (new_ofs > m_buf.size())
      return false;

    m_ofs = static_cast<uint>(new_ofs);

    post_seek();

    return true;
  }

 private:
  crnlib::vector<uint8> m_buf;
  uint m_ofs;
};

}  // namespace crnlib
