// File: crn_vector2d.h
#pragma once

namespace crnlib {
template <typename T>
class vector2D {
 public:
  typedef crnlib::vector<T> vector_type;
  typedef T value_type;
  typedef T& reference;
  typedef const T& const_reference;
  typedef T* pointer;
  typedef const T* const_pointer;

  inline vector2D(uint width = 0, uint height = 0, const T& def = T())
      : m_width(width),
        m_height(height),
        m_vec(width * height),
        m_def(def) {
  }

  inline vector2D(const vector2D& other)
      : m_width(other.m_width),
        m_height(other.m_height),
        m_vec(other.m_vec),
        m_def(other.m_def) {
  }

  inline vector2D& operator=(const vector2D& rhs) {
    if (this == &rhs)
      return *this;

    m_width = rhs.m_width;
    m_height = rhs.m_height;
    m_vec = rhs.m_vec;

    return *this;
  }

  bool try_resize(uint width, uint height, bool preserve = true) {
    if ((width == m_width) && (height == m_height))
      return true;

    vector_type new_vec;
    if (!new_vec.try_resize(width * height))
      return false;

    if (preserve) {
      const uint nx = math::minimum(width, m_width);
      const uint ny = math::minimum(height, m_height);

      for (uint y = 0; y < ny; y++) {
        const T* pSrc = &m_vec[y * m_width];
        T* pDst = &new_vec[y * width];
        if (CRNLIB_IS_BITWISE_COPYABLE_OR_MOVABLE(T))
          memcpy(pDst, pSrc, nx * sizeof(T));
        else {
          for (uint x = 0; x < nx; x++)
            *pDst++ = *pSrc++;
        }
      }
    }

    m_width = width;
    m_height = height;
    m_vec.swap(new_vec);

    return true;
  }

  void resize(uint width, uint height, bool preserve = true) {
    if (!try_resize(width, height, preserve)) {
      CRNLIB_FAIL("vector2D::resize: Out of memory");
    }
  }

  inline void clear() {
    m_vec.clear();
    m_width = 0;
    m_height = 0;
  }

  inline uint width() const { return m_width; }
  inline uint height() const { return m_height; }
  inline uint size() const { return m_vec.size(); }

  inline uint size_in_bytes() const { return m_vec.size() * sizeof(T); }

  const vector_type& get_vec() const { return m_vec; }
  vector_type& get_vec() { return m_vec; }

  inline const T* get_ptr() const { return m_vec.get_ptr(); }
  inline T* get_ptr() { return m_vec.get_ptr(); }

  inline const T& operator[](uint i) const { return m_vec[i]; }
  inline T& operator[](uint i) { return m_vec[i]; }

  inline const T& operator()(uint x, uint y) const {
    CRNLIB_ASSERT((x < m_width) && (y < m_height));
    return m_vec[x + y * m_width];
  }

  inline T& operator()(uint x, uint y) {
    CRNLIB_ASSERT((x < m_width) && (y < m_height));
    return m_vec[x + y * m_width];
  }

  inline const T& at(uint x, uint y) const {
    if ((x >= m_width) || (y >= m_height))
      return m_def;
    return m_vec[x + y * m_width];
  }

  inline T& at(uint x, uint y) {
    if ((x >= m_width) || (y >= m_height))
      return m_def;
    return m_vec[x + y * m_width];
  }

  inline void swap(vector2D& other) {
    m_vec.swap(other.m_vec);
    anvil::swap(m_width, other.m_width);
    anvil::swap(m_height, other.m_height);
  }

  inline void set_all(const T& x) {
    m_vec.set_all(x);
  }

 private:
  vector_type m_vec;
  uint m_width;
  uint m_height;
  T m_def;
};

}  // namespace anvil
