module usb_ft1248 (
    if_system.sys sys,

    input usb_enabled,

    output usb_clk,
    output usb_cs,
    input usb_miso,
    inout [3:0] usb_miosi,

    input rx_flush,
    output rx_empty,
    input rx_read,
    output [7:0] rx_rdata,

    input tx_flush,
    output tx_full,
    input tx_write,
    input [7:0] tx_wdata,

    output rx_escape_valid,
    input rx_escape_ack,
    output [7:0] rx_escape
);

    parameter bit [7:0] ESCAPE_CHARACTER    = 8'h1B;

    // FIFOs

    logic rx_full;
    logic rx_write;
    logic [7:0] rx_wdata;

    logic tx_empty;
    logic tx_read;
    logic [7:0] tx_rdata;

    logic rx_wdata_valid;
    logic rx_escape_active;

    intel_fifo_8 fifo_8_rx_inst (
        .clock(sys.clk),
        .sclr(rx_flush || !usb_enabled),

        .empty(rx_empty),
        .rdreq(rx_read),
        .q(rx_rdata),

        .full(rx_full),
        .wrreq(rx_write),
        .data(rx_wdata)
    );

    intel_fifo_8 fifo_8_tx_inst (
        .clock(sys.clk),
        .sclr(tx_flush || !usb_enabled),

        .empty(tx_empty),
        .rdreq(tx_read),
        .q(tx_rdata),

        .full(tx_full),
        .wrreq(tx_write),
        .data(tx_wdata)
    );


    // Escape character detection

    always_comb begin
        rx_write = 1'b0;
        if (rx_wdata_valid) begin
            rx_write = rx_escape_active ? rx_wdata == ESCAPE_CHARACTER : rx_wdata != ESCAPE_CHARACTER;
        end
    end

    always_ff @(posedge sys.clk) begin
        if (sys.reset || !usb_enabled) begin
            rx_escape_valid <= 1'b0;
            rx_escape_active <= 1'b0;
        end else begin
            if (rx_escape_ack) begin
                rx_escape_valid <= 1'b0;
            end

            if (rx_wdata_valid) begin
                if (!rx_escape_active) begin
                    if (rx_wdata == ESCAPE_CHARACTER) begin
                        rx_escape_active <= 1'b1;
                    end
                end else begin
                    rx_escape_active <= 1'b0;
                    rx_escape <= rx_wdata;
                    if (rx_wdata != ESCAPE_CHARACTER) begin
                        rx_escape_valid <= 1'b1;
                    end
                end
            end
        end
    end


    // FT1248 interface controller

    typedef enum bit [1:0] {
        S_TRY_RX,
        S_TRY_TX,
        S_COMMAND,
        S_DATA
    } e_state;

    typedef enum bit [7:0] {
        C_WRITE = 8'h00,
        C_READ = 8'h04
    } e_command;

    typedef enum bit [1:0] {
        P_PRE_RISING,
        P_RISING,
        P_PRE_FALLING,
        P_FALLING
    } e_clock_phase;

    e_state state;

    logic [3:0] clock_phase;

    logic usb_clk_output;
    logic usb_cs_output;
    logic [3:0] usb_miosi_input;
    logic [3:0] usb_miosi_output;
    logic [3:0] usb_miosi_output_data;
    logic usb_miosi_output_enable;
    logic usb_miosi_output_enable_data;
    logic usb_miso_input;

    logic is_cmd_write;
    logic [1:0] nibble_counter;
    logic [7:0] tx_buffer;

    always_ff @(posedge sys.clk) begin
        if (sys.reset || state == S_TRY_RX || state == S_TRY_TX) begin
            clock_phase <= 4'b0001;
        end else begin
            clock_phase <= {clock_phase[2:0], clock_phase[3]};
        end
    end

    always_ff @(posedge sys.clk) begin
        usb_clk <= usb_clk_output;
        usb_cs <= usb_cs_output;

        usb_miosi_input <= usb_miosi;
        usb_miosi_output <= usb_miosi_output_data;
        usb_miosi_output_enable <= usb_miosi_output_enable_data;
        
        usb_miso_input <= usb_miso;

        tx_buffer <= tx_rdata;
    end

    always_comb begin
        usb_miosi = usb_miosi_output_enable ? usb_miosi_output : 4'bZZZZ;
    end

    always_comb begin
        case (state)
            S_COMMAND: begin
                usb_clk_output = clock_phase[P_PRE_FALLING] || clock_phase[P_FALLING];
                usb_cs_output = 1'b0;
                if (is_cmd_write) begin
                    usb_miosi_output_data = nibble_counter[0] ? C_WRITE[3:0] : C_WRITE[7:4];
                end else begin
                    usb_miosi_output_data = nibble_counter[0] ? C_READ[3:0] : C_READ[7:4];
                end
                usb_miosi_output_enable_data = nibble_counter < 2'd2;
            end

            S_DATA: begin
                usb_clk_output = clock_phase[P_PRE_FALLING] || clock_phase[P_FALLING];
                usb_cs_output = 1'b0;
                usb_miosi_output_data = nibble_counter[0] ? tx_buffer[7:4] : tx_buffer[3:0];
                usb_miosi_output_enable_data = is_cmd_write;
            end

            default: begin
                usb_clk_output = 1'b0;
                usb_cs_output = 1'b1;
                usb_miosi_output_data = 4'hF;
                usb_miosi_output_enable_data = 1'b0;
            end
        endcase
    end

    always_ff @(posedge sys.clk) begin
        rx_wdata_valid <= 1'b0;
        tx_read <= 1'b0;

        if (clock_phase[P_RISING]) begin
            nibble_counter <= nibble_counter + 1'd1;
        end

        if (sys.reset || !usb_enabled) begin
            state <= S_TRY_RX;
        end else begin
            case (state)
                S_TRY_RX: begin
                    if (!rx_full && !rx_escape_valid) begin
                        state <= S_COMMAND;
                        is_cmd_write <= 1'b0;
                        nibble_counter <= 2'b11;
                    end else begin
                        state <= S_TRY_TX;
                    end
                end

                S_TRY_TX: begin
                    if (!tx_empty) begin
                        state <= S_COMMAND;
                        is_cmd_write <= 1'b1;
                        nibble_counter <= 2'b11;
                    end else begin
                        state <= S_TRY_RX;
                    end
                end

                S_COMMAND: begin
                    if (clock_phase[P_RISING]) begin
                        if (nibble_counter == 2'd2) begin
                            if (usb_miso_input) begin
                                state <= is_cmd_write ? S_TRY_RX : S_TRY_TX;
                            end else begin
                                state <= S_DATA;
                                nibble_counter <= 2'd0;
                            end
                        end
                    end
                end

                S_DATA: begin
                    if (clock_phase[P_FALLING]) begin
                        if (nibble_counter[0]) begin
                            tx_read <= is_cmd_write;
                        end
                    end
                    if (clock_phase[P_RISING]) begin
                        rx_wdata <= {usb_miosi_input, rx_wdata[7:4]};
                        if (nibble_counter[0]) begin
                            rx_wdata_valid <= !is_cmd_write;
                        end
                        if (usb_miso_input || (!is_cmd_write && (rx_full || rx_escape_valid)) || (is_cmd_write && tx_empty)) begin
                            state <= is_cmd_write ? S_TRY_RX : S_TRY_TX;
                        end
                    end
                end

                default: begin
                    state <= S_TRY_RX;
                end
            endcase
        end
    end

endmodule