/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright (C) 2015-2021 m-privacy GmbH, Berlin
 * 
 * This 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 software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

//
// A MemOutStream grows as needed when data is written to it.
//

#ifndef __RDR_MEMOUTSTREAM_H__
#define __RDR_MEMOUTSTREAM_H__

#include <stdlib.h>
#include <rdr/Exception.h>

#include <rdr/OutStream.h>
#include <rfb/LogWriter.h>

#define MAXMEMSIZE (1 << 24)

static rfb::LogWriter mvlog("MemOutStream");

namespace rdr {

  class MemOutStream : public OutStream {

  public:

    MemOutStream(int len = 64*1024) : classMemLog(&mvlog) {
      classLog = &mvlog;
      if (len > MAXMEMSIZE)
        len = MAXMEMSIZE;
      else if (len < 1024)
        len = 1024;
      classMemLog->debug("inititializing buffer with size %u", len);
      memBuffer.size = len;
      memBuffer.used = 0;
      memBuffer.data = (U8 *) malloc(len);
      memBuffer.next = NULL;
      memBuffer.number = 0;
    }

    virtual ~MemOutStream() {
      free(memBuffer.data);
      memBuffer.data = NULL;
      memBuffer.size = 0;
     resetClassLog();
    }

    virtual void writeBytes(const void* data, size_t numBytes, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(numBytes, key, prio);
      memcpy(memBuffer.data + memBuffer.used, data, numBytes);
      memBuffer.used += numBytes;
    }

    virtual void writeU8(U8 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(1, key, prio);
      memBuffer.data[memBuffer.used++] = u;
    }

    virtual void writeU16(U16 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(2, key, prio);
      memBuffer.data[memBuffer.used++] = (U8) (u >> 8);
      memBuffer.data[memBuffer.used++] = (U8) u;
    }

    virtual void writeU32(U32 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(4, key, prio);
      memBuffer.data[memBuffer.used++] = (U8) (u >> 24);
      memBuffer.data[memBuffer.used++] = (U8) (u >> 16);
      memBuffer.data[memBuffer.used++] = (U8) (u >> 8);
      memBuffer.data[memBuffer.used++] = (U8) u;
    }

    // writeOpaqueN() writes a quantity without byte-swapping.
    virtual void writeOpaque8(U8 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(1, key, prio);
      memBuffer.data[memBuffer.used++] = u;
    }
    virtual void writeOpaque16(U16 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(2, key, prio);
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[0];
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[1];
    }
    virtual void writeOpaque32(U32 u, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(4, key, prio);
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[0];
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[1];
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[2];
      memBuffer.data[memBuffer.used++] = ((U8*)&u)[3];
    }

    // copyBytes() efficiently transfers data between streams
    virtual void copyBytes(InStream* is, int length, int key=0, QPrio prio=QPRIOMEDIUM) {
      check(length, key, prio);
      is->readBytes(memBuffer.data + memBuffer.used, length);
      memBuffer.used += length;
    }

    virtual void pad(size_t bytes, int key=0, QPrio prio=QPRIOMEDIUM) {
      if (bytes <= 0) {
#ifndef WIN32
        classMemLog->debug("pad(): called with bytes %zu, returning", bytes);
#endif
        return;
      }
      check(bytes, key, prio);
      while (bytes > 0) {
        memBuffer.data[memBuffer.used++] = 0;
        bytes--;
      }
    }

    virtual void skip(size_t bytes, int key=0, QPrio prio=QPRIOMEDIUM) {
      if (bytes <= 0) {
#ifndef WIN32
        classMemLog->debug("skip(): called with bytes %zu, returning", bytes);
#endif
        return;
      }
      check(bytes, key, prio);
      memBuffer.used += bytes;
    }

    virtual size_t length(int key=0, QPrio prio=QPRIOMEDIUM) {
      return memBuffer.used;
    }

    void clear(QPrio prio=QPRIOMEDIUM, bool zero=false) {
        if (zero)
          memset(memBuffer.data, 0, memBuffer.used);
        memBuffer.used = 0;
    }

    void clearAndZero(QPrio prio=QPRIOMEDIUM) {
      clear(prio, true);
    }

    virtual int bufferUsage(int key=0, QPrio prio=QPRIOMEDIUM) {
      return memBuffer.used;
    }

    void reposition(int pos, QPrio prio=QPRIOMEDIUM) {
      if (pos < 0 || pos > MAXMEMSIZE / 2) {
        classMemLog->error("reposition(): invalid pos");
        return;
      }
      if (pos > (int) memBuffer.size) {
        classMemLog->status("reposition(): resize buffer %p from %u to %u, this can lead to crashes", &memBuffer, memBuffer.size, pos * 3 / 2);
        U8 * newdata = (U8 *) realloc(memBuffer.data, pos * 3 / 2);
        if (!newdata) {
          classMemLog->error("reposition(): failed to realloc");
          return;
        }
        memBuffer.size = pos * 2;
        memBuffer.data = newdata;
      }
      memBuffer.used = pos;
    }

    // data() returns a pointer to the buffer.

    const void* data(QPrio prio=QPRIOMEDIUM) {
      return (const void*)memBuffer.data;
    }

    // avail() returns the number of bytes that can currently be written to the
    // stream without any risk of blocking.
    virtual size_t avail(int key=0, QPrio prio=QPRIOMEDIUM)
    {
      return memBuffer.size - memBuffer.used;
    }


    U8* getptr(size_t length, QPrio prio=QPRIOMEDIUM) {
      check(length);
      return memBuffer.data + memBuffer.used;
    }

    U8* getend(QPrio prio=QPRIOMEDIUM) {
      return memBuffer.data + memBuffer.size;
    }

    void setptr(size_t length, int key=0, QPrio prio=QPRIOMEDIUM) {
      reposition(memBuffer.used + length, prio);
    }

  protected:

    // overrun() either doubles the buffer or adds enough space for nItems of
    // size itemSize bytes.

    virtual void overrun(size_t needed, int key=0, QPrio prio=QPRIOMEDIUM) {
      unsigned len = memBuffer.used + needed;

      if (len > MAXMEMSIZE) {
        throw Exception("MemOutStream overrun: requested size of "
                        "%lu bytes exceeds maximum of %lu bytes",
                        (long unsigned)len,
                        (long unsigned)MAXMEMSIZE);
      }

      if (len < memBuffer.size * 3 / 2)
        len = memBuffer.size * 3 / 2;
      if (len > MAXMEMSIZE) {
        len = MAXMEMSIZE;
        classMemLog->error("overrun(): max size exceeded, reducing to %u", MAXMEMSIZE);
      }

      classMemLog->status("overrun(): resize buffer %p from %u to %u, this can lead to crashes", &memBuffer, memBuffer.size, len);
      U8 * newdata = (U8 *) realloc(memBuffer.data, len);
      if (!newdata) {
        classMemLog->error("overrun(): failed to realloc");
      } else {
        memBuffer.data = newdata;
        memBuffer.size = len;
      }
    }

    virtual void check(size_t length, int key=0, QPrio prio=QPRIOMEDIUM) {
      if (length > memBuffer.size - memBuffer.used)
        overrun(length, key, prio);
    }

    struct queueBuffer * getQueueBuffer(U32 size, QPrio prio=QPRIOMEDIUM) {
//      classMemLog->debug("getQueueBuffer(): not available in MemOutStream");
      return NULL;
    }

    void returnQueueBuffer(struct queueBuffer * buffer, QPrio prio=QPRIOMEDIUM, time_t now=0) {
      classMemLog->debug("returnQueueBuffer(): not available in MemOutStream");
      return;
    }

    bool submitQueueBuffer(struct queueBuffer * firstBuffer, struct queueBuffer * lastBuffer, int key=0, QPrio prio=QPRIOMEDIUM) {
      classMemLog->debug("submitQueueBuffer(): not available in MemOutStream");
      return false;
    }

    struct queueBuffer * popBuffer (QPrio prio) {
      classMemLog->debug("popBuffer(): not available in MemOutStream");
      return NULL;
    }

    void pushBuffer (struct queueBuffer * buffer, QPrio prio) {
      classMemLog->debug("pushBuffer(): not available in MemOutStream");
    }

    void resetClassMemLog() { classMemLog = &mvlog; }

    struct queueBuffer memBuffer;

    rfb::LogWriter * classMemLog;
  };

}

#endif
