/**************************************************************************
 * Copyright (C) 2017-2017  Unisound
 *
 * 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
 * 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.
 **************************************************************************
 *
 * Description : databuf.c
 * Author      : wufangfang@unisound.com
 * Date        : 2019.08.08
 *
 **************************************************************************/
#include <autoconf.h>
#include <stdio.h>
#include <types.h>

#include "databuf.h"
#include "fakelibc.h"

#define DATA_BUF_ATTR_READ       (1 << 0)
#define DATA_BUF_ATTR_SYNC       (1 << 1)
#define DATA_BUF_ATTR_WRITE      (1 << 2)


typedef int DataBufferAttr;

typedef enum {
  DATABUF_MODE_NORMAL = 0,
  DATABUF_MODE_EXT_BUF,
  DATABUF_MODE_INVALID
}DataBufMode;

typedef struct {
  int             size;
  char            *buf;
  int             head, tail;
  int             data_size;
  DataBufMode     mode;
} DataBuf;

static DataBuf *_create_data_buf(void) {
  DataBuf *data_buf = (DataBuf *)uni_malloc(sizeof(DataBuf));
  data_buf->head = data_buf->tail = 0;
  data_buf->data_size = 0;
  return data_buf;
}

static int _get_data_size(DataBuf *data_buf) {
  return data_buf->data_size;
}

static int _get_free_size(DataBuf *data_buf) {
  return data_buf->size - _get_data_size(data_buf);
}

static void _init_data_buf(void *data_buf, int size) {
  DataBuf *buf = (DataBuf *)data_buf;
  buf->size = size;
  buf->buf = (char *)uni_malloc(size);
  buf->head = buf->tail = 0;
  buf->data_size = 0;
  buf->mode = DATABUF_MODE_NORMAL;
}

static void _init_data_buf_with_ready_buf(void *data_buf,
                                                    char *buf_rdy, int size) {
  DataBuf *buf = (DataBuf *)data_buf;
  buf->size = size;
  buf->buf = buf_rdy;
  buf->head = buf->tail = 0;
  buf->data_size = 0;
  buf->mode = DATABUF_MODE_EXT_BUF;
}

static inline int _read_attr_setted(DataBufferAttr attr) {
  return ((attr & DATA_BUF_ATTR_READ) == 0 ? 0 : 1);
}

static inline int _write_attr_setted(DataBufferAttr attr) {
  return ((attr & DATA_BUF_ATTR_WRITE) == 0 ? 0 : 1);
}

static inline int _sync_attr_setted(DataBufferAttr attr) {
  return ((attr & DATA_BUF_ATTR_SYNC) == 0 ? 0 : 1);
}

static inline int _read_length_overflow(DataBuf *data_buf, int readlen) {
  return _get_data_size(data_buf) < readlen;
}

static inline int _write_length_overflow(DataBuf *data_buf, int writelen) {
  return _get_free_size(data_buf) < writelen;
}

static int _read_internal(DataBuf *data_buf, char *buf, int size,
                          DataBufferAttr attr) {
  int pos;
  pos = (data_buf->tail + size) % data_buf->size;
  if (_read_attr_setted(attr)) {
    if (pos >= data_buf->tail) {
      memcpy(buf, data_buf->buf + data_buf->tail, size);
    } else {
      int remain = (data_buf->size - data_buf->tail);
      memcpy(buf, data_buf->buf + data_buf->tail, remain);
      memcpy(buf + remain, data_buf->buf, size - remain);
    }
  }
  if (_sync_attr_setted(attr)) {
    data_buf->tail = pos;
    data_buf->data_size -= size;
  }
  return size;
}

static int _write_internal(DataBuf *data_buf, const char *buf, int size,
                                DataBufferAttr attr) {
  int pos;
  pos = (data_buf->head + size) % data_buf->size;
  if (_write_attr_setted(attr)) {
    if (pos >= data_buf->head) {
      memcpy(data_buf->buf + data_buf->head, buf, size);
    } else {
      int remain = (data_buf->size - data_buf->head);
      memcpy(data_buf->buf + data_buf->head, buf, remain);
      memcpy(data_buf->buf, buf + remain, size - remain);
    }
  }
  if (_sync_attr_setted(attr)) {
    data_buf->head = pos;
    data_buf->data_size += size;
  }
  return size;
}

static int _copy_internal(DataBuf *dst, DataBuf *src, int size,
                          DataBufferAttr attr) {
  int src_pos = (src->tail + size) % src->size;
  if (src_pos > src->tail) {
    _write_internal(dst, src->buf + src->tail, size,
                    DATA_BUF_ATTR_WRITE | DATA_BUF_ATTR_SYNC);
  } else {
    int remain = src->size - src->tail;
    _write_internal(dst, src->buf + src->tail, remain,
                    DATA_BUF_ATTR_WRITE | DATA_BUF_ATTR_SYNC);
    _write_internal(dst, src->buf, size - remain,
                    DATA_BUF_ATTR_WRITE | DATA_BUF_ATTR_SYNC);
  }
  if (_sync_attr_setted(attr)) {
    src->tail = src_pos;
    src->data_size -= size;
  }
  return size;
}

DataBufHandle DataBufferCreate(int size) {
  DataBuf *data_buf;
  if (size <= 0) {
    return (DataBufHandle)NULL;
  }
  data_buf = _create_data_buf();
  _init_data_buf(data_buf, size);
  return (DataBufHandle)data_buf;
}

DataBufHandle DataBufferCreateWithBuffer(char *buf, int size) {
  DataBuf *data_buf;
  if ((size <= 0) || (NULL == buf)) {
    return (DataBufHandle)NULL;
  }
  data_buf = _create_data_buf();
  _init_data_buf_with_ready_buf(data_buf, buf, size);
  return (DataBufHandle)data_buf;
}

int DataBufferDestroy(DataBufHandle handle) {
  DataBuf *data_buf = (DataBuf *)handle;
  if (DATABUF_MODE_NORMAL == data_buf->mode) {
    uni_free(data_buf->buf);
  }
  uni_free(data_buf);
  return 0;
}

static int _data_buffer_read(char *dst, int readlen, DataBuf *data_buf,
                             DataBufferAttr attr) {
  if (_read_length_overflow(data_buf, readlen)) {
    return -1;
  }
  return _read_internal(data_buf, dst, readlen, attr);
}

static int _data_buffer_write(DataBuf *data_buf, const char *src,
                                    int writelen, DataBufferAttr attr) {
  if (_write_length_overflow(data_buf, writelen)) {
    return -1;
  }
  return _write_internal(data_buf, src, writelen, attr);
}

int DataBufferClear(DataBufHandle handle) {
  DataBuf *data_buf = (DataBuf *)handle;
  data_buf->head = data_buf->tail = 0;
  data_buf->data_size = 0;
  return 0;
}

int DataBufferGetFreeSize(DataBufHandle handle) {
  DataBuf *data_buf = (DataBuf *)handle;
  return _get_free_size(data_buf);
}

int DataBufferGetDataSize(DataBufHandle handle) {
  DataBuf *data_buf = (DataBuf *)handle;
  return _get_data_size(data_buf);
}

int DataBufferPeek(char *dst, int readlen, DataBufHandle handle) {
  return _data_buffer_read(dst, readlen, (DataBuf *)handle, DATA_BUF_ATTR_READ);
}

int DataBufferWrite(DataBufHandle handle, const char *src, int writelen) {
  return _data_buffer_write((DataBuf *)handle, src, writelen,
                            DATA_BUF_ATTR_WRITE | DATA_BUF_ATTR_SYNC);
}

int DataBufferRead(char *dst, int readlen, DataBufHandle handle) {
  return _data_buffer_read(dst, readlen, (DataBuf *)handle,
                           DATA_BUF_ATTR_READ | DATA_BUF_ATTR_SYNC);
}

int DataBufferSkip(int readlen, DataBufHandle handle) {
  return _data_buffer_read(NULL, readlen, (DataBuf *)handle, DATA_BUF_ATTR_SYNC);
}

static void _invalidate_data_buf_for_write(DataBuf *data_buf,
                                                     int size) {
  if (_get_free_size(data_buf) < size) {
    printf("[DATABUF]force read out exist data %d\n", size);
    _data_buffer_read(NULL, size, data_buf, DATA_BUF_ATTR_SYNC);
  }
}

int DataBufferWriteForcely(DataBufHandle handle, const char *src,
                                  int writelen) {
  DataBuf *data_buf = (DataBuf *)handle;
  _invalidate_data_buf_for_write(data_buf, writelen);
  return _data_buffer_write(data_buf, src, writelen,
                            DATA_BUF_ATTR_WRITE | DATA_BUF_ATTR_SYNC);
}

static void _invalidate_data_size(DataBuf *data_buf, int *size) {
  int data_size = _get_data_size(data_buf);
  if (data_size < *size) {
    *size = data_size;
  }
}

static int _data_buffer_copy(DataBuf *data_dst, DataBuf *data_src,
                             int copylen, int forcely, DataBufferAttr attr) {
  if (forcely) {
    _invalidate_data_buf_for_write(data_dst, copylen);
  }
  return _copy_internal(data_dst, data_src, copylen, attr);
}

int DataBufferCopy(DataBufHandle dst, DataBufHandle src, int copylen, int forcely) {
  DataBuf *data_src = (DataBuf*)src;
  _invalidate_data_size(data_src, &copylen);
  return _data_buffer_copy((DataBuf*)dst, data_src, copylen, forcely, 0);
}

int DataBufferMove(DataBufHandle dst, DataBufHandle src, int movelen, int forcely) {
  DataBuf *data_src = (DataBuf*)src;
  _invalidate_data_size(data_src, &movelen);
  return _data_buffer_copy((DataBuf*)dst, data_src, movelen, forcely,
                           DATA_BUF_ATTR_SYNC);
}

int DataBufferCopyAll(DataBufHandle dst, DataBufHandle src, int forcely) {
  DataBuf *data_src = (DataBuf*)src;
  int size = _get_data_size(data_src);
  return _data_buffer_copy((DataBuf*)dst, data_src, size, forcely, 0);
}

int DataBufferMoveAll(DataBufHandle dst, DataBufHandle src, int forcely) {
  DataBuf *data_src = (DataBuf*)src;
  int size = _get_data_size(data_src);
  return _data_buffer_copy((DataBuf*)dst, data_src, size, forcely,
                           DATA_BUF_ATTR_SYNC);
}
