/* StarPU --- Runtime system for heterogeneous multicore architectures.
 *
 * Copyright (C) 2010,2011 University of Bordeaux
 *
 * StarPU is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * StarPU 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 Lesser General Public License in COPYING.LGPL for more details.
 */

#include "socl.h"

static void release_callback_memobject(void * e) {
  cl_mem mem = (cl_mem)e;

  /* Release references */
  gc_entity_unstore(&mem->context);

  //Delete this mem_object from the mem_object list
  mem_object_release(mem);

  /* Destruct object */
  starpu_data_unregister_submit(mem->handle);

  if (!(mem->flags & CL_MEM_USE_HOST_PTR))
    free(mem->ptr);
}


/**
 * \brief Create a buffer
 *
 * A buffer has always an allocated region in host memory. If CL_MEM_USE_HOST_PTR
 * is set, we use memory pointed by host_ptr, otherwise some host memory is
 * allocated.
 *
 * If CL_MEM_USE_HOST_PTR or CL_MEM_ALLOC_HOST_PTR are set, memory pointed by host_ptr
 * is not coherent. To enforce coherency, you have to map the buffer (clEnqueueMapBuffer).
 * 
 * If CL_MEM_COPY_HOST_PTR is set, the buffer will be duplicated in host memory. You
 * should avoid it.
 *
 */
CL_API_ENTRY cl_mem CL_API_CALL
soclCreateBuffer(cl_context   context,
               cl_mem_flags flags,
               size_t       size,
               void *       host_ptr,
               cl_int *     errcode_ret) CL_API_SUFFIX__VERSION_1_0
{
   cl_mem mem;

   if (errcode_ret != NULL)
      *errcode_ret = CL_SUCCESS;

   //Check flags
   if (((flags & CL_MEM_READ_ONLY) && (flags & CL_MEM_WRITE_ONLY))
      || ((flags & CL_MEM_READ_WRITE) && (flags & CL_MEM_READ_ONLY))
      || ((flags & CL_MEM_READ_WRITE) && (flags & CL_MEM_WRITE_ONLY))
      || ((flags & CL_MEM_USE_HOST_PTR) && (flags & CL_MEM_ALLOC_HOST_PTR))
      || ((flags & CL_MEM_USE_HOST_PTR) && (flags & CL_MEM_COPY_HOST_PTR))) {
      if (errcode_ret != NULL)
         *errcode_ret = CL_INVALID_VALUE;
      return NULL;
   }

   if (size == 0) {
      if (errcode_ret != NULL)
         *errcode_ret = CL_INVALID_BUFFER_SIZE;
      return NULL;
   }

   if ((host_ptr == NULL && (flags & (CL_MEM_USE_HOST_PTR | CL_MEM_COPY_HOST_PTR)))
      || (host_ptr != NULL && !(flags & (CL_MEM_USE_HOST_PTR | CL_MEM_COPY_HOST_PTR)))) {
      if (errcode_ret != NULL)
         *errcode_ret = CL_INVALID_HOST_PTR;
      return NULL;
   }


   //Alloc cl_mem structure
   mem = (cl_mem)gc_entity_alloc(sizeof(struct _cl_mem), release_callback_memobject, "buffer");
   if (mem == NULL) {
      if (errcode_ret != NULL)
         *errcode_ret = CL_OUT_OF_HOST_MEMORY;
      return NULL;
   }
   
   mem->ptr = NULL;
   mem->map_count = 0;
   gc_entity_store(&mem->context, context);
   mem->flags = flags;
   mem->size = size;
   mem->host_ptr = host_ptr;

   #ifdef DEBUG
   static int id = 0;
   mem->id = id++;
   #endif

   mem_object_store(mem);

   //TODO: we shouldn't allocate the buffer ourselves. StarPU allocates it if a NULL pointer is given

   // If not MEM_USE_HOST_PTR, we need to alloc the buffer ourselves
   if (!(flags & CL_MEM_USE_HOST_PTR)) {
      mem->ptr = valloc(size);
      if (mem->ptr == NULL) {
         if (errcode_ret != NULL)
            *errcode_ret = CL_MEM_OBJECT_ALLOCATION_FAILURE;
         free(mem);
         return NULL;
      }
      //The buffer doesn't contain meaningful data
      mem->scratch = 1;
   }
   else {
      //The buffer may contain meaningful data
      mem->scratch = 0;
      mem->ptr = host_ptr;
   }

   // Access mode
   mem->mode = flags & CL_MEM_READ_ONLY  ? CL_MEM_READ_ONLY :
               flags & CL_MEM_WRITE_ONLY ? CL_MEM_WRITE_ONLY : CL_MEM_READ_WRITE;

   // Perform data copy if necessary
   if (flags & CL_MEM_COPY_HOST_PTR)
      memcpy(mem->ptr, host_ptr, size);
   
   // Create StarPU buffer (on home node? what's this?)
   starpu_variable_data_register(&mem->handle, STARPU_MAIN_RAM, (uintptr_t)mem->ptr, size); 

   DEBUG_MSG("[Buffer %d] Initialized (cl_mem %p handle %p)\n", mem->id, mem, mem->handle);
   
   return mem;
}
