def _generate_grasp_pattern(enz_states_list: list, promiscuous: bool, inhib_list: list,
                            activ_list: list) -> str:
    """
    Given a list of enzyme state numbers and metabolites that bind/unbind to them, generates the respective
    GRASP pattern.

    For this to work both with promiscuous reactions where one reaction has an ordered mechanism and another a random
     mechanism as well as ping-pong mechanisms a few steps are taken:
       - the number of reactions in a mechanism is counted. A new reaction starts when the enzyme state to which
       a ligand can bind is 1 (i.e. the free enzyme), there has been more than one transition step and there
       has been a new transition step since it was checked. This allows
       one to distinguish different reactions in a given promiscuous enzyme mechanism and naming each first product
       to be released after a transition step as P{i}, where i stands for the number of the reaction. This is important
       so GRASP can later on write the flux equation correctly for each reaction catalyzed by a promiscuous enzyme.
       The reason reactions are counted and not only transition steps is because ping-pong mechanisms also have
       multiple transition steps and yet the first product to be released after a transition step should be named
       P, Q, R, S instead of P{i}.

       - whether we are in a state right after the transition step or not is tracked, so that we can name the product
       as P{i} instead of Q, R, S, etc.

    Inhibiting and activating metabolites are assumed to only bind to the enzyme and not be released, except as
    a "reverse binding".

    Args:
        enz_states_list: list of all enzyme state numbers and ligands that bind/unbind.
        promiscuous: to indicate whether reaction is promiscuous or not.
        inhib_list: list of inhibiting metabolites.
        activ_list: list of activating metabolites.

    Returns:
        The GRASP pattern.

    """
    subs_list = ['A', 'B', 'C', 'D', 'E']
    prod_list = ['P', 'Q', 'R', 'S', 'T']
    subs_dic = {}
    prod_dic = {}
    subs_i = 0
    prod_i = 0
    inhib_dic = dict((met, f'I{i+1}') for i, met in enumerate(inhib_list)) if inhib_list else dict()
    activ_dic = dict((met, f'Z{i+1}') for i, met in enumerate(activ_list)) if activ_list else dict()

    rxn_i = 0
    n_transitions = 0
    previous_n_transitions = 0
    after_transition = False

    grasp_pattern = ''
    rate_i = 1

    for state_i in range(0, len(enz_states_list), 2):

        # if there is more than one transition step, there has been a transition step since the last one
        #  and the enzyme state is 1 again then it is a new reaction
        # if the enzyme is promiscuous restart dictionaries so that no repeated ligand names are used.
        if n_transitions > 0 and n_transitions > previous_n_transitions and enz_states_list[state_i][0] == 1:
            previous_n_transitions = n_transitions
            rxn_i += 1

            if promiscuous:
                subs_dic = {}
                prod_dic = {}

        # get binding ligand name
        bind_ligand = ''

        if len(enz_states_list[state_i]) == 2:
            if enz_states_list[state_i][1] in inhib_dic.keys():
                bind_ligand = f'.*{inhib_dic[enz_states_list[state_i][1]]}'
            elif enz_states_list[state_i][1] in activ_dic.keys():
                bind_ligand = f'.*{activ_dic[enz_states_list[state_i][1]]}'
            else:
                if not enz_states_list[state_i][1] in subs_dic.keys():
                    subs_dic[enz_states_list[state_i][1]] = subs_list[subs_i]
                    subs_i += 1

                bind_ligand = f'.*{subs_dic[enz_states_list[state_i][1]]}'

        # write forward step
        grasp_pattern += f'{enz_states_list[state_i][0]} {enz_states_list[state_i + 1][0]} k{rate_i:02}{bind_ligand}\n'
        rate_i += 1

        # get unbinding ligand name
        release_ligand = ''
        if len(enz_states_list[state_i + 1]) == 2:
            if not enz_states_list[state_i + 1][1] in prod_dic.keys():
                prod_dic[enz_states_list[state_i + 1][1]] = prod_list[prod_i]
                prod_i += 1

            # by default P is named P1
            release_ligand = f'.*P1' if prod_dic[enz_states_list[state_i + 1][1]] == 'P' \
                             else f'.*{prod_dic[enz_states_list[state_i + 1][1]]}'

            # if ligand unbinding is the first one after the transition step name it Pi
            if after_transition and promiscuous:
                release_ligand = f'.*P{rxn_i+1}'
                prod_dic[enz_states_list[state_i + 1][1]] = f'P{rxn_i+1}'
                after_transition = False

                if rxn_i > 0:
                    prod_i -= 1

        else:  # check if this is a transition step
            if not bind_ligand:
                n_transitions += 1
                after_transition = True

        # write reverse step
        grasp_pattern += f'{enz_states_list[state_i + 1][0]} {enz_states_list[state_i][0]} k{rate_i:02}{release_ligand}\n'
        rate_i += 1

    # if there is only one reaction all P1 products should be P
    if rxn_i == 0:
        grasp_pattern = grasp_pattern.replace('P1', 'P')

    # remove last new line
    grasp_pattern = grasp_pattern[:-1]

    return grasp_pattern