#pragma once

#include <algorithm>
#include <array>
#include <ctime>
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <memory>
#include <random>

#include "constants.h"
#include "shader.h"

constexpr std::size_t index(const std::size_t x, const std::size_t y) {
  return x + y * kGeographyShort;
}

class Grid {
 public:
  float get(const std::size_t x, const std::size_t y) const {
    return (*data_)[index(x, y)];
  }
  void set(const std::size_t x, const std::size_t y, const float value) const {
    (*data_)[index(x, y)] = value;
  }

  Grid operator+(const Grid &) const;
  void operator+=(const Grid &) const;
  Grid operator-() const;
  Grid operator-(const Grid &) const;
  void operator-=(const Grid &) const;
  Grid operator*(float) const;
  void operator*=(float) const;
  Grid operator/(float) const;
  void operator/=(float) const;

  glm::vec3 normal_at(std::size_t, std::size_t, float = 1.) const;

  static Grid *PerlinNoise(int, int, std::size_t);

  std::array<Vertex, kTotalVertices> *vertices() const;
  static std::array<unsigned int, kTotalIndices> *indices();
  static void RandomizeBase() { base_random_ = device_() << 4; }

  float min() const { return *std::min_element(data_->begin(), data_->end()); }
  float max() const { return *std::max_element(data_->begin(), data_->end()); }

 private:
  std::unique_ptr<std::array<float, kGeographyShort * kGeographyLong>> data_{
      std::make_unique<std::array<float, kGeographyShort * kGeographyLong>>()};

  static std::random_device device_;
  std::mt19937 engine_{device_()};
  std::uniform_real_distribution<float> dist_{0, glm::two_pi<float>()};
  static std::mt19937::result_type base_random_;

  float RandomAngle() { return dist_(engine_); }
  void AngleSeed(const std::mt19937::result_type seed) { engine_.seed(seed); }
};
