library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ray_unit is
	port (
		signal clk : in std_logic;
		
		-- initial ray
		signal col_base : in signed (8 downto 0) := (others => '0'); -- should be one of these: -160, -150, ... , -10, 0, 10, ... , 150
		signal row : in signed (8 downto 0) := (others => '0'); -- should start at 189 and go down to -50
		signal col_offset : in unsigned (3 downto 0) := (others => '0'); -- should start at 0 and go to 9
		signal cam_co_ord_x, cam_co_ord_y, cam_co_ord_z :  in unsigned (8 downto 0) := (others => '0'); -- coordinates of the camera
		
		-- control signals
		signal new_pixel : in std_logic := '0'; -- assert for 1 cycle when starting a new pixel
		signal new_rectangle : in std_logic := '0'; -- assert for 1 cycle with each new rectangle
		signal done : out std_logic := '0'; -- active high. Wait for this signal after you assert new_rectangle
		signal done_rectangles : in std_logic := '0'; -- assert for 1 cycle when you finish all the rectangles
		
		-- rectangles are 7 bytes. x,y,z for corners 1 and 2, and 1 byte for color.
		signal rec_x0, rec_y0, rec_z0, rec_x1, rec_y1, rec_z1 : in unsigned (8 downto 0) := (others => '0');
		-- color(2) = red, color(1) = green, color(0) = blue. Or, "rgb"
		signal color : in unsigned (5 downto 0) := (others => '0'); 
		
		-- color output
		-- this is the final result.
		-- tests are written for separate red, green, blue. Don't actually use.
		signal red, green, blue : out unsigned (5 downto 0) := (others => '0');
		signal rgb : out unsigned (17 downto 0) := (others => '0')
	);
end ray_unit;

architecture unit of ray_unit is
	-- current ray.
	constant X_CAMERA_SLOPE : signed(8 downto 0) := "000110010";
	signal ray_x, ray_y, ray_z : unsigned (8 downto 0) := (others => '0');
	signal ray_dx, ray_dy, ray_dz : signed (8 downto 0) := (others => '0');
	-- core math
	signal a1, c1, d1 : unsigned (8 downto 0) := (others => '0');
	signal b1, e1 : signed (8 downto 0) := (others => '0');
	signal core_result1 : unsigned (8 downto 0) := (others => '0');
	signal core_1_done : std_logic := '0';
	signal a2, c2, d2 : unsigned (8 downto 0) := (others => '0');
	signal b2, e2 : signed (8 downto 0) := (others => '0');
	signal core_result2 : unsigned (8 downto 0) := (others => '0');
	signal core_2_done : std_logic := '0';
	signal reset_core : std_logic := '0';
	-- intersection_finder
	type states is (zero, one, two, three);
	signal state : states;
	signal intersect_x, intersect_y, intersect_z : unsigned (8 downto 0) := (others => '0');
	-- z_buffer
	signal close_distance : unsigned (8 downto 0) := (others => '0');
	signal close_x, close_y, close_z : unsigned (8 downto 0) := (others => '0');
	signal close_r, close_g, close_b : unsigned (8 downto 0) := (others => '0');
	signal reflect_x, reflect_y, reflect_z : signed (8 downto 0) := (others => '0');
	-- color_combiner
	signal ray_count : unsigned (0 downto 0) := "0";
	signal total_r, total_g, total_b : unsigned (8 downto 0) := (others => '0');
	
	constant INFINITE : unsigned (8 downto 0) := "011111111";
begin
	red <= total_r(7 downto 2);
	green <= total_g(7 downto 2);
	blue <= total_b(7 downto 2);
	rgb <= total_r(7 downto 2) & total_g(7 downto 2) & total_b(7 downto 2);
	
	depending_on_control : process(clk)
		variable blue_helper : signed (8 downto 0) := (others => '0');
		variable temp_r_0 : unsigned (1 downto 0) := (others => '0');
		variable temp_r_1 : unsigned (6 downto 0) := (others => '0');
		variable temp_g_0 : unsigned (1 downto 0) := (others => '0');
		variable temp_g_1 : unsigned (6 downto 0) := (others => '0');
		variable temp_b_0 : unsigned (1 downto 0) := (others => '0');
		variable temp_b_1 : unsigned (6 downto 0) := (others => '0');
	begin
		if rising_edge(clk) then
			if new_pixel = '1' then
				ray_x <= cam_co_ord_x;
				ray_y <= cam_co_ord_y;
				ray_z <= cam_co_ord_z;
				
				ray_dx <= X_CAMERA_SLOPE;
				ray_dy <= col_base + signed("0" & col_offset);
				ray_dz <= row;
				reflect_x <= X_CAMERA_SLOPE;
				reflect_y <= col_base + signed("0" & col_offset);
				reflect_z <= row;
				
				total_r <= "000000000";
				total_g <= "000000000";
				total_b <= "000000000";
				ray_count <= "0";
			elsif done_rectangles = '1' then
				ray_x <= close_x;
				ray_y <= close_y;
				ray_z <= close_z;
				ray_dx <= reflect_x;
				ray_dy <= reflect_y;
				ray_dz <= reflect_z;
				
				if ray_count = "0" then
					total_r <= close_r / 2;
					total_g <= close_g / 2;
					total_b <= close_b / 2;
				elsif ray_count = "1" then
					total_r <= total_r + close_r / 4;
					total_g <= total_g + close_g / 4;
					total_b <= total_b + close_b / 4;
				end if;
				ray_count <= ray_count + 1;
			end if;
			
			-- Handle next rectangle
			if new_rectangle = '1' then
				if rec_x0 = rec_x1 then
					intersect_x <= rec_x0;
					a1 <= ray_y; b1 <= ray_dy; c1 <= rec_x0; d1 <= ray_x; e1 <= ray_dx;
					a2 <= ray_z; b2 <= ray_dz; c2 <= rec_x0; d2 <= ray_x; e2 <= ray_dx;
				elsif rec_y0 = rec_y1 then
					a1 <= ray_x; b1 <= ray_dx; c1 <= rec_y0; d1 <= ray_y; e1 <= ray_dy;
					intersect_y <= rec_y0;
					a2 <= ray_z; b2 <= ray_dz; c2 <= rec_y0; d2 <= ray_y; e2 <= ray_dy;
				elsif rec_z0 = rec_z1 then
					a1 <= ray_x; b1 <= ray_dx; c1 <= rec_z0; d1 <= ray_z; e1 <= ray_dz;
					a2 <= ray_y; b2 <= ray_dy; c2 <= rec_z0; d2 <= ray_z; e2 <= ray_dz;
					intersect_z <= rec_z0;
				end if;
				reset_core <= '1';
				state <= zero;
				done <= '0';
			else
				case state is
				when zero =>
					reset_core <= '0';
					state <= one;
				when one =>
					if core_1_done = '1' and core_2_done = '1' then
						if rec_x0 = rec_x1 then
							intersect_y <= core_result1;
							intersect_z <= core_result2;
						elsif rec_y0 = rec_y1 then
							intersect_x <= core_result1;
							intersect_z <= core_result2;
						elsif rec_z0 = rec_z1 then
							intersect_x <= core_result1;
							intersect_y <= core_result2;
						end if;
						state <= two;
					end if;
				when two =>
					if intersect_x /= INFINITE and intersect_y /= INFINITE and intersect_z /= INFINITE
							and intersect_x /= ray_x
							and (
								(intersect_x <= rec_x0 and intersect_x >= rec_x1) or
								(intersect_x >= rec_x0 and intersect_x <= rec_x1)
							) and (
								(intersect_y <= rec_y0 and intersect_y >= rec_y1) or
								(intersect_y >= rec_y0 and intersect_y <= rec_y1)
							) and (
								(intersect_z <= rec_z0 and intersect_z >= rec_z1) or
								(intersect_z >= rec_z0 and intersect_z <= rec_z1)
							) and (
								(intersect_x < ray_x and ray_x - intersect_x < close_distance) or
								(intersect_x > ray_x and intersect_x - ray_x < close_distance)
							) then
						if (intersect_x < ray_x) then
							close_distance <= ray_x - intersect_x;
						else
							close_distance <= intersect_x - ray_x;
						end if;
						close_x <= intersect_x;
						close_y <= intersect_y;
						close_z <= intersect_z;
						close_r <= "000000000"; close_g <= "000000000"; close_b <= "000000000";
						
						temp_r_0 := "00";
						if color(5) = '1' then
							temp_r_0 := "01";
						end if;
						temp_r_1 := "0000000";
						if color(4) = '1' then
							temp_r_1 := "1111111";
						end if;
						close_r <= temp_r_0 & temp_r_1;
						temp_g_0 := "00";
						if color(3) = '1' then
							temp_g_0 := "01";
						end if;
						temp_g_1 := "0000000";
						if color(2) = '1' then
							temp_g_1 := "1111111";
						end if;
						close_g <= temp_g_0 & temp_g_1;
						temp_b_0 := "00";
						if color(1) = '1' then
							temp_b_0 := "01";
						end if;
						temp_b_1 := "0000000";
						if color(0) = '1' then
							temp_b_1 := "1111111";
						end if;
						close_b <= temp_b_0 & temp_b_1;
						
						if rec_x0 = rec_x1 then
							reflect_x <= not(ray_dx) + 1;
							reflect_y <= ray_dy;
							reflect_z <= ray_dz;
						elsif rec_y0 = rec_y1 then
							reflect_x <= ray_dx;
							reflect_y <= not(ray_dy) + 1;
							reflect_z <= ray_dz;
						elsif rec_z0 = rec_z1 then
							reflect_x <= ray_dx;
							reflect_y <= ray_dy;
							reflect_z <= not(ray_dz) + 1;
						end if;
					end if;
					done <= '1';
					state <= three;
				when three =>
					-- hang out for a while
				end case;
			end if;
			
			-- Reset closest rectangle
			if new_pixel = '1' or done_rectangles = '1' then
				done <= '0';
				if new_pixel = '1' then
					blue_helper := row;
				elsif done_rectangles = '1' then
					blue_helper := reflect_z;
				end if;
				blue_helper := blue_helper + "000110010";
				-- reflect_z can be less than -50. In that case, the background color
				-- should be black, just like if we were at row -50.
				if blue_helper(8) = '1' and done_rectangles = '1' and reflect_z(8) = '1' then
					blue_helper := "000000000";
				end if;
				close_distance <= INFINITE;
				close_x <= INFINITE; close_y <= INFINITE; close_z <= INFINITE;
				close_r <= "000000000";
				close_g <= "000000000";
				close_b <= unsigned(blue_helper);
			end if;
		end if;
	end process;
	
	intersection_1 : entity work.core_math
	port map (
		clk => clk,
		reset => reset_core,
		a => a1,
		b => b1,
		c => c1,
		d => d1,
		e => e1,
		result => core_result1,
		done => core_1_done
	);
	
	intersection_2 : entity work.core_math
	port map (
		clk => clk,
		reset => reset_core,
		a => a2,
		b => b2,
		c => c2,
		d => d2,
		e => e2,
		result => core_result2,
		done => core_2_done
	);
end unit;