/*
 *
 *    Copyright (c) 2020-2021 Project CHIP Authors
 *    All rights reserved.
 *
 *    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.
 */

/**
 *    @file
 *      This file defines heap memory allocation APIs for CHIP.
 *
 */

#pragma once

#include <lib/core/CHIPError.h>
#include <stdlib.h>

#include <memory>
#include <new>
#include <utility>

namespace chip {
namespace Platform {

#define CHIP_ZERO_AT(value)                                                                                                        \
    do                                                                                                                             \
    {                                                                                                                              \
        memset(&value, 0, sizeof(value));                                                                                          \
    } while (0)

/**
 * This function is called by CHIP layer to initialize memory and resources
 * required for proper functionality of the CHIP memory allocator.
 * This function is platform specific and might be empty in certain cases.
 * For example, this function is doing nothing when the C Standard Library malloc()
 * and free() functions are used for memory allocation.
 *
 * @param[in]  buf      A pointer to a dedicated memory buffer, which should be used as
 *                      a memory pool for CHIP memory allocation.
 *                      This input is optional (defaults to NULL) and shouldn't be used
 *                      if a dedicated memory buffer is not used.
 *
 * @param[in]  bufSize  Size of a dedicated memory buffer. This input is optional (defaults to 0)
 *                      and shouldn't be used if dedicated memory buffer is not used.
 *                      When a dedicated memory buffer is used the function checks and
 *                      generates an error if buffer size is not big enough to support
 *                      CHIP use cases.
 *
 * @retval  #CHIP_ERROR_BUFFER_TOO_SMALL  If dedicated input buffer size is not sufficient
 *                                         to support CHIP use cases.
 * @retval  #CHIP_NO_ERROR                On success.
 * @retval  other                          An error generated by platform-specific memory
 *                                         initialization function.
 *
 */
extern CHIP_ERROR MemoryInit(void * buf = nullptr, size_t bufSize = 0);

/**
 * This function is called by the CHIP layer to releases all resources that were allocated
 * by MemoryInit() function.
 * This function can be an empty call if there is no need to release resources. For example,
 * this is the case when the C Standard Library malloc() and free() functions are used
 * for memory allocation.
 *
 */
extern void MemoryShutdown();

/**
 * This function is called by the CHIP layer to allocate a block of memory of "size" bytes.
 *
 * @param[in]  size             Specifies requested memory size in bytes.
 *
 * @retval  Pointer to a memory block in case of success.
 * @retval  NULL-pointer if memory allocation fails.
 *
 */
extern void * MemoryAlloc(size_t size);

/**
 * This function is called by the CHIP layer to allocate a block of memory for an array of num
 * elements, each of them size bytes long, and initializes all its bits to zero.
 * The effective result is the allocation of a zero-initialized memory block of (num*size) bytes.
 *
 * @param[in]  num              Specifies number of elements to allocate.
 * @param[in]  size             Specifies size of each element in bytes.
 *
 * @retval  Pointer to a memory block in case of success.
 * @retval  NULL-pointer if memory allocation fails.
 *
 */
extern void * MemoryCalloc(size_t num, size_t size);

/**
 * This function is called by the Chip layer to change the size of the memory block pointed to by p.
 * The function may move the memory block to a new location (whose address is returned by the function).
 * The content of the memory block is preserved up to the lesser of the new and old sizes, even if the
 * block is moved to a new location. If the new size is larger, the value of the newly allocated portion
 * is indeterminate.

 * In case that p is a null pointer, the function behaves like malloc, assigning a new block of size bytes
 * and returning a pointer to its beginning.
 *
 * @param[in]  p                Pointer to a memory block previously allocated with MemoryAlloc, MemoryCalloc
 *                              or MemoryRealloc.
 * @param[in]  size             Specifies new size for the memory block, in bytes..
 *
 * @retval  Pointer to a memory block in case of success.
 * @retval  NULL-pointer if memory allocation fails.
 *
 */
extern void * MemoryRealloc(void * p, size_t size);

/**
 * This function is called by the Chip layer to release a memory block allocated by
 * the MemoryAlloc(), MemoryCalloc or MemoryRealloc.
 * @param[in]  p                Pointer to a memory block that should be released.
 *
 */
extern void MemoryFree(void * p);

/**
 * This function wraps the operator `new` with placement-new using MemoryAlloc().
 * Instead of
 *    p = new T(arguments)
 * use
 *    p = New<T>(arguments)
 * In a few cases it may be necessary to add explicit casts to arguments, notably
 * when passing integer constants to smaller integer parameters.
 */
template <typename T, typename... Args>
inline T * New(Args &&... args)
{
    void * p = MemoryAlloc(sizeof(T));
    if (p != nullptr)
    {
        return new (p) T(std::forward<Args>(args)...);
    }
    return nullptr;
}

/**
 * This function wraps the operator `delete` with using MemoryFree().
 * Instead of
 *    delete p
 * use
 *    Delete(p)
 */
template <typename T>
inline void Delete(T * p)
{
    if (p == nullptr)
    {
        return;
    }

    p->~T();
    MemoryFree(p);
}

template <typename T>
struct Deleter
{
    void operator()(T * p) { Delete(p); }
};

template <typename T>
using UniquePtr = std::unique_ptr<T, Deleter<T>>;

template <typename T, typename... Args>
inline UniquePtr<T> MakeUnique(Args &&... args)
{
    return UniquePtr<T>(New<T>(std::forward<Args>(args)...));
}

template <typename T>
using SharedPtr = std::shared_ptr<T>;

template <typename T, typename... Args>
inline SharedPtr<T> MakeShared(Args &&... args)
{
    return SharedPtr<T>(New<T>(std::forward<Args>(args)...), Deleter<T>());
}

template <typename T>
using WeakPtr = std::weak_ptr<T>;

// See MemoryDebugCheckPointer().
extern bool MemoryInternalCheckPointer(const void * p, size_t min_size);

/**
 * In debug builds, test the validity of a pointer obtained from a chip::Platform memory allocation.
 *
 * @param[in]  p                Pointer to a memory block previously allocated with MemoryAlloc, MemoryCalloc,
 *                              MemoryRealloc, or New, and not freed.
 * @param[in]  min_size         Gives a size that the allocated block is expected to be able to hold.
 *
 * @e Unless configured with #CHIP_CONFIG_MEMORY_DEBUG_CHECKS, this function returns `true` without performing
 * any check, inlined with the expectation that the compiler can remove any associated failure code.
 *
 * With #CHIP_CONFIG_MEMORY_DEBUG_CHECKS enabled:
 *
 * This function is guaranteed to return `false` if \a p is `nullptr`. The function returns `true` if \a p is a valid
 * pointer to an allocation *and* the implementation memory manager is in a fully functioning state.
 *
 * @note For non-null \a p, the function *may* return `true` even if the pointer is invalid. That is, a particular
 *       implementation or configuration is not guaranteed to catch any particular faulty state.
 * @note For non-null \a p, the function return value *may* be incorrect if the memory manager is in a faulty state
 *       (e.g. corrupt heap), even if the faulty state does not directly involve \a p.
 * @note For non-null \a p, the function *may* abort the program rather than return at all if the memory manager is in
 *       a faulty state, even if \a p is valid.
 * @note For a non-null \a p, checking *may* be slow.
 *
 *
 * @return  An implementation- and configuration-defined estimate of whether \a p is a valid allocated pointer.
 */
inline bool MemoryDebugCheckPointer(const void * p, size_t min_size = 0)
{
#if CHIP_CONFIG_MEMORY_DEBUG_CHECKS
    return MemoryInternalCheckPointer(p, min_size);
#else  // CHIP_CONFIG_MEMORY_DEBUG_CHECKS
    return true;
#endif // CHIP_CONFIG_MEMORY_DEBUG_CHECKS
}

} // namespace Platform
} // namespace chip
