// Copyright 2023 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "SampleData.h"

#include <iterator>
#include <cstddef>

namespace openpgl
{

    struct SampleContainerInternal
    {
        using value_type = SampleData;
        struct Iterator 
        {
            using iterator_category = std::forward_iterator_tag;
            using difference_type   = std::ptrdiff_t;
            using value_type        = SampleData;
            using pointer           = SampleData*;
            using reference         = SampleData&;

            Iterator(pointer ptr) : m_ptr(ptr) {}

            reference operator*() const { return *m_ptr; }
            pointer operator->() { return m_ptr; }

            // Prefix and postfix increment
            Iterator& operator++() { m_ptr++; return *this; }  
            Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }

            // Prefix and postfix decrement
            Iterator &operator--() { --m_ptr; return *this; }
            Iterator operator--(int) { Iterator tmp(*this); operator--(); return tmp; }
            
            friend Iterator operator+(const Iterator lhs, const int idx) { return Iterator(lhs.m_ptr+idx);}
            friend Iterator operator+=(const Iterator lhs, const int idx) { return Iterator(lhs.m_ptr+idx);}
            friend Iterator operator-(const Iterator lhs, const int idx) { return Iterator(lhs.m_ptr-idx);}
            friend Iterator operator-=(const Iterator lhs, const int idx) { return Iterator(lhs.m_ptr-idx);}

            friend bool operator== (const Iterator& a, const Iterator& b) { return a.m_ptr == b.m_ptr; };
            friend bool operator!= (const Iterator& a, const Iterator& b) { return a.m_ptr != b.m_ptr; }; 

            Iterator::difference_type operator -(const Iterator &it) const
            {
                return m_ptr - it.m_ptr;
            }

            bool operator <(const Iterator &it) const
            {
                return m_ptr < it.m_ptr;
            }

            bool operator <=(const Iterator &it) const
            {
                return m_ptr <= it.m_ptr;
            }

            bool operator >(const Iterator &it) const
            {
                return m_ptr > it.m_ptr;
            }

            bool operator >=(const Iterator &it) const
            {
                return m_ptr >= it.m_ptr;
            }

            value_type& operator[](size_t idx) { 
            return m_ptr[idx]; 
            }
            const value_type& operator[](size_t idx) const { 
                return m_ptr[idx]; 
            }
        private:

            pointer m_ptr;

        };

        using iterator = Iterator;

        SampleContainerInternal() = default;
        ~SampleContainerInternal()
        {
            delete[] m_data;
            m_data = nullptr;
            m_size = 0;
            m_maxSize = 0;
        }

        Iterator begin() { 
            OPENPGL_ASSERT(m_data);
            return Iterator(&m_data[0]); 
        }
        Iterator end() {
            OPENPGL_ASSERT(m_data);
            OPENPGL_ASSERT(m_size>0);
            return Iterator(&m_data[m_size-1]);
        }

        const Iterator begin() const { 
            OPENPGL_ASSERT(m_data);
            return Iterator(&m_data[0]);
        }
        const Iterator end() const  { return Iterator(&m_data[m_size-1]); }

        inline SampleData* data() { return m_data; }

        inline const SampleData* data() const { return m_data; }
        
        inline void reserve(size_t size)
        {
            if (size > m_maxSize)
            {
                delete[] m_data;
                m_data = new SampleData[size];
                m_maxSize = size;
            }
        }

        inline size_t capacity() const {return m_maxSize;};

        inline size_t size() const {return m_size;};

        inline void resize(size_t size)
        {
            reserve(size);
            m_size = size;
        }

        inline void clear() { m_size = 0; }

        SampleData& operator[](size_t idx) { 
            OPENPGL_ASSERT(m_data);
            OPENPGL_ASSERT(m_size > 0);
            OPENPGL_ASSERT( idx < m_size);
            return m_data[idx]; 
        }
        const SampleData& operator[](size_t idx) const { 
            OPENPGL_ASSERT(m_data);
            OPENPGL_ASSERT(m_size > 0);
            OPENPGL_ASSERT(idx < m_size);
            return m_data[idx]; 
        }

    private:
        SampleData* m_data {nullptr};
        size_t m_size {0};
        size_t m_maxSize {0};

    };

}