#pragma once
#include <cstdlib>
#include <vector>
#include <functional>

#include "TemplateParameters.h"

enum class buf_choose_policy
{
    naiv,
    sorted_vec,
    find_best_unsorted
};

template< MemType mem >
class buffer
{
private:
    void *ptr;
    size_t allocated_size;
    size_t scalar_size;
    bool is_free;
    int id;

public:
    buffer(const buffer<mem>& other);
    buffer();
    ~buffer();
    buffer(const size_t required_size);

    bool is_available() const;
    void* get_ptr();
    bool get_status() const;
    size_t get_size() const;
    int get_id() const;
    buffer<mem>& operator=(buffer<mem>& other);
    buffer<mem>& operator=(const buffer<mem>& other);
    void reallocate(const size_t required_size);
    void set_allocated_size(const size_t required_size);
    void set_status(const bool status);
    void set_id(const int id);
};

class memory_pipline_base
{
public:
#ifdef INCLUDE_CUDA
    std::vector<buffer<MemType::GPU> > gpu_buff;
#endif
    std::vector<buffer<MemType::CPU> > cpu_buff;

    memory_pipline_base();
    ~memory_pipline_base();

    template< MemType mem >
    void set_available(const int id);

    template< MemType mem >
    std::vector<buffer<mem> >& get_memtyped_vector();
};

template<buf_choose_policy choose_type = buf_choose_policy::naiv>
class memory_pipline: public memory_pipline_base 
{
private:
#ifdef INCLUDE_CUDA
    using memory_pipline_base::gpu_buff;
#endif
    using memory_pipline_base::cpu_buff;
public:
    memory_pipline(/* args */) : memory_pipline_base() {}
    ~memory_pipline() = default;

    template< MemType mem >
    int get_buffer(const size_t required_size, void *& ptr);
};

template<>
class memory_pipline<buf_choose_policy::sorted_vec>: public memory_pipline_base 
{
private:
#ifdef INCLUDE_CUDA
    using memory_pipline_base::gpu_buff;
#endif
    using memory_pipline_base::cpu_buff;
public:
    memory_pipline(/* args */) : memory_pipline_base() {}
    ~memory_pipline() = default;

    template< MemType mem >
    int get_buffer(const size_t required_size, void *& ptr);
};

template<>
class memory_pipline<buf_choose_policy::find_best_unsorted>: public memory_pipline_base 
{
private:
#ifdef INCLUDE_CUDA
    using memory_pipline_base::gpu_buff;
#endif
    using memory_pipline_base::cpu_buff;
public:
    memory_pipline(/* args */) : memory_pipline_base() {}
    ~memory_pipline() = default;

    template< MemType mem >
    int get_buffer(const size_t required_size, void *& ptr);
};

class memory_faucet
{
private:
    memory_faucet() = delete;
    memory_faucet(const memory_faucet&) = delete;
    memory_faucet& operator=(const memory_faucet&) = delete;

    static memory_pipline<buf_choose_policy::naiv>* mem_pipe_naiv;
    static memory_pipline<buf_choose_policy::sorted_vec>* mem_pipe_sorted;
    static memory_pipline<buf_choose_policy::find_best_unsorted>* mem_pipe_unsorted;

public:

    template<buf_choose_policy choose_type = buf_choose_policy::naiv>
    static memory_pipline<choose_type>* get_faucet();
};

template< MemType mem, buf_choose_policy choose_type = buf_choose_policy::naiv >
class memBuf
{
private:
    void* buf;
    int id;
    size_t size;

public:
    memBuf(const size_t required_size);
    ~memBuf();
    void* ptr();
    int get_size();
};