library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity control is
  
  port (
    cpu_in     : in  std_logic_vector(7 downto 0);
    addr_in    : in  std_logic_vector(15 downto 0);
    cpu_read   : in  std_logic;
    cpu_write  : in  std_logic;
    ppu_clock  : in  std_logic;
    reset      : in  std_logic;
    vram_addr  : out std_logic_vector(13 downto 0);
    vram_data  : out std_logic_vector(7 downto 0);
    vram_write : out std_logic;
    loadsr1 : out std_logic;
    loadsr2 : out std_logic;
    sr1_data : out std_logic_vector(7 downto 0);
    sr2_data : out std_logic_vector(7 downto 0);
    vram_in : in std_logic_vector(7 downto 0);
    attrib_out : out std_logic_vector(1 downto 0);
    write_color : out std_logic;
    ppu_going : out std_logic;
    color_data : out std_logic_vector(7 downto 0);
    color_addr : out std_logic_vector(3 downto 0);
    vram_read  : out std_logic);
    
end control;
architecture behavior of control is

  
signal p2002, p2001, p2000, p2007 : std_logic_vector(7 downto 0);
signal p2006top, p2006bot : std_logic_vector(7 downto 0);
--signal loaded2006 : std_logic;
signal nametable, attrib, pattern1, pattern2 : std_logic_vector(13 downto 0);
signal namebyte, attribyte, pbyte1, pbyte2 : std_logic_vector(7 downto 0);
--signal go_do : std_logic;
signal current_state, next_state : std_logic_vector(3 downto 0);
--gnal hcount : std_logic_vector(7 downto 0);
--signal vcount : std_logic_vector(6 downto 0);
signal pixel_count, line_count : std_logic_vector(7 downto 0);
--signal reset_pixels : std_logic;
signal writex, writey : std_ulogic;
signal shit, shit1, shit2, loaded2006, go_do, reset_pixels : std_ulogic;
signal clock_tick : std_ulogic;

constant IDLE : std_logic_vector(3 downto 0) := "0000";
constant FETCH : std_logic_vector(3 downto 0) := "0001";
constant FETCH_A : std_logic_vector(3 downto 0) := "0010";
constant FETCH_P1 : std_logic_vector(3 downto 0) := "0011";
constant FETCH_P2 : std_logic_vector(3 downto 0) := "0100";
constant LOADPATS : std_logic_vector(3 downto 0) := "0101";
constant GO : std_logic_vector(3 downto 0) := "0110";
constant GET_ATTR : std_logic_vector(3 downto 0) := "0111";
constant GET_P1 : std_logic_vector(3 downto 0) := "1000";
constant GET_P2 : std_logic_vector(3 downto 0) := "1001";
constant LOAD : std_logic_vector(3 downto 0) := "1010";


begin  -- behavior

  process(ppu_clock, reset)
    begin
      if reset = '1' then
        shit <= '1';
        current_state <= IDLE;
        clock_tick <= '0';
      elsif ppu_clock = '1' and ppu_clock'event then
        clock_tick <= not(clock_tick);
        if addr_in = "0010000000000010" and cpu_read = '1' then
          p2002 <= "00000000";
          shit <= '0';
          current_state <= IDLE;
        else
          current_state <= next_state;
          shit <= '0';
        end if;
      end if;
    end process;

  process(writex, writey)
    begin
      if writex = '0' and writey = '0' then
        attrib_out <= attribyte(1 downto 0);
      elsif writex = '1' and writey = '0' then
        attrib_out <= attribyte(3 downto 2);
      elsif writex = '0' and writey = '1' then
        attrib_out <= attribyte(5 downto 4);
      elsif writex = '1' and writey = '1' then
        attrib_out <= attribyte(7 downto 6);
      end if;
    end process;

  process(ppu_clock, reset_pixels)
    begin
      if reset_pixels = '1' then
        pixel_count <= "00000000";
      elsif ppu_clock = '1' and ppu_clock'event then
        if pixel_count = "11111111" then
          pixel_count <= "00000000";
        else
          pixel_count <= pixel_count + 1;
        end if;
      end if;
    end process;

  process(pixel_count, reset_pixels)
    begin
      if reset_pixels = '1' then
        line_count <= "00000000" ;
      elsif pixel_count = "11111111" and line_count = 239 then
        line_count <= "00000000" ;
      elsif pixel_count = "11111111" then
        line_count <= line_count + 1;        
      end if;
    end process;

  process(pixel_count, line_count, reset, reset_pixels)
    begin
      if reset = '1' or reset_pixels = '1' then
        writex <= '0';
        writey <= '0';
      else
        if pixel_count(3 downto 0) = "1111" then
          writex <= not(writex);
        end if;
        if line_count(3 downto 0) = "1111" then
          writey <= not(writey);
        end if;
      end if;
    end process;

--  process(cpu_in, addr_in, cpu_read, cpu_write, current_state, reset, clock_tick)

    vram_write <= cpu_write when addr_in = "0010000000000111" else
                  '0';

    vram_read <= '0' when cpu_write = '1' and addr_in = "0010000000000111" else
                 '1';
                  
    vram_data <= cpu_in when addr_in = "0010000000000111" else
                 "00000000";


    ppu_going <= '0' when current_state = IDLE else '1';
    
    process(clock_tick, current_state)
    begin    

      reset_pixels <= '0';
      loadsr1 <= '0';
      loadsr2 <= '0';
--      vram_read <= '0';
      
      case current_state is
        when IDLE =>
          if shit = '1' then
            loaded2006 <= '0';
            go_do <= '0';
            reset_pixels <= '1';
            next_state <= IDLE;
          end if;
          -- here it handles all vram writes, etc.
          vram_addr <= p2006top(5 downto 0) & p2006bot(7 downto 0);
          

          -- for sram mux
--           if addr_in(3 downto 0) = "0111" then
--             write2007 <= '1';
--           else
--             write2007 <= '0';
--           end if;
          
--            shit2 <= '1';
            if addr_in(15 downto 12) = "0010" then
              if cpu_write = '1' then
                shit1 <= '1';
                if addr_in(3 downto 0) = "0000" then
                  p2000 <= cpu_in;
                  next_state <= IDLE;
                elsif addr_in(3 downto 0) = "0001" then
                  p2001 <= cpu_in;
                  if go_do = '0' then
                    go_do <= '1';
                    next_state <= IDLE;
                  else
                    go_do <= '0';
                    next_state <= FETCH;
                    --tilecount <= "0000000000";                    
                    -- will need to fetch first nametable byte
                    nametable <= "10" & p2000(1 downto 0) & "0000000000";
                    vram_addr <= "10" & p2000(1 downto 0) & "0000000000";
                  end if;
                elsif addr_in(3 downto 0) = "0110" then 
                  if loaded2006 = '0' then
                    p2006top <= cpu_in;
                    loaded2006 <= '1';
                    next_state <= IDLE;
                  else
                    p2006bot <= cpu_in;
                    loaded2006 <= '0';
                    next_state <= IDLE;
                  end if;
                elsif addr_in(3 downto 0) = "0111" then                  
                  p2007 <= cpu_in;
                  --vram_data <= cpu_in;
                  --vram_write <= '1';
                  next_state <= IDLE;
                  if p2000(2) = '1' then
                    p2006bot <= p2006bot + 32;
                  else
                    p2006bot <= p2006bot + 1;
                  end if;                
                end if;
                if p2006top(5 downto 0) & p2006bot(7 downto 4) = "1111110000" then
                  -- writing to pallete
                  color_addr <= p2006bot(3 downto 0);
                  write_color <= '1';
                  color_data <= cpu_in; --p2007;              
                else
                  write_color <= '0';            
                end if;
              end if;
            end if;




        when FETCH =>
--          if ppu_clock = '1' and ppu_clock'event then
          -- commenting for latching testing purposes
            --vram_addr <= nametable;
--            vram_read <= '1';
            namebyte <= vram_in;            
            if p2000(2) = '1' then
              nametable <= nametable + 32;
            else
              nametable <= nametable + 1;
            end if;

            attrib <= nametable + 960;
            vram_addr <= nametable + 960;
            if attrib = "10001111010000" then
              attrib <= "10001111011000";
              vram_addr <= "10001111011000";
            end if;
            
            reset_pixels <= '1';
            -- location of first attribute byte
            next_state <= FETCH_A;
--          end if;
          
        when FETCH_A =>
--          if ppu_clock = '1' and ppu_clock'event then        
            --vram_addr <= attrib;
--            vram_read <= '1';
            attribyte <= vram_in;
          -- now find location of pattern table byte 1
            if p2000(4) = '1' then
              pattern1 <= "01" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
              vram_addr <= "01" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
            else
              pattern1 <= "00" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
              vram_addr <= "00" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
            end if;
            next_state <= FETCH_P1;
--          end if;

        when FETCH_P1 =>
--          if ppu_clock = '1' and ppu_clock'event then        
--            vram_addr <= pattern1;
--            vram_read <= '1';
            pbyte1 <= vram_in;
            -- now find location of pattern table byte 2
            if p2000(4) = '1' then
              pattern2 <= "01" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
              vram_addr <= "01" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
            else
              pattern2 <= "00" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
              vram_addr <=  "00" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
            end if;
            next_state <= FETCH_P2;
--          end if;

        when FETCH_P2 =>
--          if ppu_clock = '1' and ppu_clock'event then
--            vram_addr <= pattern2;
--            vram_read <= '1';
            pbyte2 <= vram_in;
            -- now both shift registers are loaded with the first two bytes to
            -- make the first 8 pixels
            reset_pixels <= '1';
            next_state <= LOADPATS;
--          end if;

        when LOADPATS =>
--          if ppu_clock = '1' and ppu_clock'event then
            sr1_data <= pbyte1;
            sr2_data <= pbyte2;
            loadsr1 <= '1';
            loadsr2 <= '1';
            reset_pixels <= '1';
            vram_addr <= nametable;
            next_state <= GO;
--          end if;
          
        when GO =>
          -- this is the main function loop
          -- will need to grab a new nametable byte before 8 pixels are drawn
          --      will need to update address after every fetch and at end of line
          --      if pixel_count = 255 then need to subtract 32 or 32*32 from
          --      nametable address based on p2000
          -- will need to grab a new attribute byte if necessary for next tile
          --      attribute table address will be updated in LOAD state
          -- will need to grab two pattern table bytes and load them when
          --      pixel_count(2 downto 0) = "111"
          -- already incremented nametable byte address in FETCH state
--          if ppu_clock = '1' and ppu_clock'event then      
--            vram_addr <= nametable;
--            vram_read <= '1';
          vram_addr <= attrib;
          if attrib = "10001111010000" then
              attrib <= "10001111011000";
              vram_addr <= "10001111011000";
          end if;
          namebyte <= vram_in;
          next_state <= GET_ATTR;
  --              end if;

        when GET_ATTR =>
--          if ppu_clock = '1' and ppu_clock'event then
--          vram_addr <= attrib;
--            vram_read <= '1';
            attribyte <= vram_in;
            -- update pattern 1 address
            if p2000(4) = '1' then
              pattern1 <= "01" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
              vram_addr <=  "01" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
            else
              pattern1 <= "00" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
              vram_addr <=  "00" & namebyte(7 downto 0) & '0' & line_count(2 downto 0);
            end if;
            next_state <= GET_P1;
--          end if;

        when GET_P1 =>
--          if ppu_clock = '1' and ppu_clock'event then          
--            vram_addr <= pattern1;
--            vram_read <= '1';
            pbyte1 <= vram_in;
            if p2000(4) = '1' then
              pattern2 <= "01" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
              vram_addr <=  "01" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
            else
              pattern2 <= "00" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
              vram_addr <= "00" & namebyte(7 downto 0) & '1' & line_count(2 downto 0);
            end if;
            next_state <= GET_P2;
--          end if;

          
        when GET_P2 =>
--          if ppu_clock = '1' and ppu_clock'event then          
--            vram_addr <= pattern2;
--            vram_read <= '1';
            pbyte2 <= vram_in;
            next_state <= LOAD;
--          end if;

        when LOAD =>
--         if ppu_clock = '1' and ppu_clock'event then
            if pixel_count(2 downto 0) = "111" then
              loadsr1 <= '1';
              sr1_data <= pbyte1;
              loadsr2 <= '1';
              sr2_data <= pbyte2;
              -- name table address update
              -- update by 1 or 32 if tile is done
              -- subtract by 32 or 32*32 if at end of line, but not row of tiles
              -- update by 1 or 32 if row is done
              if pixel_count = 255 then
                if line_count(2 downto 0) = "111" then  -- done with a row of tiles
                  if p2000(2) = '1' then
                    nametable <= nametable + 32;
                  else
                    nametable <= nametable + 1;
                  end if;
                else
                  if p2000(2) = '1' then
                    nametable <= nametable - 992;  -- offsets may be wrong
                  else
                    nametable <= nametable - 31;
                  end if;
                end if;
              else
                -- pixel_count(2 downto 0) = "111" but not at end of line
                if p2000(2) = '1' then
                  nametable <= nametable + 32;
                  vram_addr <= nametable + 32;
                else
                  nametable <= nametable + 1;
                  vram_addr <= nametable + 1;
                end if;
              end if;                   -- end if 255 for nametable

              -- update attribute address
              if pixel_count = 255 then
                if line_count(4 downto 0) = "11111" then
                  attrib <= attrib + 1;
                else
                  attrib <= attrib - 7;
                end if;
              else
                if pixel_count(4 downto 0) = "11111" then
                  attrib <= attrib + 1;
                end if;                
              end if;

              -- check if at end of screen
              if pixel_count = 255 and line_count = 240 then
                nametable <= "01" & p2000(1 downto 0) & "0000000000";
                vram_addr <=  "01" & p2000(1 downto 0) & "0000000000";
                attrib <= "01" & p2000(1 downto 0) & "1111000000";  -- 960 offset
                reset_pixels <= '1';
                next_state <= FETCH;
              else
                next_state <= GO;
              end if;
--            next_state <= GO;
            else
              next_state <= LOAD;
            end if;
--          end if;
          
          
        when others => null;
      end case;
    end process;

end behavior;

