module viewtube_line_buffer(
	input logic        clk,
	input logic [7:0]  background_VGA_R, background_VGA_G, background_VGA_B,
	input logic [7:0]  sprite_table_VGA_R, sprite_table_VGA_G, sprite_table_VGA_B,
	input logic [11:0] background_x_index,
	input logic [11:0] background_y_index,
	input logic [11:0] background_x_start,
    input logic [11:0] background_y_start,
	input logic [10:0] hcount,
	input logic [9:0]  vcount,
	input logic [9:0]  background_width,
	input logic [9:0]  background_height,
	input logic        background_ready,
	input logic [11:0] sprite_start_x_pos,
	input logic [11:0] sprite_start_y_pos,
	input logic [9:0]  sprite_width,
	input logic [9:0]  sprite_height,
	input logic        sprite_ready,
	input logic        sprite_active,
	input logic 		sync_movement,
	input logic  [4:0]   sprite_col_offset,
	output logic       background_ready_to_read,
	output logic 	   background_seek_to_x_pos,
	output logic [7:0] sprite_index,
	output logic 		sprite_increment_x, 
	output logic [4:0]   sprite_row_offset,
	output logic  shift_current_sprite,
	output logic [7:0]  out_VGA_R, out_VGA_G, out_VGA_B
);

	parameter DISPLAY_PANE_TOP  = 10'd10;
	parameter DISPLAY_PANE_LEFT = 10'd20;
	parameter TOTAL_SPRITES     = 8'd64;
	parameter NON_VISIBLE_OFFSET = 10'd640;

	parameter MEM_WIDTH=24;
	parameter MEM_ROW=641; // add extra row as non-visible offset
	parameter MEM_ROW_ADDR_W=$clog2(MEM_ROW);
	logic [MEM_WIDTH-1:0] buffer_one_data_in;
	logic [MEM_WIDTH-1:0] buffer_one_data_out;
	logic [MEM_ROW_ADDR_W-1:0] buffer_one_ptr;
	logic buffer_one_write;

	logic [MEM_WIDTH-1:0] buffer_two_data_in;
	logic [MEM_WIDTH-1:0] buffer_two_data_out;
	logic [MEM_ROW_ADDR_W-1:0] buffer_two_ptr;
	logic buffer_two_write;

	logic sprite_read_delay;
	initial sprite_read_delay = 0;

	logic finished_with_sprites;
	initial finished_with_sprites = 0;

	logic display_buffer_one_active;
	initial display_buffer_one_active = 0;

	logic [11:0] prev_x_pos; // don't let pixel get overwritten
	logic [MEM_ROW_ADDR_W-1:0] write_buffer_ptr;

	logic  move_sprites;
	
	logic  blank_output;
	logic [1:0] read_delay;

	logic write_delay;

	initial write_delay = 0;
	initial read_delay = 2'b0;

	initial blank_output = 0;
	initial move_sprites = 0;
	initial shift_current_sprite = 0;



	initial background_seek_to_x_pos = 1;
	initial background_ready_to_read = 0;
	initial prev_x_pos = 12'b0;

	initial sprite_index = 8'b0;
	initial sprite_row_offset = 5'b0;

	viewtube_dynamic_memory #(
        .WIDTH(MEM_WIDTH),  
        .ROW(MEM_ROW)
	) buffer_one (
        .addr(buffer_one_ptr),
        .data_out(buffer_one_data_out),
		.data_in(buffer_one_data_in),
		.clk(clk),
		.write(buffer_one_write)
    );

	viewtube_dynamic_memory #(
        .WIDTH(MEM_WIDTH),  
        .ROW(MEM_ROW)
	) buffer_two (
        .addr(buffer_two_ptr),
        .data_out(buffer_two_data_out),
		.data_in(buffer_two_data_in),
		.clk(clk),
		.write(buffer_two_write)
    );

	always_ff @(posedge clk) begin

		prev_x_pos <= background_x_index;

		if (sync_movement) begin
			move_sprites <= 1;
			sprite_index <= 0;
			write_delay <= 0;
			read_delay <= 1;
		end else if (move_sprites) begin
			
			if (read_delay != 2'b0) begin 
				read_delay <= read_delay -1;
			end else if(sprite_ready) begin
				if (shift_current_sprite == 0 && ! write_delay) begin
					shift_current_sprite <= 1;
				end else if(write_delay == 0 ) begin
					shift_current_sprite <= 0;
					write_delay <= 1;
				end else if(write_delay) begin
					write_delay <= 0;
					read_delay <= 2'd2;
					if(sprite_index +1 == TOTAL_SPRITES) begin
						sprite_index <= 0;
						move_sprites <= 0;
					end else begin
						sprite_index <= sprite_index +1;
					end
				end
			end 
			
		end else if ( (vcount +1) >= DISPLAY_PANE_TOP && (vcount ) <= DISPLAY_PANE_TOP + background_height && background_ready) begin 
            // in range for viewing pane
			
			if(hcount ==1599 
			&& ( (vcount ) < DISPLAY_PANE_TOP + background_height ) 
			) begin
				background_ready_to_read <= 1;
			end else if (write_buffer_ptr + 1 == background_width) begin
				background_ready_to_read <= 0;
			end



			if ( write_buffer_ptr < background_width  &&  background_x_index > background_x_start) begin
				if (display_buffer_one_active) begin
					if ( background_x_index >=background_x_start) begin
						buffer_two_write <= ( prev_x_pos != background_x_index );
						// only write first color read from input
						buffer_two_ptr <= DISPLAY_PANE_LEFT + write_buffer_ptr;
						buffer_two_data_in <= {background_VGA_R, background_VGA_G, background_VGA_B}; //{8'h0,8'hff,8'h0};
						
					end else begin
						buffer_two_ptr <= NON_VISIBLE_OFFSET;
						buffer_two_write <= 0;
					end
					buffer_one_ptr <= hcount[10:1];
					if (hcount[0] == 1) begin
						buffer_one_write <= 1;
						buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
					end else begin
						buffer_one_write <= 0;
					end
					//buffer_two_data_in <= {8'h0,8'hff,8'h0};
				end else begin
					if (background_x_index>= background_x_start ) begin
						// only write first color read for x_pos
						buffer_one_ptr <= DISPLAY_PANE_LEFT + write_buffer_ptr;
						buffer_one_data_in <= {background_VGA_R, background_VGA_G, background_VGA_B}; //{8'h00,8'hff,8'h0};	
						buffer_one_write <= ( prev_x_pos != background_x_index);
						
					end else begin
						buffer_one_ptr <= NON_VISIBLE_OFFSET;
						buffer_one_write <= 0;
					end
					buffer_two_ptr <= hcount[10:1];
					if (hcount[0] == 1) begin
						buffer_two_write <= 1;
						buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
					end else begin
						buffer_two_write <= 0;
					end
				end	
				
			end else begin
				
				// read from sprite table here

				if((vcount +1) >= sprite_start_y_pos[9:0] && ({2'b0,vcount} +1 ) < sprite_start_y_pos + {2'b0,sprite_height} && ! sprite_read_delay) begin
					
					sprite_row_offset <= vcount[4:0] +1 - sprite_start_y_pos[4:0];

					if(sprite_col_offset == sprite_width[4:0] -1 || sprite_width[4:0]  == 5'b0) begin
						sprite_read_delay <= 1;
						if(sprite_index +1 == TOTAL_SPRITES) begin
							sprite_index <= 0;
						end else begin
							sprite_index <= sprite_index +1;
						end
					end

					if (sprite_ready) begin
						if(sprite_col_offset < sprite_width[4:0]) begin
							sprite_increment_x <= 1;
						end else begin
							sprite_increment_x <= 0;
						end
						if (sprite_active ) begin
							if (display_buffer_one_active) begin
								buffer_two_write <= 1;
								buffer_two_ptr <= sprite_start_x_pos[9:0] + {4'b0, sprite_col_offset[4:0]}; 
								buffer_two_data_in <= {sprite_table_VGA_R, sprite_table_VGA_G, sprite_table_VGA_B};
								buffer_one_ptr <= hcount[10:1];
								if (hcount[0] == 1) begin
									buffer_one_write <= 1;
									buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
								end else begin
									buffer_one_write <= 0;
								end
							end else begin
								buffer_one_write <= 1;
								buffer_one_ptr <= sprite_start_x_pos[9:0] + {4'b0, sprite_col_offset[4:0]}; 
								buffer_one_data_in <= {sprite_table_VGA_R, sprite_table_VGA_G, sprite_table_VGA_B};
								buffer_two_ptr <= hcount[10:1];
								if (hcount[0] == 1) begin
									buffer_two_write <= 1;
									buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
								end else begin
									buffer_two_write <= 0;
								end
							end
						end else begin
							if (display_buffer_one_active) begin
								buffer_two_write <= 0;
								buffer_two_ptr <= NON_VISIBLE_OFFSET;
								buffer_one_ptr <= hcount[10:1];
								if (hcount[0] == 1) begin
									buffer_one_write <= 1;
									buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
								end else begin
									buffer_one_write <= 0;
								end
							end else begin
								buffer_one_write <= 0;
								buffer_one_ptr <= NON_VISIBLE_OFFSET;
								buffer_two_ptr <= hcount[10:1];
								if (hcount[0] == 1) begin
									buffer_two_write <= 1;
									buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
								end else begin
									buffer_two_write <= 0;
								end
							end
						end
					end else begin
						sprite_increment_x <= 0;

						if (display_buffer_one_active) begin
							buffer_two_write <= 0;
							buffer_two_ptr <= NON_VISIBLE_OFFSET;
							buffer_one_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_one_write <= 1;
								buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
							end else begin
								buffer_one_write <= 0;
							end
						end else begin
							buffer_one_write <= 0;
							buffer_one_ptr <= NON_VISIBLE_OFFSET;
							buffer_two_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_two_write <= 1;
								buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
							end else begin
								buffer_two_write <= 0;
							end
						end

					end

				end else begin // if not, increment
					sprite_increment_x <= 0;
					if (sprite_ready && ! sprite_read_delay) begin
						sprite_read_delay <= 1;
						if(sprite_index +1 == TOTAL_SPRITES) begin
							sprite_index <= 0;
						end else begin
							sprite_index <= sprite_index +1;
						end
					end else begin
						sprite_read_delay <= 0;
					end
					
					if (display_buffer_one_active) begin
						buffer_one_ptr <= hcount[10:1];
						buffer_two_write <= 0;
						buffer_two_ptr <= NON_VISIBLE_OFFSET;
						if (hcount[0] == 1) begin
							buffer_one_write <= 1;
							buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
						end else begin
							buffer_one_write <= 0;
						end
					end else begin
						buffer_one_write <= 0;
						buffer_one_ptr <= NON_VISIBLE_OFFSET;
						buffer_two_ptr <= hcount[10:1];
						if (hcount[0] == 1) begin
							buffer_two_write <= 1;
							buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
						end else begin
							buffer_two_write <= 0;
						end
					end
					

				end
	
			end

		end else begin


			// check if this sprite is in range
			if((vcount +1) >= sprite_start_y_pos[9:0] && ({2'b0,vcount} +1 ) < sprite_start_y_pos + {2'b0,sprite_height} && ! sprite_read_delay) begin
				
				sprite_row_offset <= vcount[4:0] +1 - sprite_start_y_pos[4:0];

				if(sprite_col_offset == sprite_width[4:0] -1 || sprite_width[4:0] == 5'b0) begin
					sprite_read_delay <= 1;
					if(sprite_index +1 == TOTAL_SPRITES) begin
						sprite_index <= 0;
					end else begin
						sprite_index <= sprite_index +1;
					end
				end

				if (sprite_ready) begin
					if(sprite_col_offset < sprite_width[4:0]) begin
						sprite_increment_x <= 1;
					end else begin
						sprite_increment_x <= 0;
					end
					if (sprite_active ) begin
						if (display_buffer_one_active) begin
							buffer_two_write <= 1;
							buffer_two_ptr <= sprite_start_x_pos[9:0] + {4'b0, sprite_col_offset[4:0]}; 
							buffer_two_data_in <= {sprite_table_VGA_R, sprite_table_VGA_G, sprite_table_VGA_B};
							buffer_one_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_one_write <= 1;
								buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
							end else begin
								buffer_one_write <= 0;
							end
						end else begin
							buffer_one_write <= 1;
							buffer_one_ptr <= sprite_start_x_pos[9:0] + {4'b0, sprite_col_offset[4:0]}; 
							buffer_one_data_in <= {sprite_table_VGA_R, sprite_table_VGA_G, sprite_table_VGA_B};
							buffer_two_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_two_write <= 1;
								buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
							end else begin
								buffer_two_write <= 0;
							end
						end
					end else begin
						if (display_buffer_one_active) begin
							buffer_two_write <= 0;
							buffer_two_ptr <= NON_VISIBLE_OFFSET;
							buffer_one_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_one_write <= 1;
								buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
							end else begin
								buffer_one_write <= 0;
							end
						end else begin
							buffer_one_write <= 0;
							buffer_one_ptr <= NON_VISIBLE_OFFSET;
							buffer_two_ptr <= hcount[10:1];
							if (hcount[0] == 1) begin
								buffer_two_write <= 1;
								buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
							end else begin
								buffer_two_write <= 0;
							end
						end
					end
				end else begin
					sprite_increment_x <= 0;

					if (display_buffer_one_active) begin

						buffer_two_write <= 0;
						buffer_two_ptr <= NON_VISIBLE_OFFSET;

						buffer_one_ptr <= hcount[10:1];
						if (hcount[0] == 1) begin
							buffer_one_write <= 1;
							buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
						end else begin
							buffer_one_write <= 0;
						end
					end else begin

						buffer_one_write <= 0;
						buffer_one_ptr <= NON_VISIBLE_OFFSET;

						buffer_two_ptr <= hcount[10:1];
						if (hcount[0] == 1) begin
							buffer_two_write <= 1;
							buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
						end else begin
							buffer_two_write <= 0;
						end
					end

				end

			end else begin 
				
				// TODO: add section here to slide each pixel one time, once vcount is outside of visible range.
				
				sprite_increment_x <= 0;
				// if not, increment
				
				if (sprite_ready && ! sprite_read_delay) begin
					sprite_read_delay <= 1;
					if(sprite_index +1 == TOTAL_SPRITES) begin
						sprite_index <= 0;
					end else begin
						sprite_index <= sprite_index +1;
					end
				end else begin 
					sprite_read_delay <= 0;
				end

				if (display_buffer_one_active) begin
					
					buffer_two_write <= 0;
					buffer_two_ptr <= NON_VISIBLE_OFFSET;

					buffer_one_ptr <= hcount[10:1];
					if (hcount[0] == 1) begin
						buffer_one_write <= 1;
						buffer_one_data_in <= {8'h0,8'h0,8'h0}; // zero out previous value as it's read;
					end else begin
						buffer_one_write <= 0;
					end
				end else begin
					
					buffer_one_write <= 0;
					buffer_one_ptr <= NON_VISIBLE_OFFSET;

					buffer_two_ptr <= hcount[10:1];
					if (hcount[0] == 1) begin
						buffer_two_write <= 1;
						buffer_two_data_in <= {8'h0,8'h0,8'h0};  // zero out previous value as it's read;
					end else begin
						buffer_two_write <= 0;
					end
				end

			end

			background_ready_to_read <= 0;
			
		end

	end

	always_ff @(posedge clk) begin
		if(hcount == 1599) begin
			display_buffer_one_active <= !display_buffer_one_active;
			blank_output <= 1;
		end
		if (hcount == 1) begin
			blank_output <= 0;
		end
	end

	// note every pixel gets hcount cycles
	always_comb begin
		write_buffer_ptr = background_x_index[9:0] - background_x_start[9:0];

		if (blank_output) begin
			out_VGA_R = 8'b0;
			out_VGA_G = 8'b0;
			out_VGA_B = 8'b0;
		end else if (display_buffer_one_active) begin
			out_VGA_R = buffer_one_data_out[23:16];
			out_VGA_G = buffer_one_data_out[15:8];
			out_VGA_B = buffer_one_data_out[7:0];
		end else begin
			out_VGA_R = buffer_two_data_out[23:16];
			out_VGA_G = buffer_two_data_out[15:8];
			out_VGA_B = buffer_two_data_out[7:0];
		end
	end

	       
endmodule
