#pragma once

#include "algos/matrix.hpp"
#include "algos/spatialmap.hpp"
#include "game/level.hpp"
#include "graphics/textures.hpp"
#include <SFML/System/Clock.hpp>

using matrix::Matrix;
using RGBA = uint32_t;

struct CameraLOL {
  double t = 0.0;
  double move_speed = 0.1;
  double rot_speed = 0.06;
  double target_x = 0.0;
  double target_y = 0.0;
  double target_dir_x = 0.0;
  double target_dir_y = 0.0;
  double target_plane_x = 0.0;
  double target_plane_y = 0.0;
};

using SpriteRender = std::pair<sf::Sprite, std::shared_ptr<sf::Shader>>;

struct Raycaster {
  sf::Texture $view_texture;
  sf::Sprite $view_sprite;
  int $width;
  int $height;
  int $screen_pos_x;
  int $screen_pos_y;
  std::vector<double> $zbuffer; // width

  int $pitch=0;
  sf::Clock $clock;
  std::shared_ptr<sf::Shader> $brightness = nullptr;
  double $pos_x = 0;
  double $pos_y = 0;

  // initial direction vector
  double $dir_x = 1;
  double $dir_y = 0;

  // the 2d raycaster version of camera plane
  double $plane_x = 0.0;
  double $plane_y = 0.66;
  Point aiming_at{0,0};
  Point camera_at{0,0};
  CameraLOL $camera;
  std::unique_ptr<RGBA[]> $pixels = nullptr;

  std::unordered_map<DinkyECS::Entity, textures::SpriteTexture> $sprites;
  // BUG: this can be way better I think
  std::vector<SpriteRender> $sprites_to_render;
  SortedEntities $sprite_order;

  GameDB::Level $level;
  Matrix $tiles;
  Matrix $walls;

  Raycaster(int x, int y, int width, int height);

  void cast_rays();
  void draw_ceiling_floor();
  void draw_pixel_buffer();
  void sprite_casting(sf::RenderTarget& target);
  void update();
  void render(sf::RenderTarget& target);

  void sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount);
  void set_position(int x, int y);

  inline size_t pixcoord(int x, int y) {
    return ((y) * $width) + (x);
  }

  void update_level(GameDB::Level& level);
  void update_sprite(DinkyECS::Entity ent, components::Sprite& sprite);
  void init_shaders();

  void position_camera(float player_x, float player_y);
  Point plan_move(int dir, bool strafe);
  void plan_rotate(int dir, float amount);

  bool play_rotate();
  bool play_move();

  void abort_plan();
  bool is_target(DinkyECS::Entity entity);
  void update_camera_aiming();

  // BUG: these should go away when Bug #42 is solved
  void apply_sprite_effect(std::shared_ptr<sf::Shader> effect, float width, float height);
  std::shared_ptr<sf::Shader> apply_lighting_effect(components::Position& sprite_pos, matrix::Matrix &lights);
};
