module viewtube_background_layer(
	input logic         clk,
	input logic 	    reset,
	input logic			ready_to_read,
	input logic			seek_to_x_pos,
	input logic [31:0]  writedata,
	input logic 	    write,
//    input logic [23:0]  internal_offset,
//	input logic [10:0]  new_window_x,
//	input logic [10:0]  new_window_y,
	input logic [1:0]   address,
	input logic 		sync_movement,
	input logic 		go_to_beginning,
	output logic [7:0]  VGA_R, VGA_G, VGA_B,
	output logic        ready,
	output logic        active,
	output logic [11:0] x_pos,
	output logic [11:0] y_pos,
	output logic [11:0] window_x,
	output logic [11:0] window_y
//	output logic [10:0] width,
//	output logic [9:0]  height
);

	parameter BACKGROUND_ROM="roms/background_map.compressed.mem";
	parameter MEM_WIDTH=12;
	parameter MEM_ROW=95939;
	parameter MEM_ROW_ADDR_W=$clog2(MEM_ROW);
	parameter WINDOW_WIDTH = 500;
	parameter WINDOW_HEIGHT = 425;
	parameter GRAPHIC_WIDTH = 1500;
	parameter GRAPHIC_HEIGHT = 1814;
	parameter WINDOW_PIXELS = WINDOW_WIDTH*WINDOW_HEIGHT;
	parameter GRAPHIUC_PIXELS = GRAPHIC_WIDTH*GRAPHIC_HEIGHT;



	logic [MEM_WIDTH-1:0] cur_row_data;
	logic [MEM_ROW_ADDR_W-1:0] mem_row_ptr;
	logic [23:0]  graphic_offset;
	logic [7:0]   counter;
	logic [7:0]   count;
	logic [3:0]   color;
	logic delay_needs_to_be_processed;

	initial delay_needs_to_be_processed = 0;

	

	logic [MEM_ROW_ADDR_W-1:0] window_start_mem_row_ptr;
	logic [7:0] window_start_counter;
	logic [23:0] window_start_graphic_offset;

	logic [MEM_ROW_ADDR_W-1:0] window_checkpoint_mem_row_ptr;
	logic [7:0] window_checkpoint_counter;
	logic [23:0] window_checkpoint_graphic_offset;
	logic [11:0] window_checkpoint_x_pos;
	logic [11:0] window_checkpoint_y_pos;

	logic [11:0] jump_pos;
	logic [11:0] dist_to_edge;

	logic [11:0]  new_window_x;
    logic [11:0]  new_window_y;

	logic new_window_bounce;

	logic [1:0] read_delay;

	initial graphic_offset = 24'b0;
	initial counter = 0;
	initial active = 0;
	initial x_pos = 0;
	initial y_pos = 0;
	initial new_window_bounce = 0;
	initial read_delay = 2'b0;



	initial window_x = 12'd156;
	initial window_y = 12'd868;
	initial new_window_x = 12'd156;
	initial new_window_y = 12'd868;

	

	

	viewtube_static_memory #(
        .WIDTH(MEM_WIDTH),  
        .ROW(MEM_ROW),
        .ROM_FILE(BACKGROUND_ROM)
	) background_memory (
        .addr(mem_row_ptr),
        .data_out(cur_row_data),
		.clk(clk)
    );





	always_ff @(posedge clk) begin

		//reset
		if(reset) begin 
			mem_row_ptr <= 0; 
			graphic_offset <= 0;
			counter <= 0;		
			window_start_counter <=0;
			window_start_graphic_offset <= 0;
			window_start_mem_row_ptr <= 0;
			window_checkpoint_mem_row_ptr <= 0;
			window_checkpoint_counter <= 0;
			window_checkpoint_graphic_offset <= 0;
			window_checkpoint_x_pos <= 0;
			window_checkpoint_y_pos <= 0;
			active <= 0;
			x_pos <= 0;
			y_pos <= 0;
			new_window_bounce <= 0;
			//window_x <= 12'd0;
			//window_y <= 12'd0;
			//new_window_x <= 12'd0;
			//new_window_y <= 12'd0;

		end else begin

			/**
			 * Memory IO	
			 **/
			if(write) begin
				case (address)
						2'h0 : begin
							new_window_bounce <= writedata[16];
							//jump: 0->no; 1->yes

							if(writedata[24]) begin
								// 1 -> y axis
								if (writedata[11:0] < GRAPHIC_HEIGHT - WINDOW_HEIGHT)
									new_window_y <= writedata[11:0];
							end else begin
								// 0 -> x axis
								if(writedata[11:0] < GRAPHIC_WIDTH - WINDOW_WIDTH)
									new_window_x <= writedata[11:0];
							end
						end
						2'h1 : active <= 1;
						2'h2 : active <= 1;
						2'h3 : active <= 1;
						default : active <= 1;
				endcase
			end
			// hcount and vcount are such that 
			// it's time to move the window one click
			if(sync_movement) begin

				// if we are going to move, reset everything
				if (new_window_x != window_x || new_window_y != window_y ) begin
					mem_row_ptr <= 0; 
					graphic_offset <= 0;
					counter <= 0;		
					window_start_counter <= 0;
					window_start_graphic_offset <= 0;
					window_start_mem_row_ptr <= 0;
					x_pos <= 0;
					y_pos <= 0;
					read_delay <= 2'd1;
					window_checkpoint_counter<= 0;
					window_checkpoint_x_pos<= 0;
					window_checkpoint_graphic_offset <= 0;
					window_checkpoint_y_pos <= 0;
					window_checkpoint_mem_row_ptr <= 0;
				end 
				if(new_window_bounce) begin
					window_x <= new_window_x;
					window_y <= new_window_y;
				end else begin

					if (new_window_x > window_x) begin
						window_x <= window_x + 1;
					end else if (new_window_x < window_x) begin
						window_x <= window_x -1;
					end

					if(new_window_y > window_y) begin
						window_y <= window_y + 1;
					end else if (new_window_y < window_y) begin
						window_y <= window_y - 1;
					end
				end

			end else if (go_to_beginning) begin
				counter <= 0;
				x_pos <= 0;
				graphic_offset <= 0;
				y_pos <= 0;
				mem_row_ptr <= 0;
				read_delay <= 1;
			end else begin
			
				if (x_pos == window_x && y_pos == window_y) begin
					window_start_counter <= counter;
					window_start_graphic_offset <= graphic_offset;
					window_start_mem_row_ptr <= mem_row_ptr ;
				end

				if (counter == 0 && y_pos+2 == window_y && read_delay == 0) begin
					window_checkpoint_counter <= counter;
					window_checkpoint_graphic_offset <= graphic_offset;
					window_checkpoint_mem_row_ptr <= mem_row_ptr ;
					window_checkpoint_x_pos <= x_pos;
					window_checkpoint_y_pos <= y_pos;
				end
							
				
				if ( ready_to_read || delay_needs_to_be_processed) begin
					// iterate in single steps

					// if we are at the end of a encoded block
					if (read_delay != 0) begin
						read_delay <= read_delay -1;
					// catch an edge case where we read a single bit
					end else if (count == 1 && count == counter) begin
						mem_row_ptr <= mem_row_ptr + 1;
						read_delay <= 2'd1;
						delay_needs_to_be_processed <= 1;
						counter <= 0;
					end else if (counter+1 == count ) begin		
							
							mem_row_ptr <= mem_row_ptr + 1;
							graphic_offset  <= graphic_offset +1;
							counter <= 0;
							if(x_pos == GRAPHIC_WIDTH -1) begin
								x_pos <= 0;
								y_pos <= y_pos +1;
							end else begin
								x_pos <= x_pos +1;
							end
							if(counter == 0 ) begin
								read_delay <= 2'd1;
							end else if ( x_pos+1 >=  window_x +WINDOW_WIDTH) begin
								//  accounts for edge case when ready_to_ready goes low next cycle
								// and we need a read delay for the decompression phase
								read_delay <= 2'd1;
							end
						//end
					// if we are at the end of an active window region when not at end of memory block
					end else begin	
						// if not at end, step one		
						counter <= counter + 1;
						delay_needs_to_be_processed <= 0;
						graphic_offset <= graphic_offset +1;
						if(x_pos == GRAPHIC_WIDTH -1) begin
							x_pos <= 0;
							y_pos <= y_pos +1;
						end else begin
							x_pos <= x_pos +1;
						end
					end


				end else if ( seek_to_x_pos && ( ! ready ) || delay_needs_to_be_processed  ) begin 
					// iterate in chunks

					if (read_delay != 0) begin
						read_delay <= read_delay -1;
					end else if (count == counter ) begin
						//account for an edge case during transition from single-step reading
						mem_row_ptr <= mem_row_ptr + 1;
						read_delay <= 2'd1;
						delay_needs_to_be_processed <= 1;
						counter <= 0; 
						
						/*read_delay <= 2'd1;
						counter <= 0;
						mem_row_ptr <= mem_row_ptr + 1;
						delay_needs_to_be_processed <= 1;
						if(x_pos == GRAPHIC_WIDTH ) begin
							x_pos <= 0;
							y_pos <= y_pos +1;
						end else begin
							x_pos <= x_pos +1;
						end*/
					end else if (y_pos < window_y ) begin

			
						if (jump_pos < GRAPHIC_WIDTH && jump_pos > x_pos) begin
							x_pos <= jump_pos ;
							graphic_offset <= graphic_offset + ({12'b0,jump_pos} - {12'b0,x_pos}) ;
							counter <= 0;
							mem_row_ptr <= mem_row_ptr + 1;
							read_delay <= 2'd1;
						end else if (x_pos <= GRAPHIC_WIDTH ) begin
							x_pos <= 0;
							y_pos <= y_pos +1;
							graphic_offset <= graphic_offset + {12'b0, dist_to_edge} ;
							if (counter + dist_to_edge[7:0] == count) begin
								counter <= 0;
								mem_row_ptr <= mem_row_ptr + 1;
								read_delay <= 2'd1;
							end else begin
								counter <= counter + dist_to_edge[7:0];
							end
						end
					// if we extend further beyond the region somehow
					end else if ( jump_pos < window_x && jump_pos > x_pos ) begin 
						// consume full count chunk
						x_pos <= jump_pos ;
						graphic_offset <= graphic_offset + ({12'b0,jump_pos} - {12'b0,x_pos}) ;
						counter <= 0;
						mem_row_ptr <= mem_row_ptr + 1;
						read_delay <= 2'd1;
					end else if (x_pos < window_x) begin 
						// consume whatever is left, left of window
						x_pos <= window_x;
						graphic_offset <= (graphic_offset + {12'b0,window_x}) -  {12'b0,x_pos};
						if( counter + (window_x[7:0] - x_pos[7:0]) == count) begin
								counter <= 0;
								mem_row_ptr <= mem_row_ptr + 1;
								read_delay <= 2'd1;
						end else begin
							counter <= counter + (window_x[7:0] - x_pos[7:0]);
						end
					end else if ( (x_pos >= window_x + WINDOW_WIDTH) && (jump_pos < GRAPHIC_WIDTH && jump_pos > x_pos)) begin
						// consume full chunk right of window
						x_pos <= jump_pos ;
						graphic_offset <= graphic_offset + ({12'b0,jump_pos} - {12'b0,x_pos}) ;
						counter <= 0;
						mem_row_ptr <= mem_row_ptr + 1;
						read_delay <= 2'd1;
					end else if ((x_pos >= window_x+WINDOW_WIDTH) && x_pos <= GRAPHIC_WIDTH) begin
						// consume whatever is left to edge of graphic
						
						if (y_pos >= window_y + WINDOW_HEIGHT || mem_row_ptr == MEM_ROW) begin
							/*x_pos <= window_x;
							y_pos <= window_y;
							mem_row_ptr <= window_start_mem_row_ptr;
							read_delay <= 2'd1;
							graphic_offset  <= window_start_graphic_offset ;
							counter <= window_start_counter;*/

						end else begin
							y_pos <= y_pos +1;
							x_pos <= 0;
							graphic_offset <= graphic_offset + {12'b0, dist_to_edge} ;
							if(counter + dist_to_edge[7:0] == count) begin
								counter <= 0;
								mem_row_ptr <= mem_row_ptr + 1;
								read_delay <= 2'd1;
							end else begin
								counter <= counter + dist_to_edge[7:0];
							end
						end
					end
				end else begin
					if(read_delay != 2'd0) begin

						read_delay <= read_delay -1;

					end
				end
			end	
		end
	end

	// make color and count always match value in memory
	always_comb begin
		{VGA_R, VGA_G, VGA_B} = {8'h0, 8'h0, 8'h0};
		color = cur_row_data[3:0];
		count = cur_row_data[MEM_WIDTH-1:MEM_WIDTH-8];
		jump_pos = ((x_pos ) + {3'b0, count}) - {3'b0,counter};
		dist_to_edge = GRAPHIC_WIDTH - x_pos;
		ready = (x_pos >= window_x ) && (x_pos < window_x +WINDOW_WIDTH) &&
		(y_pos >= window_y) && 
		(y_pos <= window_y+WINDOW_HEIGHT) ;
		case (color)
	      4'h0 :{VGA_R, VGA_G, VGA_B} = {8'h0, 8'h0, 8'h0}; // black
		  4'h1 :{VGA_R, VGA_G, VGA_B} = {8'hfe, 8'h04, 8'h0}; // red
		  4'h2 :{VGA_R, VGA_G, VGA_B} = {8'hba, 8'he3, 8'ha9}; // land-green
		  4'h3 :{VGA_R, VGA_G, VGA_B} = {8'hff, 8'hff, 8'hff}; // white
		  4'h4 :{VGA_R, VGA_G, VGA_B} = {8'hff, 8'hff, 8'hd4}; // island-beige
		  4'h5 :{VGA_R, VGA_G, VGA_B} = {8'h5b, 8'hac, 8'hb4}; // water-blue
		  4'h6 :{VGA_R, VGA_G, VGA_B} = {8'hf5, 8'h71, 8'h11}; //orange
		  4'h7 :{VGA_R, VGA_G, VGA_B} = {8'h0b, 8'h67, 8'hbe}; // track blue
		  4'h8 :{VGA_R, VGA_G, VGA_B} = {8'h7e, 8'hce, 8'h49}; // track green
		  4'h9 :{VGA_R, VGA_G, VGA_B} = {8'hff, 8'hd4, 8'h0d}; // track yellow
		  4'ha :{VGA_R, VGA_G, VGA_B} = {8'ha8, 8'h49, 8'ha4}; // purple
		  4'hb :{VGA_R, VGA_G, VGA_B} = {8'h97, 8'h93, 8'h91}; // gray
		  4'hc :{VGA_R, VGA_G, VGA_B} = {8'hae, 8'h66, 8'h00}; // brown
	      4'hd :{VGA_R, VGA_G, VGA_B} = {8'h2d, 8'h2c, 8'h1a}; // dark gray
		  4'he :{VGA_R, VGA_G, VGA_B} = {8'h00, 8'h9f, 8'h57}; // dark green
		  4'hf :{VGA_R, VGA_G, VGA_B} = {8'hff, 8'h0, 8'h0};
          default : {VGA_R, VGA_G, VGA_B} = {8'h0, 8'h0, 8'h0};
       endcase
	end
	

endmodule