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

library i2c;

use work.ssd1306_pkg.all;

entity ssd1306_counter_master_logic is
  generic (
    DIGITS : natural;
    I2C_CLK_FREQ : integer);
  port (
    clk_i : in std_logic;
    rst_in : in std_logic;

    start_i : in std_logic;

    count_i : in std_logic_vector(DIGITS*4 - 1 downto 0);

    tx_valid_o : out std_logic;
    tx_ready_i : in std_logic;
    tx_data_o : out std_logic_vector(7 downto 0);

    dev_busy_i : in std_logic;
    waiting_i : in std_logic;
    any_err_i : in std_logic;

    state_o : out std_logic_vector(3 downto 0);
    substate_o : out std_logic_vector(2 downto 0));

end entity ssd1306_counter_master_logic;

architecture a1 of ssd1306_counter_master_logic is
  signal master_start, master_stop : std_logic;

  type state_t is (IDLE, INIT_DISPLAY, INIT_ADDRESSING_MODE, ZERO_OUT_RAM, SET_ADR_ZERO, DIGIT_N, SEC_DELAY);
  signal curr_state : state_t;
  signal next_state : state_t;

  signal curr_digit : natural;
  signal next_digit : natural;

  type substate_t is (IDLE, START, DATA, STOP, ALT);
  signal curr_substate : substate_t;
  signal next_substate : substate_t;

  signal digit_data : data_arr_t(0 to 8);

  signal curr_index : natural;
  signal next_index : natural;
  signal max_index : natural;

begin  -- architecture a1

  state_o <= std_logic_vector(to_unsigned(state_t'pos(curr_state), 4));
  substate_o <= std_logic_vector(to_unsigned(substate_t'pos(curr_substate), 3));


  max_index <= SSD1306_INIT'length when curr_state = INIT_DISPLAY else
               SSD1306_HORIZONTAL_ADDRESSING_MODE'length when curr_state = INIT_ADDRESSING_MODE else
               9 when curr_state = DIGIT_N else
               SSD1306_CURSOR_TO_ZERO'length when curr_state = SET_ADR_ZERO else
               I2C_CLK_FREQ/2 when curr_state = SEC_DELAY else
               SSD1306_ZERO_LEN when curr_state = ZERO_OUT_RAM else
               0;

  next_index <= 0 when curr_substate = IDLE else
                (curr_index + 1) when curr_substate = ALT and curr_index < max_index else
                (curr_index + 1) when tx_ready_i = '1' and curr_index < max_index and curr_substate = DATA else
                curr_index;

  tx_valid_o <= '1' when tx_ready_i = '1' and curr_index < max_index and curr_substate = DATA else '0';

  tx_data_o <= SSD1306_INIT(curr_index) when curr_state = INIT_DISPLAY and curr_index < max_index else
             SSD1306_HORIZONTAL_ADDRESSING_MODE(curr_index) when curr_state = INIT_ADDRESSING_MODE and curr_index < max_index else
             SSD1306_ZERO(0) when curr_state = ZERO_OUT_RAM and curr_index = 0 else
             SSD1306_ZERO(1) when curr_state = ZERO_OUT_RAM and curr_index /= 0 else
             SSD1306_CURSOR_TO_ZERO(curr_index) when curr_state = SET_ADR_ZERO and curr_index < max_index else
             digit_data(curr_index) when curr_state = DIGIT_N and curr_index < max_index else
             "00000000";

  next_digit <= 0 when curr_state /= DIGIT_N else
                (curr_digit + 1) when curr_state = DIGIT_N and curr_substate = STOP and curr_digit < DIGITS - 1 else
                curr_digit;

  digit_data <= ssd1306_bcd_digit_data(count_i(((DIGITS - 1 - curr_digit) + 1) * 4 - 1 downto (DIGITS - 1 - curr_digit) * 4));

  master_start <= '1' when curr_substate = START else '0';
  master_stop <= '1' when curr_substate = STOP else '0';

  set_next_state: process (all) is
  begin  -- process set_next_state
    next_state <= curr_state;

    if curr_state = IDLE then
      next_state <= INIT_DISPLAY;
    elsif curr_state = INIT_DISPLAY then
      if curr_substate = STOP then
        next_state <= INIT_ADDRESSING_MODE;
      end if;
    elsif curr_state = INIT_ADDRESSING_MODE then
      if curr_substate = STOP then
        next_state <= ZERO_OUT_RAM;
      end if;
    elsif curr_state = ZERO_OUT_RAM then
      if curr_substate = STOP then
        next_state <= SET_ADR_ZERO;
      end if;
    elsif curr_state = SET_ADR_ZERO then
      if curr_substate = STOP then
        next_state <= DIGIT_N;
      end if;
    elsif curr_state = DIGIT_N then
      if curr_substate = STOP and curr_digit = DIGITS - 1 then
        next_state <= SEC_DELAY;
      end if;
    elsif curr_state = SEC_DELAY then
      if curr_index = max_index then
        next_state <= SET_ADR_ZERO;
      end if;
    end if;

    if any_err_i = '1' then
      next_state <= IDLE;
    end if;
  end process set_next_state;

  set_next_substate: process (all) is
  begin  -- process set_next_state
    next_substate <= curr_substate;

    if curr_state /= IDLE and curr_state /= SEC_DELAY then
      if curr_substate = IDLE then
        if dev_busy_i = '0' then
          next_substate <= START;
        end if;
      elsif curr_substate = START then
        next_substate <= DATA;
      elsif curr_substate = DATA and curr_index = max_index then
        if waiting_i = '1' then
          next_substate <= STOP;
        end if;
      elsif curr_substate = STOP then
        next_substate <= IDLE;
      elsif curr_substate = ALT then
        next_substate <= IDLE;
      end if;
    else
      next_substate <= ALT;
    end if;
  end process set_next_substate;

  set_regs: process (clk_i) is
  begin  -- process set_regs
    if rising_edge(clk_i) then          -- rising clock edge
      if rst_in = '0' then                 -- synchronous reset (active low)
        curr_state <= IDLE;
        curr_substate <= IDLE;
        curr_digit <= 0;
        curr_index <= 0;
      else
        curr_state <= next_state;
        curr_substate <= next_substate;
        curr_digit <= next_digit;
        curr_index <= next_index;
      end if;
    end if;
  end process set_regs;

end architecture a1;
