#ifndef CAST_ARRAY_HPP
#define CAST_ARRAY_HPP

#include <Array.hpp>
#include <PastisTraits.hpp>

template <typename DataType,
          typename CastDataType>
class CastArray
{
 public:
  using data_type = CastDataType;
 private:
  const Array<DataType> m_array;
  const size_t m_size;
  CastDataType* const m_values;

 public:
  PASTIS_INLINE
  const size_t& size() const
  {
    return m_size;
  }

  PASTIS_INLINE
  CastDataType& operator[](const size_t& i) const
  {
    Assert(i<m_size);
    return m_values[i];
  }

  PASTIS_INLINE
  CastArray& operator=(const CastArray&) = default;

  PASTIS_INLINE
  CastArray& operator=(CastArray&&) = default;

  PASTIS_INLINE
  CastArray()
      : m_size(0),
        m_values(nullptr)
  {
    ;
  }

  PASTIS_INLINE
  CastArray(const Array<DataType>& array)
      : m_array (array),
        m_size  (sizeof(DataType)*array.size()/sizeof(CastDataType)),
        m_values((array.size() == 0) ? nullptr : reinterpret_cast<CastDataType*>(&(array[0])))
  {
    static_assert((std::is_const_v<CastDataType> and std::is_const_v<DataType>) or
                  (not std::is_const_v<DataType>), "CastArray cannot remove const attribute");

    if (sizeof(DataType)*array.size() % sizeof(CastDataType)) {
      std::cerr << "cannot cast array to the chosen data type\n";
      std::exit(1);
    }
  }

  PASTIS_INLINE
  CastArray(DataType& value)
      : m_size  (sizeof(DataType)/sizeof(CastDataType)),
        m_values(reinterpret_cast<CastDataType*>(&(value)))
  {
    static_assert((std::is_const_v<CastDataType> and std::is_const_v<DataType>) or
                  (not std::is_const_v<DataType>), "CastArray cannot remove const attribute");
    static_assert(is_trivially_castable<DataType>, "Defining CastArray from non trivially castable type is not allowed");
  }

  PASTIS_INLINE
  CastArray(DataType&& value) = delete;

  PASTIS_INLINE
  CastArray(const CastArray&) = default;

  PASTIS_INLINE
  CastArray(CastArray&&) = default;

  PASTIS_INLINE
  ~CastArray() = default;
};

template <typename CastDataType>
struct cast_array_to
{
  template <typename DataType>
  PASTIS_INLINE
  static CastArray<DataType, CastDataType>
  from(const Array<DataType>& array)
  {
    return CastArray<DataType, CastDataType>(array);
  }
};

template <typename CastDataType>
struct cast_value_to
{
  template <typename DataType>
  PASTIS_INLINE
  static CastArray<DataType, CastDataType>
  from(DataType& value)
  {
    return CastArray<DataType, CastDataType>(value);
  }
};

#endif // CAST_ARRAY_HPP