def add_enrichr_cats(df, inst_rc, run_enrichr, num_terms=10):
    from copy import deepcopy

    tmp_gene_list = deepcopy(df.index.tolist())

    gene_list = (
        [inst_tuple[0] for inst_tuple in tmp_gene_list]
        if isinstance(tmp_gene_list[0], tuple)
        else tmp_gene_list
    )

    orig_gene_list = deepcopy(gene_list)

    # set up for non-tuple case first
    if ": " in gene_list[0]:
        # strip titles
        gene_list = [inst_gene.split(": ")[1] for inst_gene in gene_list]

    # strip extra information (e.g. PTMs)
    gene_list = [inst_gene.split("_")[0] for inst_gene in gene_list]
    gene_list = [inst_gene.split(" ")[0] for inst_gene in gene_list]
    gene_list = [inst_gene.split("-")[0] for inst_gene in gene_list]

    user_list_id = post_request(gene_list)

    enr, response_list = get_request(run_enrichr, user_list_id, max_terms=20)

    # p-value, adjusted pvalue, z-score, combined score, genes
    # 1: Term
    # 2: P-value
    # 3: Z-score
    # 4: Combined Score
    # 5: Genes
    # 6: pval_bh

    # while generating categories store as list of lists, then convert to list of
    # tuples

    bar_info = []
    cat_list = [[inst_gene] for inst_gene in orig_gene_list]

    for inst_enr in response_list[:num_terms]:
        inst_term = inst_enr[1]
        inst_pval = inst_enr[2]
        inst_cs = inst_enr[4]
        inst_list = inst_enr[5]

        pval_string = f"<p> Pval {inst_pval}</p>"

        bar_info.append(inst_cs)

        for inst_info in cat_list:
            # strip titles
            gene_name = inst_info[0]

            if ": " in gene_name:
                gene_name = gene_name.split(": ")[1]

            # strip extra information (e.g. PTMs)
            gene_name = gene_name.split("_")[0]
            gene_name = gene_name.split(" ")[0]
            gene_name = gene_name.split("-")[0]

            result_str = (
                f"{inst_term}: True{pval_string}"
                if gene_name in inst_list
                else f"{inst_term}: False{pval_string}"
            )
            inst_info.append(result_str)

    cat_list = [tuple(x) for x in cat_list]

    df.index = cat_list

    return df, bar_info


def clust_from_response(response_list):
    from copy import deepcopy
    import math

    from clustergrammer import Network
    import pandas as pd
    import scipy

    # print('----------------------')
    # print('enrichr_clust_from_response')
    # print('----------------------')

    ini_enr = transfer_to_enr_dict(response_list)

    enr = []
    scores = {}
    score_types = ["combined_score", "pval", "zscore"]

    for score_type in score_types:
        scores[score_type] = pd.Series()

    for inst_enr in ini_enr:
        if inst_enr["combined_score"] > 0:
            # make series of enriched terms with scores
            for score_type in score_types:
                # collect the scores of the enriched terms
                if score_type == "combined_score":
                    scores[score_type][inst_enr["name"]] = inst_enr[score_type]
                elif score_type == "pval":
                    scores[score_type][inst_enr["name"]] = -math.log(inst_enr[score_type])
                elif score_type == "zscore":
                    scores[score_type][inst_enr["name"]] = -inst_enr[score_type]

            # keep enrichment values
            enr.append(inst_enr)

    # sort and normalize the scores
    for score_type in score_types:
        scores[score_type] = scores[score_type] / scores[score_type].max()
        scores[score_type].sort_values(ascending=False)

    number_of_enriched_terms = len(scores["combined_score"])

    enr_score_types = ["combined_score", "pval", "zscore"]

    if number_of_enriched_terms < 10:
        num_dict = {"ten": 10}
    elif number_of_enriched_terms < 20:
        num_dict = {"ten": 10, "twenty": 20}
    else:
        num_dict = {"ten": 10, "twenty": 20, "thirty": 30}

    # gather lists of top scores
    top_terms = {}
    for enr_type in enr_score_types:
        top_terms[enr_type] = {}
        for num_terms in list(num_dict.keys()):
            inst_num = num_dict[num_terms]
            top_terms[enr_type][num_terms] = scores[enr_type].index.tolist()[:inst_num]

    # gather the terms that should be kept - they are at the top of the score list
    keep_terms = []
    for inst_enr_score in top_terms:
        for tmp_num in list(num_dict.keys()):
            keep_terms.extend(top_terms[inst_enr_score][tmp_num])

    keep_terms = list(set(keep_terms))

    # keep enriched terms that are at the top 10 based on at least one score
    keep_enr = [inst_enr for inst_enr in enr if inst_enr["name"] in keep_terms]

    # fill in full matrix
    #######################

    # genes
    row_node_names = []
    # enriched terms
    col_node_names = []

    # gather information from the list of enriched terms
    for inst_enr in keep_enr:
        col_node_names.append(inst_enr["name"])
        row_node_names.extend(inst_enr["int_genes"])

    row_node_names = sorted(set(row_node_names))

    net = Network()
    net.dat["nodes"]["row"] = row_node_names
    net.dat["nodes"]["col"] = col_node_names
    net.dat["mat"] = scipy.zeros([len(row_node_names), len(col_node_names)])

    for inst_enr in keep_enr:
        inst_term = inst_enr["name"]
        col_index = col_node_names.index(inst_term)

        # use combined score for full matrix - will not be seen in viz
        tmp_score = scores["combined_score"][inst_term]
        net.dat["node_info"]["col"]["value"].append(tmp_score)

        for inst_gene in inst_enr["int_genes"]:
            row_index = row_node_names.index(inst_gene)

            # save association
            net.dat["mat"][row_index, col_index] = 1

    # cluster full matrix
    #############################
    # do not make multiple views
    views = [""]

    if len(net.dat["nodes"]["row"]) > 1:
        net.cluster(dist_type="jaccard", views=views, dendro=False)
    else:
        net.cluster(dist_type="jaccard", views=views, dendro=False, run_clustering=False)

    # get dataframe from full matrix
    df = net.dat_to_df()

    for score_type in score_types:
        for num_terms in num_dict:
            inst_df = deepcopy(df)
            inst_net = deepcopy(Network())

            inst_df = inst_df[top_terms[score_type][num_terms]]

            # load back into net
            inst_net.df_to_dat(inst_df)

            # make views
            if len(net.dat["nodes"]["row"]) > 1:
                inst_net.cluster(dist_type="jaccard", views=["N_row_sum"], dendro=False)
            else:
                inst_net.cluster(
                    dist_type="jaccard", views=["N_row_sum"], dendro=False, run_clustering=False
                )

            inst_views = inst_net.viz["views"]

            # add score_type to views
            for inst_view in inst_views:
                inst_view["N_col_sum"] = num_dict[num_terms]

                inst_view["enr_score_type"] = score_type

                # add values to col_nodes and order according to rank
                for inst_col in inst_view["nodes"]["col_nodes"]:
                    inst_col["rank"] = len(top_terms[score_type][num_terms]) - top_terms[
                        score_type
                    ][num_terms].index(inst_col["name"])

                    inst_name = inst_col["name"]
                    inst_col["value"] = scores[score_type][inst_name]

            # add views to main network
            net.viz["views"].extend(inst_views)

    return net


# make the get request to enrichr using the requests library
# this is done before making the get request with the lib name
def post_request(input_genes, meta=""):
    # get metadata
    import json

    import requests

    # stringify list
    input_genes = "\n".join(input_genes)

    # define post url
    post_url = "http://amp.pharm.mssm.edu/Enrichr/addList"

    # define parameters
    params = {"list": input_genes, "description": ""}

    # make request: post the gene list
    post_response = requests.post(post_url, files=params)

    # load json
    inst_dict = json.loads(post_response.text)
    # print(user_list_id)

    # return the user_list_id that is needed to reference the list later
    return str(inst_dict["userListId"])


# make the get request to enrichr using the requests library
# this is done after submitting post request with the input gene list
def get_request(lib, user_list_id, max_terms=50):
    import json

    import requests

    # convert user_list_id to string
    user_list_id = str(user_list_id)

    # define the get url
    get_url = "http://amp.pharm.mssm.edu/Enrichr/enrich"

    # get parameters
    params = {"backgroundType": lib, "userListId": user_list_id}

    # try get request until status code is 200
    inst_status_code = 400

    # wait until okay status code is returned
    num_try = 0

    # print(('\tEnrichr enrichment get req user_list_id: '+str(user_list_id)))

    while inst_status_code == 400 and num_try < 100:
        num_try += 1
        try:
            # make the get request to get the enrichr results

            try:
                get_response = requests.get(get_url, params=params)

                # get status_code
                inst_status_code = get_response.status_code

            except Exception:
                print("retry get request")

        except Exception:
            print("get requests failed")

    # load as dictionary
    resp_json = json.loads(get_response.text)

    # get the key
    only_key = next(iter(resp_json.keys()))

    # get response_list
    response_list = resp_json[only_key]

    # transfer the response_list to the enr_dict
    enr = transfer_to_enr_dict(response_list, max_terms)

    # return enrichment json and user_list_id
    return enr, response_list


# transfer the response_list to a list of dictionaries
def transfer_to_enr_dict(response_list, max_terms=50):
    # # reduce the number of enriched terms if necessary
    # if len(response_list) < num_terms:
    #   num_terms = len(response_list)

    # p-value, adjusted pvalue, z-score, combined score, genes
    # 1: Term
    # 2: P-value
    # 3: Z-score
    # 4: Combined Score
    # 5: Genes
    # 6: pval_bh

    num_enr_term = min(len(response_list), max_terms)

    # transfer response_list to enr structure
    # and only keep the top terms
    #
    # initialize enr
    enr = []
    for i in range(num_enr_term):
        # get list element
        inst_enr = response_list[i]

        # initialize dict
        inst_dict = {
            "name": inst_enr[1],  # transfer term
            "pval": inst_enr[2],  # transfer pval
            "zscore": inst_enr[3],  # transfer zscore
            "combined_score": inst_enr[4],  # transfer combined_score
            "int_genes": inst_enr[5],  # transfer int_genes
            "pval_bh": inst_enr[6],  # adjusted pval
        }

        # append dict
        enr.append(inst_dict)

    return enr
