/* ************************************************************************** */
/*                                                                            */
/*                                                        ::::::::            */
/*   DDAscratch.c                                       :+:    :+:            */
/*                                                     +:+                    */
/*   By: qmennen <qmennen@student.codam.nl>           +#+                     */
/*                                                   +#+                      */
/*   Created: 2025/05/02 11:58:09 by whaffman      #+#    #+#                 */
/*   Updated: 2025/05/07 11:52:24 by whaffman      ########   odam.nl         */
/*                                                                            */
/* ************************************************************************** */

#include "cub3d.h"
#include "vec_math.h"
#include <math.h>

t_vec2	get_delta_dist(t_vec2 ray_dir)
{
	t_vec2	delta_dist;

	if (ray_dir.x == 0)
		delta_dist.x = 1e30;
	else
		delta_dist.x = fabs(1 / ray_dir.x);
	if (ray_dir.y == 0)
		delta_dist.y = 1e30;
	else
		delta_dist.y = fabs(1 / ray_dir.y);
	return (delta_dist);
}

t_vec2	get_step(t_vec2 ray_dir)
{
	t_vec2	step;

	step.x = 2 * (ray_dir.x >= 0) - 1;
	step.y = 2 * (ray_dir.y >= 0) - 1;
	return (step);
}

t_vec2	get_side_dist(t_vec2 ray_dir, t_vec2 pos, t_vec2 delta_dist)
{
	const t_vec2	frac_pos = (t_vec2){pos.x - (int)pos.x, pos.y - (int)pos.y};
	const int		raydir_x_pos = (ray_dir.x >= 0);
	const int		raydir_y_pos = (ray_dir.y >= 0);
	t_vec2			side_dist;

	side_dist.x = ((1 - raydir_x_pos) * frac_pos.x
			+ raydir_x_pos * (1 - frac_pos.x)) * delta_dist.x;
	side_dist.y = ((1 - raydir_y_pos) * frac_pos.y
			+ raydir_y_pos * (1 - frac_pos.y)) * delta_dist.y;
	return (side_dist);
}

int	dda_main(t_vec2 ray_dir, t_vec2_int map_pos, t_vec2 *side_dist, t_map *map)
{
	const t_vec2	delta_dist = get_delta_dist(ray_dir);
	const t_vec2	step = get_step(ray_dir);
	int				side;
	int				hit;

	hit = 0;
	while (hit == 0)
	{
		side = (side_dist->x >= side_dist->y);
		side_dist->x += delta_dist.x * (1 - side);
		side_dist->y += delta_dist.y * side;
		map_pos.x += step.x * (1 - side);
		map_pos.y += step.y * side;
		if (map_pos.x < 0
			|| map_pos.x >= map->width
			|| map_pos.y < 0
			|| map_pos.y >= map->height)
			printf("Out of bounds: %d %d\n", map_pos.x, map_pos.y);
		hit = (map->grid[map_pos.y][map_pos.x] == TILE_WALL);
	}
	return (side);
}

// Returns the distance to the wall hit if the wall is hit in y
// direction the result is negative, if the wall is hit
// in x direction the result is positive
double	dda(t_vec2 ray_dir, t_vec2 pos, t_map *map)
{
	const t_vec2	delta_dist = get_delta_dist(ray_dir);
	t_vec2			side_dist;
	int				side;

	side_dist = get_side_dist(ray_dir, pos, delta_dist);
	side = dda_main(ray_dir,
			(t_vec2_int){(int)pos.x, (int)pos.y},
			&side_dist,
			map);
	return ((1 - side) * (side_dist.x - delta_dist.x)
		- side * (side_dist.y - delta_dist.y));
}

t_side	get_side(t_vec2 ray_dir, double perp_dist)
{
	if (perp_dist > 0)
	{
		if (ray_dir.x > 0)
			return (SIDE_WEST);
		else
			return (SIDE_EAST);
	}
	else
	{
		if (ray_dir.y > 0)
			return (SIDE_NORTH);
		else
			return (SIDE_SOUTH);
	}
}

t_render	cast_ray(t_game *game, int x)
{
	t_vec2		ray_dir;
	t_vec2		pos;
	t_render	render;
	double		perp_dist;

	ray_dir = add(game->player->dir,
			mul(game->player->camera,
				(2.0f * x / (double)game->screen->width - 1)));
	pos = game->player->pos;
	perp_dist = dda(ray_dir, pos, game->map);
	render.perp_dist = fabs(perp_dist);
	render.side = get_side(ray_dir, perp_dist);
	if (perp_dist < 0)
		render.wall_x = pos.x + ray_dir.x * render.perp_dist;
	else
		render.wall_x = pos.y + ray_dir.y * render.perp_dist;
	render.wall_x -= floor(render.wall_x);
	return (render);
}

unsigned int	get_texture_color(mlx_texture_t *texture,
	t_render render, int tex_x, int tex_y)
{
	int			index;
	double		dist;

	index = (tex_x + tex_y * texture->height) * texture->bytes_per_pixel;
	dist = (render.perp_dist == 0) * 1e30
		+ (render.perp_dist > 1) * render.perp_dist
		+ (render.perp_dist <= 1) * 1;
	return (texture->pixels[index] << 24
		| texture->pixels[index + 1] << 16
		| texture->pixels[index + 2] << 8
		| (int)(1.0 / dist * 255));
}

void	draw_line(t_game *game, t_render render, int x)
{
	int		y;
	int		color;
	int		height;
	int		start;
	int		tex_start;
	int		end;
	int		tex_x;
	int		tex_y;

	height = (int)(game->screen->height / render.perp_dist);
	start = -height / 2 + game->screen->height / 2;
	tex_start = start;
	if (start < 0)
		start = 0;
	end = height / 2 + game->screen->height / 2;
	if (end >= game->screen->height)
		end = game->screen->height - 1;
	tex_x = (int)(render.wall_x * (double)game->map->textures[render.side]->width);
	if (render.side == SIDE_NORTH || render.side == SIDE_EAST)
		tex_x = game->map->textures[render.side]->width - tex_x - 1;
	y = start;
	while (y < end)
	{
		tex_y = (int)(((double) game->map->textures[render.side]->height / (double) height) * (y - tex_start));
		color = get_texture_color(game->map->textures[render.side], render, tex_x, tex_y);
		if (x < 0 || x >= game->screen->width || y < 0 || y >= game->screen->height)
			break ;
		mlx_put_pixel(game->screen->img, x, y, color);
		y++;
	}
}

void	draw_floor(t_game *game)
{
	int				y;
	int				x;
	int				color;
	double			row_dist;
	t_vec2			floor_step;
	t_vec2			floor_pos;
	t_vec2_int		floor_cell;
	t_vec2_int		tex_coords;
	const t_vec2	left_ray = sub(game->player->dir, game->player->camera);
	const t_vec2	right_ray = add(game->player->dir, game->player->camera);

	y = game->screen->height / 2;
	while (y < game->screen->height)
	{
		row_dist = 0.5 * game->screen->height / (y - game->screen->height / 2.0);
		floor_step = mul(mul(sub(right_ray, left_ray), row_dist), 1.0 / game->screen->width);
		floor_pos = add(game->player->pos, mul(left_ray, row_dist));
		x = 0;
		while (x < game->screen->width)
		{
			floor_cell = (t_vec2_int){(int)floor_pos.x, (int)floor_pos.y};
			tex_coords = (t_vec2_int){(int)(64 * (floor_pos.x - floor_cell.x)) & 63,
				(int)(64 * (floor_pos.y - floor_cell.y)) & 63};
			floor_pos = add(floor_pos, floor_step);
			color = get_texture_color(game->map->texture_floor, (t_render){row_dist, 0, 0}, tex_coords.x, tex_coords.y);
			mlx_put_pixel(game->screen->img, x, y, color);
			color = get_texture_color(game->map->texture_ceiling, (t_render){row_dist, 0, 0}, tex_coords.x, tex_coords.y);
			mlx_put_pixel(game->screen->img, x, game->screen->height - y - 1, color);
			x++;
		}
		y++;
	}
}

void	cast_rays(t_game *game)
{
	int			x;
	t_render	render;

	x = 0;
	draw_floor(game);
	while (x < game->screen->width)
	{
		render = cast_ray(game, x);
		draw_line(game, render, x);
		x++;
	}
}
