#pragma once

#include <fmt/core.h>
#include <SFML/Graphics.hpp>
#include <numbers>
#include <algorithm>
#include <cmath>
#include "matrix.hpp"
#include <cstdlib>
#include <array>
#include "dbc.hpp"
#include <memory>
#include "texture.hpp"
#include <SFML/System/Clock.hpp>

using matrix::Matrix;
using RGBA = uint32_t;

struct Raycaster {
  int $pitch=0;
  sf::Clock $clock;

  TexturePack $textures;
  double $posX = 0;
  double $posY = 0;

  // initial direction vector
  double $dirX = -1;
  double $dirY = 0;

  // the 2d raycaster version of camera plane
  double $planeX = 0;
  double $planeY = 0.66;
  sf::Texture $view_texture;
  sf::Sprite $view_sprite;

  std::unique_ptr<RGBA[]> $pixels = nullptr;

  int $width;
  int $height;
  sf::RenderWindow& $window;
  Matrix& $map;
  std::vector<int> spriteOrder;
  std::vector<double> spriteDistance;
  std::vector<double> ZBuffer; // width

  Raycaster(sf::RenderWindow& window, Matrix &map, int width, int height);

  void draw_pixel_buffer();
  void clear();
  void cast_rays();
  void draw_ceiling_floor();
  void sprite_casting();
  void sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount);
  void render();

  bool empty_space(int new_x, int new_y);

  void run(double speed, int dir);
  void rotate(double speed, int dir);
  void position_camera(float player_x, float player_y);

  void set_position(int x, int y);
  inline size_t pixcoord(int x, int y) {
    if(!(x >=0 && x < $width)) {
      dbc::sentinel(fmt::format("pixcoord x={} but $width={}", x, $width));
    }

    if(!(y >= 0 && y < $height)) {
      dbc::sentinel(fmt::format("pixcoord y={} but $height={}", y, $height));
    }

    return ((y) * $width) + (x);
  }

};