/*
 * Copyright 2019 by its authors. See AUTHORS.
 *
 * 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.
 */
#ifndef EINA_CLONE_ALLOCATORS_HH_
#define EINA_CLONE_ALLOCATORS_HH_

#include <eina_workarounds.hh>

#include <Eo.h>

#include <memory>
#include <cstring>
#include <cstdlib>
#include <type_traits>

/**
 * @addtogroup Eina_Cxx_Containers_Group
 *
 * @{
 */

namespace efl { namespace eina {

/**
 * @defgroup Eina_Cxx_Clone_Allocators_Group Clone Allocators
 * @ingroup Eina_Cxx_Containers_Group
 *
 * Clone allocators is a formalized way to pointer containers control
 * the memory of the stored objects, allowing users to apply custom
 * allocators/deallocators for the cloned objects.
 *
 * @{
 */

/*
 * @internal
 */
struct default_clone_allocator_placeholder;
    
/**
 * This allocator creates copies of objects on the heap, calling their
 * copy constructor to make then equivalent to the given reference.
 *
 * The created objects are released with the default delete.
 */
struct heap_copy_allocator
{
  template <typename T>
  static T* allocate_clone(T const& v)
  {
    return new T(v);
  }

  template <typename T>
  static void deallocate_clone(T* p)
  {
#ifdef EFL_EINA_CXX11
    std::default_delete<T>()(p);
#else
    delete p;
#endif
  }
};

/**
 * This allocator creates copies of Eo classes through eo_ref
 *
 * The created objects are released using eo_unref
 */
struct eo_clone_allocator
{
  static Eo* allocate_clone(Eo const& v)
  {
    return ::efl_ref(&v);
  }

  static void deallocate_clone(Eo const* p)
  {
    ::efl_unref(const_cast<Eo*>(p));
  }
};

/**
 * This allocator allows users to create custom allocation schemes by
 * overloading the <tt>new_clone(T const& v)</tt> and
 * <tt>delete_clone(T* p)</tt> functions.
 */
struct heap_clone_allocator
{
  template <typename T>
  static T* allocate_clone(T const& v)
  {
    return new_clone(v);
  }
  template <typename T>
  static void deallocate_clone(T* p)
  {
    delete_clone(p);
  }
};

/**
 * This allocator does not allocate or deallocate anything. It simple
 * gets non-const-qualified pointers for objects, which allow
 * containers to hold elements without having ownership on them.
 *
 * It is commonly used to create a pointer container that is a view into
 * another existing container.
 */
struct view_clone_allocator
{
  template <typename T>
  static T* allocate_clone(T const& v)
  {
    return const_cast<T*>(&v);
  }
  template <typename T>
  static void deallocate_clone(T*)
  {
  }
};

/**
 * This allocator does not define an @c allocate_clone member function,
 * so it should be used to disable operations that require elements to
 * be cloned.
 */
struct heap_no_copy_allocator
{
  static void deallocate_clone(_Elm_Calendar_Mark const volatile*) {}
  static void deallocate_clone(_Elm_Calendar_Mark volatile*) {}
  static void deallocate_clone(_Elm_Calendar_Mark const*) {}
  static void deallocate_clone(_Elm_Calendar_Mark*) {}
  static void deallocate_clone(Elm_Gen_Item const volatile*) {}
  static void deallocate_clone(Elm_Gen_Item volatile*) {}
  static void deallocate_clone(Elm_Gen_Item const*) {}
  static void deallocate_clone(Elm_Gen_Item*) {}
  static void deallocate_clone(_Elm_Map_Overlay const volatile*) {}
  static void deallocate_clone(_Elm_Map_Overlay volatile*) {}
  static void deallocate_clone(_Elm_Map_Overlay const*) {}
  static void deallocate_clone(_Elm_Map_Overlay*) {}
  template <typename T>
  static void deallocate_clone(T* p)
  {
#ifdef EFL_EINA_CXX11
    std::default_delete<T>()(p);
#else
    delete p;
#endif
  }
};

/**
 * Manages allocation and deallocation of memory using the function
 * @c malloc and @c free. This allocator does not calls constructors,
 * the content of the newly allocated objects are assigned using
 * @c memcpy, so it has to be used with types that have
 * <em>standard-layout</em>.
 */
struct malloc_clone_allocator
{
  template <typename T>
  static T* allocate_clone(T const& v)
  {
    static_assert(std::is_pod<T>::value, "malloc_clone_allocator can only be used with POD types");
    T* p = static_cast<T*>(std::malloc(sizeof(T)));
    std::memcpy(p, &v, sizeof(T));
    return p;
  }

  template <typename T>
  static void deallocate_clone(T const* p)
  {
    static_assert(std::is_pod<T>::value || std::is_void<T>::value
                  , "malloc_clone_allocator can only be used with POD types");
    std::free(const_cast<T*>(p));
  }
};

/**
 * @internal
 */
template <typename A>
struct clone_allocator_deleter
{
  template <typename T>
  void operator()(T* object) const
  {
    A::deallocate_clone(object);
  }
};

/**
 * @}
 */

} }

/**
 * @}
 */

#endif
