/*
 * Copyright (c) 2020 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * 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.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _TD_UTIL_BUFFER_H_
#define _TD_UTIL_BUFFER_H_

#include "os.h"

#ifdef __cplusplus
extern "C" {
#endif

////////////////////////////////////////////////////////////////////////////////
// usage example
/*
#include <stdio.h>
#include "texception.h"

int32_t main( int32_t argc, char** argv ) {
  SBufferWriter bw = tbufInitWriter( NULL, false );

  TRY( 1 ) {
    //--------------------- write ------------------------
    // reserve 1024 bytes for the buffer to improve performance
    tbufEnsureCapacity( &bw, 1024 );

    // reserve space for the interger count
    size_t pos = tbufReserve( &bw, sizeof(int32_t) );
    // write 5 integers to the buffer
    for( int32_t i = 0; i < 5; i++) {
      tbufWriteInt32( &bw, i );
    }
    // write the integer count to buffer at reserved position
    tbufWriteInt32At( &bw, pos, 5 );

    // write a string to the buffer
    tbufWriteString( &bw, "this is a string.\n" );
    // acquire the result and close the write buffer
    size_t size = tbufTell( &bw );
    char*  data = tbufGetData( &bw, false );

    //------------------------ read -----------------------
    SBufferReader br = tbufInitReader( data, size, false );
    // read & print out all integers
    int32_t count = tbufReadInt32( &br );
    for( int32_t i = 0; i < count; i++ ) {
      printf( "%d\n", tbufReadInt32(&br) );
    }
    // read & print out a string
    puts( tbufReadString(&br, NULL) );
    // try read another integer, this result in an error as there no this integer
    tbufReadInt32( &br );
    printf( "you should not see this message.\n" );
  } CATCH( code ) {
    printf( "exception code is: %d, you will see this message after print out 5 integers and a string.\n", code );
  } END_TRY

  tbufCloseWriter( &bw );
  return 0;
}
*/

typedef struct SBufferReader {
  bool        endian;
  const char* data;
  size_t      pos;
  size_t      size;
} SBufferReader;

typedef struct SBufferWriter {
  bool   endian;
  char*  data;
  size_t pos;
  size_t size;
  void* (*allocator)(void*, size_t);
} SBufferWriter;

// common functions & macros for both reader & writer

#define tbufTell(buf) ((buf)->pos)

/* ------------------------ BUFFER WRITER FUNCTIONS AND MACROS ------------------------ */
// *Allocator*, function to allocate memory, will use 'realloc' if NULL
// *Endian*, if true, writer functions of primitive types will do 'hton' automatically
#define tbufInitWriter(Allocator, Endian) \
  { .endian = (Endian), .data = NULL, .pos = 0, .size = 0, .allocator = ((Allocator) == NULL ? realloc : (Allocator)) }

void   tbufCloseWriter(SBufferWriter* buf);
void   tbufEnsureCapacity(SBufferWriter* buf, size_t size);
size_t tbufReserve(SBufferWriter* buf, size_t size);
char*  tbufGetData(SBufferWriter* buf, bool takeOver);
void   tbufWrite(SBufferWriter* buf, const void* data, size_t size);
void   tbufWriteAt(SBufferWriter* buf, size_t pos, const void* data, size_t size);
void   tbufWriteStringLen(SBufferWriter* buf, const char* str, size_t len);
void   tbufWriteString(SBufferWriter* buf, const char* str);
// the prototype of tbufWriteBinary and tbufWrite are identical
// the difference is: tbufWriteBinary writes the length of the data to the buffer
// first, then the actual data, which means the reader don't need to know data
// size before read. Write only write the data itself, which means the reader
// need to know data size before read.
void tbufWriteBinary(SBufferWriter* buf, const void* data, size_t len);
void tbufWriteBool(SBufferWriter* buf, bool data);
void tbufWriteBoolAt(SBufferWriter* buf, size_t pos, bool data);
void tbufWriteChar(SBufferWriter* buf, char data);
void tbufWriteCharAt(SBufferWriter* buf, size_t pos, char data);
void tbufWriteInt8(SBufferWriter* buf, int8_t data);
void tbufWriteInt8At(SBufferWriter* buf, size_t pos, int8_t data);
void tbufWriteUint8(SBufferWriter* buf, uint8_t data);
void tbufWriteUint8At(SBufferWriter* buf, size_t pos, uint8_t data);
void tbufWriteInt16(SBufferWriter* buf, int16_t data);
void tbufWriteInt16At(SBufferWriter* buf, size_t pos, int16_t data);
void tbufWriteUint16(SBufferWriter* buf, uint16_t data);
void tbufWriteUint16At(SBufferWriter* buf, size_t pos, uint16_t data);
void tbufWriteInt32(SBufferWriter* buf, int32_t data);
void tbufWriteInt32At(SBufferWriter* buf, size_t pos, int32_t data);
void tbufWriteUint32(SBufferWriter* buf, uint32_t data);
void tbufWriteUint32At(SBufferWriter* buf, size_t pos, uint32_t data);
void tbufWriteInt64(SBufferWriter* buf, int64_t data);
void tbufWriteInt64At(SBufferWriter* buf, size_t pos, int64_t data);
void tbufWriteUint64(SBufferWriter* buf, uint64_t data);
void tbufWriteUint64At(SBufferWriter* buf, size_t pos, uint64_t data);
void tbufWriteFloat(SBufferWriter* buf, float data);
void tbufWriteFloatAt(SBufferWriter* buf, size_t pos, float data);
void tbufWriteDouble(SBufferWriter* buf, double data);
void tbufWriteDoubleAt(SBufferWriter* buf, size_t pos, double data);

/* ------------------------ BUFFER READER FUNCTIONS AND MACROS ------------------------ */
// *Endian*, if true, reader functions of primitive types will do 'ntoh' automatically
#define tbufInitReader(Data, Size, Endian) \
  { .endian = (Endian), .data = (Data), .pos = 0, .size = ((Data) == NULL ? 0 : (Size)) }

size_t      tbufSkip(SBufferReader* buf, size_t size);
const char* tbufRead(SBufferReader* buf, size_t size);
void        tbufReadToBuffer(SBufferReader* buf, void* dst, size_t size);
const char* tbufReadString(SBufferReader* buf, size_t* len);
size_t      tbufReadToString(SBufferReader* buf, char* dst, size_t size);
const char* tbufReadBinary(SBufferReader* buf, size_t* len);
size_t      tbufReadToBinary(SBufferReader* buf, void* dst, size_t size);
bool        tbufReadBool(SBufferReader* buf);
char        tbufReadChar(SBufferReader* buf);
int8_t      tbufReadInt8(SBufferReader* buf);
uint8_t     tbufReadUint8(SBufferReader* buf);
int16_t     tbufReadInt16(SBufferReader* buf);
uint16_t    tbufReadUint16(SBufferReader* buf);
int32_t     tbufReadInt32(SBufferReader* buf);
uint32_t    tbufReadUint32(SBufferReader* buf);
int64_t     tbufReadInt64(SBufferReader* buf);
uint64_t    tbufReadUint64(SBufferReader* buf);
float       tbufReadFloat(SBufferReader* buf);
double      tbufReadDouble(SBufferReader* buf);

#ifdef __cplusplus
}
#endif

#endif /*_TD_UTIL_BUFFER_H_*/
