//
// Created by Peter Rigole on 2019-04-19.
//

#ifndef AXONBITS_ARRAY_H
#define AXONBITS_ARRAY_H

#include "../Managed.cuh"
#include <assert.h>
#include <stdio.h>

// some useful tips:
// https://codereview.stackexchange.com/questions/102036/c-array-with-iterators
// https://en.cppreference.com/w/cpp/iterator/iterator

template<class T>

/**
 * An Array class that runs on host and device, keeps elements and manages their memory on the GPU device.
 *
 * @tparam T
 */
class Array : public Managed {
public:

    __host__
    Array(unsigned int max_size_init) : max_size(max_size_init), size(0) {
        if (max_size_init > 0) {
            T *data_init;
            cudaMallocManaged(&data_init, max_size_init * sizeof(T));
            data = data_init;
        }
        // We probably don't want to call in util classes...
//        cudaDeviceSynchronize();
//        cudaError_t cudaError;
//        cudaError = cudaGetLastError();
//        if(cudaError != cudaSuccess) {
//            printf("Device failure during array initialization, cudaGetLastError() returned %d: %s\n", cudaError, cudaGetErrorString(cudaError));
//        }
    }

    // Destructor
    __host__
    ~Array() {
        if (max_size > 0) {
            cudaFree(data);
        }

        // We probably don't want to call in util classes...
        // cudaDeviceSynchronize();
    }

    /**
     * append is a convenience method to incrementelly fill up an Array.
     * Mind that it doesn't work in combination with set(...), because when using set it is assumed that the
     * array content is managed by the onwer of the array (the size is assumed to be equal to largest index seen while
     * setting the elements).
     */
    __host__
    void append(T &element) {
        if (size == max_size) assert(0); // ToDo: exception handling on device, how? https://stackoverflow.com/questions/50755717/triggering-a-runtime-error-within-a-cuda-kernel ?
        data[size] = element;
        ++size;
        // we're not doing cudaDeviceSynchronize(); here... should we?
    }

//    __host__ __device__
//    void appendMany(T* element, int number) {
//        if (size + number > max_size) assert(0); // ToDo: exception handling on device, how?
//        T* current = element;
//        for(int i = 0; i < number; i++) {
//            data[size] = *current; // not element[i] ???
//            ++current;
//            ++size;
//        }
//    }

    __host__ __device__
    T& operator[](unsigned int index) {
        if (index>=max_size) assert(0); // ToDo: exception handling on device, how?
        return data[index]; // are we returning the address of the element?
    }

//    __host__ __device__
//    const T& operator[](unsigned int index) const {
//        if(index>=max_size) assert(0); // ToDo: exception handling on device, how?
//        return data[index];
//    }

    __host__ __device__
    void set(T &element, unsigned int index) {
        if (index >= max_size) assert(0); // ToDo: exception handling on device, how?
        data[index] = element;
        if (index + 1 > size) {
            size = index + 1; // We set the size based on the largest seen index, assuming the owner fills it up without gaps!
        }
    }

    __host__ __device__
    unsigned int getSize() {
        return size;
    }

    // iterator class
    // Mind that the iterator class is not Managed and its instantiation (begin() and end()) is not by the
    // 'new' operator. This is because an iterator is only supposed to be used on thread stack memory!
    //
    // Use:
    // for (Array<T>::iterator i = arr.begin(); i != arr.end(); ++i)
    //    {
    //        T* t = *i;
    //        cout << *i << " ";
    //    }
    class iterator {
        // T* const *data;
        T *data;
    public:

        __host__ __device__
        iterator(T *arr) : data(arr) {}

        __host__ __device__
        T& operator*() {
            return *data;
        }

        __host__ __device__
        const iterator operator++(int) {
            iterator temp = *this;
            ++*this;
            return temp;
        }

        __host__ __device__
        iterator &operator++() {
            ++data;
            return *this;
        }

        __host__ __device__
        friend bool operator==(const iterator& rhs, const iterator& lhs) {
            return rhs.data == lhs.data;
        }

        __host__ __device__
        friend bool operator!=(const iterator& rhs, const iterator& lhs) {
            return !(rhs == lhs);
        }

    };

    __host__ __device__
    iterator begin() const { return iterator(data); }

    __host__ __device__
    iterator end() const{ return iterator(data + size); }

    __host__ __device__
    iterator index(const unsigned int index) const{ return iterator(data + index); }


private:
    T* data;
    unsigned int size;
    unsigned int max_size;
};


#endif //AXONBITS_ARRAY_H

	.file	"tmpxft_0032006b_00000000-6_Array.cudafe1.cpp"
	.text
#APP

#NO_APP
	.type	_ZL26__cudaUnregisterBinaryUtilv, @function
_ZL26__cudaUnregisterBinaryUtilv:
.LFB2044:
	.cfi_startproc
	endbr64
	movq	_ZL20__cudaFatCubinHandle(%rip), %rdi
	jmp	__cudaUnregisterFatBinary@PLT
	.cfi_endproc
.LFE2044:
	.size	_ZL26__cudaUnregisterBinaryUtilv, .-_ZL26__cudaUnregisterBinaryUtilv
	.section	.text.startup,"ax",@progbits
	.type	_ZL24__sti____cudaRegisterAllv, @function
_ZL24__sti____cudaRegisterAllv:
.LFB2067:
	.cfi_startproc
	endbr64
	pushq	%rax
	.cfi_def_cfa_offset 16
	leaq	_ZL15__fatDeviceText(%rip), %rdi
	call	__cudaRegisterFatBinary@PLT
	movq	%rax, %rdi
	movq	%rax, _ZL20__cudaFatCubinHandle(%rip)
	call	__cudaRegisterFatBinaryEnd@PLT
	leaq	_ZL26__cudaUnregisterBinaryUtilv(%rip), %rdi
	popq	%rdx
	.cfi_def_cfa_offset 8
	jmp	atexit@PLT
	.cfi_endproc
.LFE2067:
	.size	_ZL24__sti____cudaRegisterAllv, .-_ZL24__sti____cudaRegisterAllv
	.section	.init_array,"aw"
	.align 8
	.quad	_ZL24__sti____cudaRegisterAllv
	.section	.nvFatBinSegment,"aw"
	.align 8
	.type	_ZL15__fatDeviceText, @object
	.size	_ZL15__fatDeviceText, 24
_ZL15__fatDeviceText:
	.long	1180844977
	.long	1
	.quad	fatbinData
	.quad	0
	.local	_ZL20__cudaFatCubinHandle
	.comm	_ZL20__cudaFatCubinHandle,8,8
	.ident	"GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

//
// Created by Peter Rigole on 2019-04-19.
//

#ifndef AXONBITS_ARRAY_H
#define AXONBITS_ARRAY_H

#include "../Managed.cuh"
#include <assert.h>
#include <stdio.h>

// some useful tips:
// https://codereview.stackexchange.com/questions/102036/c-array-with-iterators
// https://en.cppreference.com/w/cpp/iterator/iterator

template<class T>

/**
 * An Array class that runs on host and device, keeps elements and manages their memory on the GPU device.
 *
 * @tparam T
 */
class Array : public Managed {
public:

    __host__
    Array(unsigned int max_size_init) : max_size(max_size_init), size(0) {
        if (max_size_init > 0) {
            T *data_init;
            hipMallocManaged(&data_init, max_size_init * sizeof(T));
            data = data_init;
        }
        // We probably don't want to call in util classes...
//        cudaDeviceSynchronize();
//        cudaError_t cudaError;
//        cudaError = cudaGetLastError();
//        if(cudaError != cudaSuccess) {
//            printf("Device failure during array initialization, cudaGetLastError() returned %d: %s\n", cudaError, cudaGetErrorString(cudaError));
//        }
    }

    // Destructor
    __host__
    ~Array() {
        if (max_size > 0) {
            hipFree(data);
        }

        // We probably don't want to call in util classes...
        // cudaDeviceSynchronize();
    }

    /**
     * append is a convenience method to incrementelly fill up an Array.
     * Mind that it doesn't work in combination with set(...), because when using set it is assumed that the
     * array content is managed by the onwer of the array (the size is assumed to be equal to largest index seen while
     * setting the elements).
     */
    __host__
    void append(T &element) {
        if (size == max_size) assert(0); // ToDo: exception handling on device, how? https://stackoverflow.com/questions/50755717/triggering-a-runtime-error-within-a-cuda-kernel ?
        data[size] = element;
        ++size;
        // we're not doing cudaDeviceSynchronize(); here... should we?
    }

//    __host__ __device__
//    void appendMany(T* element, int number) {
//        if (size + number > max_size) assert(0); // ToDo: exception handling on device, how?
//        T* current = element;
//        for(int i = 0; i < number; i++) {
//            data[size] = *current; // not element[i] ???
//            ++current;
//            ++size;
//        }
//    }

    __host__ __device__
    T& operator[](unsigned int index) {
        if (index>=max_size) assert(0); // ToDo: exception handling on device, how?
        return data[index]; // are we returning the address of the element?
    }

//    __host__ __device__
//    const T& operator[](unsigned int index) const {
//        if(index>=max_size) assert(0); // ToDo: exception handling on device, how?
//        return data[index];
//    }

    __host__ __device__
    void set(T &element, unsigned int index) {
        if (index >= max_size) assert(0); // ToDo: exception handling on device, how?
        data[index] = element;
        if (index + 1 > size) {
            size = index + 1; // We set the size based on the largest seen index, assuming the owner fills it up without gaps!
        }
    }

    __host__ __device__
    unsigned int getSize() {
        return size;
    }

    // iterator class
    // Mind that the iterator class is not Managed and its instantiation (begin() and end()) is not by the
    // 'new' operator. This is because an iterator is only supposed to be used on thread stack memory!
    //
    // Use:
    // for (Array<T>::iterator i = arr.begin(); i != arr.end(); ++i)
    //    {
    //        T* t = *i;
    //        cout << *i << " ";
    //    }
    class iterator {
        // T* const *data;
        T *data;
    public:

        __host__ __device__
        iterator(T *arr) : data(arr) {}

        __host__ __device__
        T& operator*() {
            return *data;
        }

        __host__ __device__
        const iterator operator++(int) {
            iterator temp = *this;
            ++*this;
            return temp;
        }

        __host__ __device__
        iterator &operator++() {
            ++data;
            return *this;
        }

        __host__ __device__
        friend bool operator==(const iterator& rhs, const iterator& lhs) {
            return rhs.data == lhs.data;
        }

        __host__ __device__
        friend bool operator!=(const iterator& rhs, const iterator& lhs) {
            return !(rhs == lhs);
        }

    };

    __host__ __device__
    iterator begin() const { return iterator(data); }

    __host__ __device__
    iterator end() const{ return iterator(data + size); }

    __host__ __device__
    iterator index(const unsigned int index) const{ return iterator(data + index); }


private:
    T* data;
    unsigned int size;
    unsigned int max_size;
};


#endif //AXONBITS_ARRAY_H

	.text
	.file	"Array.hip"
	.type	__hip_cuid_,@object             # @__hip_cuid_
	.bss
	.globl	__hip_cuid_
__hip_cuid_:
	.byte	0                               # 0x0
	.size	__hip_cuid_, 1

	.ident	"AMD clang version 18.0.0git (https://github.com/RadeonOpenCompute/llvm-project roc-6.3.2 25012 e5bf7e55c91490b07c49d8960fa7983d864936c4)"
	.section	".note.GNU-stack","",@progbits
	.addrsig
	.addrsig_sym __hip_cuid_

	.text
	.p2alignl 7, 3214868480
	.fill 96, 4, 3214868480
	.type	__hip_cuid_,@object             ; @__hip_cuid_
	.section	.bss,"aw",@nobits
	.globl	__hip_cuid_
__hip_cuid_:
	.byte	0                               ; 0x0
	.size	__hip_cuid_, 1

	.ident	"AMD clang version 18.0.0git (https://github.com/RadeonOpenCompute/llvm-project roc-6.3.2 25012 e5bf7e55c91490b07c49d8960fa7983d864936c4)"
	.section	".note.GNU-stack","",@progbits
	.addrsig
	.addrsig_sym __hip_cuid_
	.amdgcn_target "amdgcn-amd-amdhsa--gfx1100"
	.amdgpu_metadata
---
amdhsa.kernels:  []
amdhsa.target:   amdgcn-amd-amdhsa--gfx1100
amdhsa.version:
  - 1
  - 2
...

	.end_amdgpu_metadata
