/*
 * ZoneMinder Flexible Memory Interface, $Date$, $Revision$
 * Copyright (C) 2001-2008 Philip Coombes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef ZM_BUFFER_H
#define ZM_BUFFER_H

#include "zm_logger.h"
#include "zm_time.h"
#include <cstring>

class Buffer {
 protected:
  unsigned char *mStorage;
  unsigned int mAllocation;
  unsigned int mSize;
  unsigned char *mHead;
  unsigned char *mTail;

 public:
  Buffer() :
    mStorage(nullptr),
    mAllocation(0),
    mSize(0),
    mHead(nullptr),
    mTail(nullptr) {
  }
  explicit Buffer(unsigned int pSize) : mAllocation(pSize), mSize(0) {
    mHead = mStorage = new unsigned char[mAllocation];
    if (mAllocation) *mHead = '\0';
    mTail = mHead;
  }
  Buffer(const unsigned char *pStorage, unsigned int pSize) :
    mAllocation(pSize), mSize(pSize) {
    mHead = mStorage = new unsigned char[mSize];
    std::memcpy(mStorage, pStorage, mSize);
    mTail = mHead + mSize;
  }
  Buffer(const Buffer &buffer) :
    mAllocation(buffer.mSize),
    mSize(buffer.mSize) {
    mHead = mStorage = new unsigned char[mSize];
    std::memcpy(mStorage, buffer.mHead, mSize);
    mTail = mHead + mSize;
  }
  ~Buffer() {
    delete[] mStorage;
  }
  unsigned char *head() const { return mHead; }
  unsigned char *tail() const { return mTail; }
  unsigned int size() const { return mSize; }
  bool empty() const { return( mSize == 0 ); }
  unsigned int size( unsigned int pSize ) {
    if ( mSize < pSize ) {
      expand(pSize-mSize);
    }
    return mSize;
  }
  // unsigned int Allocation() const { return( mAllocation ); }

  void clear() {
    mSize = 0;
    mHead = mTail = mStorage;
  }

  unsigned int assign(const unsigned char *pStorage, unsigned int pSize);
  unsigned int assign(const Buffer &buffer) {
    return assign(buffer.mHead, buffer.mSize);
  }

  // Trim from the front of the buffer
  unsigned int consume(unsigned int count) {
    if (count > mSize) {
      Warning("Attempt to consume %d bytes of buffer, size is only %d bytes",
              count, mSize);
      count = mSize;
    }
    mHead += count;
    mSize -= count;
    tidy(0);
    return count;
  }
  // Trim from the end of the buffer
  unsigned int shrink(unsigned int count) {
    if (count > mSize) {
      Warning("Attempt to shrink buffer by %d bytes, size is only %d bytes",
              count, mSize);
      count = mSize;
    }
    mSize -= count;
    if (mTail > (mHead + mSize))
      mTail = mHead + mSize;
    tidy(0);
    return count;
  }
  // Add to the end of the buffer
  unsigned int expand(unsigned int count);

  // Return pointer to the first pSize bytes and advance the head
  // We don't call tidy here because it is assumed the ram will be used afterwards
  // This differs from consume
  unsigned char *extract(unsigned int pSize) {
    if (pSize > mSize) {
      Warning("Attempt to extract %d bytes of buffer, size is only %d bytes",
              pSize, mSize);
      pSize = mSize;
    }
    unsigned char *oldHead = mHead;
    mHead += pSize;
    mSize -= pSize;
    return oldHead;
  }
  // Add bytes to the end of the buffer
  unsigned int append(const unsigned char *pStorage, unsigned int pSize) {
    expand(pSize);
    std::memcpy(mTail, pStorage, pSize);
    mTail += pSize;
    mSize += pSize;
    return mSize;
  }
  unsigned int append(const char *pStorage, unsigned int pSize) {
    return append((const unsigned char *)pStorage, pSize);
  }
  unsigned int append(const Buffer &buffer) {
    return append(buffer.mHead, buffer.mSize);
  }
  void tidy(bool level=0) {
    if (mHead != mStorage) {
      if (mSize == 0) {
        mHead = mTail = mStorage;
//*mHead = '\0';
      } else if (level) {
        if (((uintptr_t)mHead-(uintptr_t)mStorage) > mSize) {
          std::memcpy(mStorage, mHead, mSize);
          mHead = mStorage;
          mTail = mHead + mSize;
        }
      }
    } else if (mSize == 0 && mHead) {
      *mHead = '\0';
    }
  }

  Buffer &operator=(const Buffer &buffer) {
    assign(buffer);
    return *this;
  }
  Buffer &operator+=(const Buffer &buffer) {
    append(buffer);
    return *this;
  }
  Buffer &operator+=(unsigned int count) {
    expand(count);
    return *this;
  }
  Buffer &operator-=(unsigned int count) {
    consume(count);
    return *this;
  }
  operator unsigned char *() const {
    return mHead;
  }
  operator char *() const {
    return reinterpret_cast<char *>(mHead);
  }
  unsigned char *operator+(int offset) const {
    return (unsigned char *)(mHead+offset);
  }
  unsigned char operator[](int index) const {
    return *(mHead+index);
  }
  operator int () const {
    return static_cast<int>(mSize);
  }
  int read_into(int sd, unsigned int bytes);
  int read_into(int sd, unsigned int bytes, Microseconds timeout);
};

#endif // ZM_BUFFER_H
