module uart_io_handler #(
  parameter BAUDRATE  = 115200
)(
  //input/output signals
  input               clk,
  input               rst,

  //input handler
  output reg          o_ih_ready,
  output              o_ih_reset,
  input               i_master_ready,

  output reg [31:0]   o_in_command,
  output reg [31:0]   o_in_address,
  output reg [31:0]   o_in_data,
  output reg [27:0]   o_in_data_count,


  //output handler
  output  reg         o_oh_ready,
  input               i_oh_en,

  input [31:0]        i_out_status,
  input [31:0]        i_out_address,
  input [31:0]        i_out_data,
  input [27:0]        i_out_data_count,

  //these are the only thing that are different between xxx_io_handler
  input               i_phy_uart_in,
  output              o_phy_uart_out
);


//STATES
localparam IDLE             = 4'h0;
localparam READ_DATA_COUNT  = 4'h1;
localparam READ_CONTROL     = 4'h2;
localparam READ_ADDRESS     = 4'h3;
localparam READ_DATA        = 4'h4;
localparam WRITE_TO_MASTER  = 4'h5;

localparam WRITE_DATA_COUNT = 4'h1;
localparam WRITE_STATUS     = 4'h2;
localparam WRITE_ADDRESS    = 4'h3;
localparam WRITE_DATA       = 4'h4;
localparam SEND_DATA_TO_HOST= 4'h5;

localparam CHAR_L           = 8'h4C;

localparam CHAR_0           = 8'h30;
localparam CHAR_HEX_OFFSET  = 8'h37;
localparam CHAR_A           = 8'h41;
localparam CHAR_F           = 8'h46;
localparam CHAR_S           = 8'h53;

//Registers/Wires
reg     [3:0]       in_state;
reg     [3:0]       out_state;

reg     [3:0]       in_nibble_count;
reg     [3:0]       out_nibble_count;
reg     [15:0]      r_count;


wire    [15:0]      user_command;
wire                is_writing;
wire                is_reading;

reg     [7:0]       out_byte;
wire                in_byte_available;
wire    [7:0]       in_byte;
wire                uart_in_busy;
reg     [27:0]      in_data_count;

wire                uart_out_busy;
wire                uart_tx_ready;
reg                 uart_out_byte_en;
reg                 oh_finished;
reg                 uart_wait_for_tx;

reg     [27:0]      li_out_data_count;
reg     [27:0]      li_out_data_count_buf;
reg     [27:0]      out_data_count;
reg     [27:0]      out_data_pos;
reg     [31:0]      li_out_data;
reg     [31:0]      li_out_status;
reg     [31:0]      li_out_address;

wire    [3:0]       in_nibble;
wire                illegal_value;

wire    [7:0]       gen_data_count;
wire    [7:0]       gen_status;
wire    [7:0]       gen_addr;
wire    [7:0]       gen_data;

//Submodules
uart #(
  .DEFAULT_BAUDRATE   (BAUDRATE           )
)uart(
  .clk                (clk                ),
  .rst                (rst                ),

  .tx                 (o_phy_uart_out     ),
  .transmit           (uart_out_byte_en   ),
  .tx_byte            (out_byte           ),
  .is_transmitting    (uart_out_busy      ),

  .rx                 (i_phy_uart_in      ),
  .rx_error           (                   ),
  .rx_byte            (in_byte            ),
  .received           (in_byte_available  ),
  .is_receiving       (uart_in_busy       ),

  .prescaler          (                   ),

  .set_clock_div      (1'b0               ),

  .user_clock_div     (def_clock_div      ),
  .default_clock_div  (def_clock_div      )
);

//Asynchronous Logic
assign          user_command   =  o_in_command[15:0];
assign          is_writing     =  (user_command == `COMMAND_WRITE);
assign          is_reading     =  (user_command == `COMMAND_READ);
assign          o_ih_reset     =  0;
assign          uart_tx_ready  =  ~uart_out_busy;
assign          in_nibble      =  (in_byte >= CHAR_A) ? (in_byte - CHAR_HEX_OFFSET) :
                                                        (in_byte - CHAR_0);
assign          illegal_value  =  (in_byte < CHAR_0) || ((in_byte > CHAR_0 + 10) && (in_byte < CHAR_A)) || (in_byte > CHAR_F);

assign          gen_data_count  = (li_out_data_count_buf[27:24] < 10) ? li_out_data_count_buf[27:24] + CHAR_0 : li_out_data_count_buf[27:24] + CHAR_HEX_OFFSET;
assign          gen_status      = (li_out_status[31:28] < 10) ? li_out_status[31:28] + CHAR_0 : li_out_status + CHAR_HEX_OFFSET;
assign          gen_address     = (li_out_address[31:28] < 10) ? li_out_address[31:28] + CHAR_0 : li_out_address[31:28] + CHAR_HEX_OFFSET;
assign          gen_data        = (li_out_data[31:28] < 10) ? li_out_data[31:28] + CHAR_0 : li_out_data[31:28] + CHAR_HEX_OFFSET;

//Synchronous Logic
//input handler
always @ (posedge clk) begin

  o_ih_ready                <= 0;

  if (rst) begin
    o_in_command            <= 32'h0000;
    o_in_address            <= 32'h0000;
    o_in_data               <= 32'h0000;
    in_state                <= IDLE;
    in_nibble_count         <= 4'h0;
    o_in_data_count         <= 27'h0;
    in_data_count           <= 0;
  end
  else begin
    //main state machine goes here
    case (in_state)
      IDLE: begin
        //putting this here lets master hold onto the data for
        //a longer time
        if (in_byte_available) begin
          if (in_byte == CHAR_L) begin
            //read the first of in_byte
            in_state            <= READ_DATA_COUNT;
            o_in_command        <= 32'h0000;
            o_in_address        <= 32'h0000;
            o_in_data           <= 32'h0000;
            o_in_data_count     <= 27'h000;
            in_data_count       <= 0;
            in_nibble_count     <= 4'h0;
          end
        end
      end
      READ_DATA_COUNT: begin
        if (in_byte_available) begin
          if (illegal_value) begin
            //invalid character go back to IDLE
            in_state            <= IDLE;
          end
          else begin
            //valid character
            o_in_data_count     <=  {o_in_data_count[23:0], in_nibble};
            in_nibble_count     <=  in_nibble_count + 1;
          end
        end
        if (in_nibble_count > 6) begin
          in_nibble_count       <= 4'h0;
          in_state              <= READ_CONTROL;
        end
      end
      READ_CONTROL: begin
        if (in_byte_available) begin
          if (illegal_value) begin
            in_state            <=  IDLE;
          end
          else begin
            o_in_command        <= {o_in_command[27:0], in_nibble};
            in_nibble_count     <=  in_nibble_count + 1;
          end
        end
        if (in_nibble_count > 7) begin
          in_state              <= READ_ADDRESS;
          in_nibble_count       <= 4'h0;
        end
      end
      READ_ADDRESS: begin
        //read the size
        if (in_byte_available) begin
          if (illegal_value) begin
            in_state            <= IDLE;
          end
          else begin
            o_in_address        <= {o_in_address[27:0], in_nibble};
            in_nibble_count     <= in_nibble_count + 1;
          end
        end
        if (in_nibble_count > 7) begin
          in_state              <= READ_DATA;
          in_nibble_count       <= 4'h0;
        end
      end
      READ_DATA : begin
        if (in_byte_available) begin
          if (illegal_value) begin
            in_state            <= IDLE;
          end
          else begin
            o_in_data           <= {o_in_data[27:0], in_nibble};
            in_nibble_count     <= in_nibble_count + 1;
          end
        end //if in byte available
        if (in_nibble_count > 7) begin
          in_state              <= WRITE_TO_MASTER;
          in_nibble_count       <= 4'h0;
          in_data_count         <= in_data_count + 1;
        end
      end
      WRITE_TO_MASTER: begin
        if (i_master_ready) begin
          o_ih_ready            <= 1;
          if (is_writing && (in_data_count < o_in_data_count)) begin
            in_state            <= READ_DATA;
          end
          else begin
            in_state            <= IDLE;
          end
        end
      end
      default: begin
          o_in_command          <= 8'h0;
          in_state              <= IDLE;
      end
     endcase
  end
end


//output handler
always @ (posedge clk) begin

  //uart_out_byte_en should only be high for one clock cycle
  oh_finished               <= 0;
  uart_out_byte_en          <= 0;
  o_oh_ready                <= 0;

  if (rst) begin
    out_state               <= IDLE;
    out_nibble_count        <= 4'h0;
    out_byte                <= 8'h0;
    li_out_data_count       <= 27'h0;
    li_out_data_count_buf   <= 27'h0;
    li_out_data             <= 32'h0;
    li_out_status           <= 32'h0;
    li_out_address          <= 32'h0;
    uart_wait_for_tx        <= 0;
    o_oh_ready              <= 0;
    out_data_count          <= 0;
    out_data_pos            <= 0;
  end

  else begin
    //don't do anything until the UART is ready
    if (~uart_wait_for_tx & uart_tx_ready) begin
    case (out_state)
      IDLE: begin
        out_byte            <=  8'h0;
        out_nibble_count    <=  4'h0;
        o_oh_ready          <=  1'h1;
        out_data_pos        <=  0;
        if (i_oh_en) begin
//moved this outside because by the time it reaches this part, the out data_count is
//changed
          li_out_status     <= i_out_status;
          li_out_address    <= i_out_address;
          li_out_data       <= i_out_data;

          out_byte          <= CHAR_S;
          out_state         <= WRITE_DATA_COUNT;
          o_oh_ready        <= 0;
          uart_out_byte_en  <= 1;
          uart_wait_for_tx  <= 1;
        end
        else begin
          li_out_data_count     <= 0;
          li_out_data_count_buf <= i_out_data_count;
          out_data_count        <= i_out_data_count;
        end
      end
      WRITE_DATA_COUNT: begin
        out_byte        <=  gen_data_count;
        li_out_data_count_buf   <= li_out_data_count_buf[27:0] << 4;
        uart_out_byte_en    <= 1;
        uart_wait_for_tx    <= 1;
        if (out_nibble_count >= 6) begin
          out_state     <= WRITE_STATUS;
          out_nibble_count  <= 4'h0;
        end
        else begin
          out_nibble_count    <= out_nibble_count + 1;
        end
      end
      WRITE_STATUS: begin
        out_byte            <=  gen_status;
        li_out_status       <= (li_out_status[31:0] << 4);
        uart_out_byte_en    <= 1;
        uart_wait_for_tx    <= 1;
        if (out_nibble_count >= 7) begin
          out_state     <= WRITE_ADDRESS;
          out_nibble_count  <= 4'h0;
        end
        else begin
          out_nibble_count    <= out_nibble_count + 1;
        end

      end
      WRITE_ADDRESS: begin
        out_byte            <=  gen_address;
        li_out_address      <= (li_out_address[31:0] << 4);
        uart_out_byte_en    <= 1;
        uart_wait_for_tx    <= 1;

        if (out_nibble_count >= 7) begin
          out_state     <= WRITE_DATA;
          out_nibble_count  <= 4'h0;
        end
        else begin
          out_nibble_count    <= out_nibble_count + 1;
        end

      end
      WRITE_DATA: begin
        out_byte              <=  gen_data;

        li_out_data           <= (li_out_data[28:0] << 4);
        uart_out_byte_en      <= 1;
        uart_wait_for_tx      <= 1;
        out_nibble_count      <= out_nibble_count + 1;

        if (out_nibble_count >= 7) begin
          out_state           <= SEND_DATA_TO_HOST;
          out_data_pos        <= out_data_pos + 1;
          out_nibble_count    <= 4'h0;
        end
      end
      SEND_DATA_TO_HOST: begin
        if (out_data_pos < out_data_count) begin
          o_oh_ready            <=  1;
          if (i_oh_en && o_oh_ready) begin
            li_out_data         <= i_out_data;
            out_state           <= WRITE_DATA;
          end
        end
        else begin
          oh_finished           <= 1;
          out_state             <= IDLE;
        end
      end
      default: begin
          out_state <=  IDLE;
      end
    endcase
    end
  end

  if (~uart_tx_ready) begin
    uart_wait_for_tx    <= 0;
  end
end


endmodule