#ifndef CAST_ARRAY_HPP
#define CAST_ARRAY_HPP

#include <utils/Array.hpp>
#include <utils/Exceptions.hpp>
#include <utils/PugsTraits.hpp>

#include <iostream>

template <typename DataType, typename CastDataType>
class [[nodiscard]] CastArray
{
 public:
  using data_type = CastDataType;

 private:
  const Array<DataType> m_array;
  const size_t m_size;
  CastDataType* const m_values;

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

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

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

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

  explicit 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*>(&(static_cast<DataType&>(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)) {
      throw UnexpectedError("cannot cast array to the chosen data type");
    }
  }

  explicit 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");
  }

  PUGS_INLINE
  CastArray(DataType && value) = delete;

  PUGS_INLINE
  CastArray(const CastArray&) = default;

  PUGS_INLINE
  CastArray(CastArray &&) = default;

  PUGS_INLINE
  ~CastArray() = default;
};

template <typename CastDataType>
struct cast_array_to
{
  template <typename DataType>
  PUGS_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>
  PUGS_INLINE static CastArray<DataType, CastDataType>
  from(DataType& value)
  {
    return CastArray<DataType, CastDataType>(value);
  }
};

#endif   // CAST_ARRAY_HPP