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

entity key_hash_generator is
    port (
        clk             : in std_logic;
        reset           : in std_logic;
        
        data_in         : in std_logic_vector(WORD_WIDTH-1 downto 0);
        valid_in        : in std_logic;
        ready_in        : out std_logic;
        last_word_in    : in std_logic;
        
        data_out        : out std_logic_vector(WORD_WIDTH-1 downto 0);
        valid_out       : out std_logic;
        ready_out       : in std_logic;  
        last_word_out   : out std_logic
    );
end entity;

architecture arch of key_hash_generator is
    
    --*****CONSTANT DECLARATION*****
    constant KEY_SIZE : natural := TODO;
    
    --*****TYPE DECLARATION*****
    type STAGE_TYPE is (IDLE, TODO);
    
    --*****SIGNAL DECLARATION*****
    signal key_gen_start, key_gen_busy, key_gen_valid_in, key_gen_ready_in, key_gen_last_word_in, key_gen_valid_out, key_gen_ready_out, key_gen_last_word_out : std_logic := '0';
    signal key_gen_opcode : KEY_GENERATOR_OPCODE_TYPE := NOP;
    signal key_gen_data_in, key_gen_data_out : std_logic_vector(WORD_WIDTH-1 downto 0) := (others => '0');
    
    signal stage, stage_next : STAGE_TYPE := IDLE;
    signal key_hash, key_hash_next  : KEY_HASH_TYPE := (others => (others => '0'));
    signal cnt, cnt_next : natural range 0 to KEY_HASH_TYPE'length-1 := 0;
    
begin
    
    key_generator_inst : entity work.key_generator(arch)
        port map(
            clk             => clk,
            reset           => reset,
            start           => key_gen_start,
            opcode          => key_gen_opcode,
            busy            => key_gen_busy,
            data_in         => key_gen_data_in,
            valid_in        => key_gen_valid_in,
            ready_in        => key_gen_ready_in,
            last_word_in    => key_gen_last_word_in,
            data_out        => key_gen_data_out,
            valid_out       => key_gen_valid_out,
            ready_out       => key_gen_ready_out,
            last_word_out   => key_gen_last_word_out
        );
    
    
    main_prc : process (all)
    begin
        -- DEFAULT
        stage_next              <= stage;
        key_hash_next           <= key_hash;
        cnt_next                <= cnt;
        ready_in                <= '0';
        key_gen_start           <= '0';
        key_gen_valid_in        <= '0';
        key_gen_last_word_in    <= '0';
        key_gen_data_in         <= (others => '0');
        key_gen_ready_out       <= '0';
        valid_out               <= '0';
        last_word_out           <= '0';
        data_out                <= (others => '0');
        
        case (stage) is
            when IDLE =>
                if (valid_in = '1' and key_gen_busy = '0') then
                    key_gen_start   <= '1';
                    key_gen_opcode  <= WRITE_PAYLOAD;
                    stage_next      <= PASSTHROUGH_PAYLOAD;
                end if;
            when PASSTHROUGH_PAYLOAD =>
                -- TODO: Connect directly to key_generator?
                ready_in                <= key_gen_ready_in;
                key_gen_valid_in        <= valid_in;
                key_gen_data_in         <= data_in;
                key_gen_last_word_in    <= last_word_in;
                
                -- Exit Condition
                if (last_word_in = '1') then
                    stage_next  <= GET_SIZE;
                end if;
            when GET_SIZE =>
                if (key_gen_busy = '0') then
                    key_gen_start   <= '1';
                    key_gen_opcode  <= READ_SIZE;
                    stage_next      <= READ_SIZE;
                end if;
            when READ_SIZE =>
                key_gen_ready_out   <= '1';
                if (key_gen_valid_out = '1') then
                    if (unsigned(key_gen_data_out)) > (KEY_HASH_WIDTH/BYTE_WIDTH)) then
                        -- TODO: MD5 Calculation
                        assert FALSE report "MD5 Key hash generation not yet implemented." severity FAILURE;
                    else
                        stage_next      <= GET_KEY_HASH;
                    end if;
                end if;
            when GET_KEY_HASH =>
                if (key_gen_busy = '0') then
                    key_gen_start   <= '1';
                    key_gen_opcode  <= READ_KEY;
                    
                    stage_next      <= WRITE_KEY_HASH;
                    cnt_next        <= 0;
                    key_hash_next   <= (others => (others => '0'));
                end if;
            when WRITE_KEY_HASH =>
                key_gen_ready_out   <= '1';
                if (key_gen_valid_out) then
                    cnt_next    <= cnt + 1;
                    
                    key_hash_next(cnt)  <= key_gen_data_out;
                    
                    if (key_gen_last_word_out = '1') then
                        stage_next  <= FINISHED_KEY_HASH;
                        cnt_next    <= 0;
                    end if;
                end if;
            when FINISHED_KEY_HASH =>
                valid_out   <= '1';
                if (ready_out = '1') then
                    cnt_next    <= cnt + 1;
                    
                    data_out    <= key_hash(cnt);
                    
                    if (cnt = key_hash'length-1) then
                        last_word_out   <= '1';
                        -- DONE
                        stage_next      <= IDLE;
                    end if;
                end if;
            when others =>
                null;
        end case;
    end process;
    
end architecture;