/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#undef MONGOC_LOG_DOMAIN
#define MONGOC_LOG_DOMAIN "gridfs_file_page"

#include <mongoc/mongoc-gridfs-file-page.h>

#include <mongoc/mongoc-gridfs-file-page-private.h>
#include <mongoc/mongoc-trace-private.h>

#include <mlib/ckdint.h>
#include <mlib/cmp.h>


/** create a new page from a buffer
 *
 * The buffer should stick around for the life of the page
 */
mongoc_gridfs_file_page_t *
_mongoc_gridfs_file_page_new(const uint8_t *data, uint32_t len, uint32_t chunk_size)
{
   mongoc_gridfs_file_page_t *page;

   ENTRY;

   BSON_ASSERT(data);
   BSON_ASSERT(len <= chunk_size);

   page = (mongoc_gridfs_file_page_t *)bson_malloc0(sizeof *page);

   page->chunk_size = chunk_size;
   page->read_buf = data;
   page->len = len;

   RETURN(page);
}


bool
_mongoc_gridfs_file_page_seek(mongoc_gridfs_file_page_t *page, uint32_t offset)
{
   ENTRY;

   BSON_ASSERT(page);

   page->offset = offset;

   RETURN(1);
}


int32_t
_mongoc_gridfs_file_page_read(mongoc_gridfs_file_page_t *page, void *dst, uint32_t len)
{
   uint32_t bytes_read;
   const uint8_t *src;

   ENTRY;

   BSON_ASSERT(page);
   BSON_ASSERT(dst);

   if (page->offset > page->len) {
      RETURN(-1);
   }

   bytes_read = BSON_MIN(len, page->len - page->offset);

   src = page->read_buf ? page->read_buf : page->buf;

   memcpy(dst, src + page->offset, bytes_read);

   if (mlib_add(&page->offset, bytes_read)) {
      return -1;
   }

   if (!mlib_in_range(int32_t, bytes_read)) {
      RETURN(-1);
   }

   RETURN((int32_t)bytes_read);
}


/**
 * _mongoc_gridfs_file_page_write:
 *
 * Write to a page.
 *
 * Writes are copy-on-write with regards to the buffer that was passed to the
 * mongoc_gridfs_file_page_t during construction. In other words, the first
 * write allocates a large enough buffer for file->chunk_size, which becomes
 * authoritative from then on.
 *
 * A write of zero bytes will trigger the copy-on-write mechanism.
 */
int32_t
_mongoc_gridfs_file_page_write(mongoc_gridfs_file_page_t *page, const void *src, uint32_t len)
{
   uint32_t bytes_written;

   ENTRY;

   BSON_ASSERT(page);
   BSON_ASSERT(src);

   if (page->offset > page->chunk_size) {
      return -1;
   }

   bytes_written = BSON_MIN(len, page->chunk_size - page->offset);

   if (!page->buf) {
      page->buf = (uint8_t *)bson_malloc(page->chunk_size);
      memcpy(page->buf, page->read_buf, BSON_MIN(page->chunk_size, page->len));
   }

   /* Copy bytes and adjust the page position */
   memcpy(page->buf + page->offset, src, bytes_written);
   if (mlib_add(&page->offset, bytes_written)) {
      return -1;
   }
   page->len = BSON_MAX(page->offset, page->len);

   /* Don't use the old read buffer, which is no longer current */
   page->read_buf = page->buf;

   if (!mlib_in_range(int32_t, bytes_written)) {
      RETURN(-1);
   }

   RETURN((int32_t)bytes_written);
}


/**
 * _mongoc_gridfs_file_page_memset0:
 *
 *      Write zeros to a page, starting from the page's current position. Up to
 *      `len` bytes will be set to zero or until the page is full, whichever
 *      comes first.
 *
 *      Like _mongoc_gridfs_file_page_write, operations are copy-on-write with
 *      regards to the page buffer.
 *
 * Returns:
 *      Number of bytes set.
 */
int32_t
_mongoc_gridfs_file_page_memset0(mongoc_gridfs_file_page_t *page, uint32_t len)
{
   uint32_t bytes_set;

   ENTRY;

   BSON_ASSERT(page);

   if (page->offset > page->chunk_size) {
      return -1;
   }

   bytes_set = BSON_MIN(page->chunk_size - page->offset, len);

   if (!page->buf) {
      page->buf = (uint8_t *)bson_malloc0(page->chunk_size);
      memcpy(page->buf, page->read_buf, BSON_MIN(page->chunk_size, page->len));
   }

   /* Set bytes and adjust the page position */
   memset(page->buf + page->offset, '\0', bytes_set);
   if (mlib_add(&page->offset, bytes_set)) {
      return -1;
   }
   page->len = BSON_MAX(page->offset, page->len);

   /* Don't use the old read buffer, which is no longer current */
   page->read_buf = page->buf;

   if (!mlib_in_range(int32_t, bytes_set)) {
      RETURN(-1);
   }

   RETURN((int32_t)bytes_set);
}


const uint8_t *
_mongoc_gridfs_file_page_get_data(mongoc_gridfs_file_page_t *page)
{
   ENTRY;

   BSON_ASSERT(page);

   RETURN(page->buf ? page->buf : page->read_buf);
}


uint32_t
_mongoc_gridfs_file_page_get_len(mongoc_gridfs_file_page_t *page)
{
   ENTRY;

   BSON_ASSERT(page);

   RETURN(page->len);
}


uint32_t
_mongoc_gridfs_file_page_tell(mongoc_gridfs_file_page_t *page)
{
   ENTRY;

   BSON_ASSERT(page);

   RETURN(page->offset);
}


bool
_mongoc_gridfs_file_page_is_dirty(mongoc_gridfs_file_page_t *page)
{
   ENTRY;

   BSON_ASSERT(page);

   RETURN(page->buf ? 1 : 0);
}


void
_mongoc_gridfs_file_page_destroy(mongoc_gridfs_file_page_t *page)
{
   ENTRY;

   if (page->buf) {
      bson_free(page->buf);
   }

   bson_free(page);

   EXIT;
}
