from typing import Tuple


def encode_5b6b(i_5b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int]:
    """5b/6b encoder.

    Args:
        i_5b (int): input 5b value
        i_disp (int): input running disparity (-1 or 1)
        i_ctrl (bool): intput control symbol flag

    Returns:
        tuple[int, int]: tuple of output 6b value and output running disparity
    """

    # lookup table maps EDBCA to iedcba for RD = -1
    lut_5b6b_rdm1 = {
        # EDCBA:   iebcba
        0b00000: 0b111001,  # D.00-
        0b00001: 0b101110,  # D.01-
        0b00010: 0b101101,  # D.02-
        0b00011: 0b100011,  # D.03-
        0b00100: 0b101011,  # D.04-
        0b00101: 0b100101,  # D.05-
        0b00110: 0b100110,  # D.06-
        0b00111: 0b000111,  # D.07-
        0b01000: 0b100111,  # D.08-
        0b01001: 0b101001,  # D.09-
        0b01010: 0b101010,  # D.10-
        0b01011: 0b001011,  # D.11-
        0b01100: 0b101100,  # D.12-
        0b01101: 0b001101,  # D.13-
        0b01110: 0b001110,  # D.14-
        0b01111: 0b111010,  # D.15-
        0b10000: 0b110110,  # D.16-
        0b10001: 0b110001,  # D.17-
        0b10010: 0b110010,  # D.18-
        0b10011: 0b010011,  # D.19-
        0b10100: 0b110100,  # D.20-
        0b10101: 0b010101,  # D.21-
        0b10110: 0b010110,  # D.22-
        0b10111: 0b010111,  # D.23-
        0b11000: 0b110011,  # D.24-
        0b11001: 0b011001,  # D.25-
        0b11010: 0b011010,  # D.26-
        0b11011: 0b011011,  # D.27-
        0b11100: 0b001110,  # D.28-
        0b11101: 0b011101,  # D.29-
        0b11110: 0b011110,  # D.30-
        0b11111: 0b110101,  # D.31-
    }

    o_6b = lut_5b6b_rdm1[i_5b]  # lookup 5b/6b RD- value

    # K.28 is unique from the rest of the table
    if i_ctrl and i_5b == 28:
        if i_disp == -1:
            o_6b = 0b111100
        else:
            o_6b = 0b000011

    elif i_disp == 1:  # may have to look up RD+ value

        # check if RD+ value is different than RD- value
        if i_5b in [0, 1, 2, 4, 7, 8, 15, 16, 23, 24, 27, 29, 30, 31]:
            o_6b = (~o_6b) & 0x3F

    # update running disparity
    string_6b = format(o_6b, "06b")
    num_ones = string_6b.count("1")
    num_zeros = string_6b.count("0")
    o_disp = i_disp + num_ones - num_zeros

    # Check for invalid output running disparity
    try:
        assert o_disp == -1 or o_disp == 1
    except AssertionError as e:
        i_5b_str = format(i_5b, "#07b")
        o_6b_str = format(o_6b, "#08b")
        print(
            f"Error: Invalid output running disparity value\n"
            f"    i_5b = {i_5b_str}, i_disp = {i_disp}, i_ctrl = {i_ctrl}\n"
            f"    o_6b = {o_6b_str}, o_disp = {o_disp}\n"
        )
        raise e

    return o_6b, o_disp