//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#pragma once
#ifndef SoA_AoS_h
#define SoA_AoS_h


#include <cstdlib>


#define DECLARE_STRUCT_BEGIN(StructName) \
  template <size_t SIZE> \
  class StructName \
  { \
    public:

#define DECLARE_MEMBER2(t, name) \
    t name[SIZE];

#define DECLARE_STRUCT_END(StructName) \
    const size_t s_size = SIZE; \
  };


/*
template <typename T>
struct ArrayAccessor
{
  explicit ArrayAccessor(std::function<T&(size_t)> func) : m_getter(func) {}
  inline T& operator[](std::size_t idx) {
    return m_getter(idx);
  }
  std::function<T&(size_t)> m_getter;
};


#define VAR(a, m) \
  var(&decltype(a)::TYPE::m)
*/


#define MEMBER(v, m) \
  v.var(&decltype(v)::TYPE::m)



template <template<size_t> class t, size_t SIZE>
struct SOA
{ 
  template <template<size_t> class t2, size_t SIZE2>
  struct MemberAccessor
  {
    typedef t2<SIZE2> TYPE;
    template <typename T>
    T& var(T (TYPE::*memberPtr)[SIZE])
    {
      return (m_this->m_array[0].*memberPtr)[m_idx];
    }
    size_t m_idx;
    SOA<t2,SIZE2>* m_this;
  };
  
  inline MemberAccessor<t,SIZE> operator[](std::size_t idx) {
    return MemberAccessor<t,SIZE>{idx, this};
  }

/*
  template <typename T>
  ArrayAccessor<T> get(T (TYPE::*memberPtr)[SIZE]) {
    return ArrayAccessor<T>([this, memberPtr](size_t idx)->T& { return (m_array[0].*memberPtr)[idx]; });
  }

  template <typename T, T (TYPE::*memberPtr)[SIZE]>
  ArrayAccessor<T> test() {
    return ArrayAccessor<T>([&](size_t idx)->T& { return (m_array[0].*memberPtr)[idx]; });
  }
*/
  t<SIZE> m_array[1];
};


template <template<size_t> class t, size_t SIZE>
struct AOS
{
  template <template<size_t> class t2, size_t SIZE2>
  struct MemberAccessor
  {
    typedef t2<1> TYPE;
    template <typename T>
    T& var(T (t2<1>::*memberPtr)[1])
    {
      return (m_this->m_array[m_idx].*memberPtr)[0];
    }
    size_t m_idx;
    AOS<t2,SIZE2>* m_this;
  };
  
  inline MemberAccessor<t,SIZE> operator[](std::size_t idx) {
    return MemberAccessor<t,SIZE>{idx, this};
  }

/*
  typedef t<1> TYPE;

  template <typename T>
  ArrayAccessor<T> get(T (t<1>::*memberPtr)[1]) {
    return ArrayAccessor<T>([this, memberPtr](size_t idx)->T& { return (m_array[idx].*memberPtr)[0]; });
  }
  */
  
  t<1> m_array[SIZE];
};


#define ARRAY_GET(array, idx, member) \
    array.get(&std::remove_reference<decltype(array.m_array[0])>::type::member) idx


#define ARRAY_MEMBER(array, member) \
    array.get(&std::remove_reference<decltype(array.m_array[0])>::type::member)


#define MAKE_AOS(array, S, SIZE) \
     S<1> array[SIZE]; \
     const bool array##_isSOA = false;

#define MAKE_SOA(array, S, SIZE) \
     S<SIZE> array[1]; \
     const bool array##_isSOA = true;

#define GET(array, a, n) \
     ((array##_isSOA) ? array[0].a[n] : array[n].a[0])




#endif // SoA_AoS_h
