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 = []
  if type(tmp_gene_list[0]) is tuple:
    for inst_tuple in tmp_gene_list:
      gene_list.append(inst_tuple[0])
  else:
    gene_list = 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 = []
  for inst_gene in orig_gene_list:
    cat_list.append([inst_gene])

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

    pval_string = '<p> Pval ' + str(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]

      if gene_name in inst_list:
        inst_info.append(inst_term+': True'+ pval_string)
      else:
        inst_info.append(inst_term+': False'+pval_string)

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

  df.index = cat_list

  return df, bar_info

def clust_from_response(response_list):
  from clustergrammer import Network
  import scipy
  import json
  import pandas as pd
  import math
  from copy import deepcopy

  # 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]
        if score_type == 'pval':
          scores[score_type][inst_enr['name']] = -math.log(inst_enr[score_type])
        if score_type == 'zscore':
          scores[score_type][inst_enr['name']] = -inst_enr[score_type]

      # keep enrichement 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(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 = []
  for inst_enr in enr:
    if inst_enr['name'] in keep_terms:
      keep_enr.append(inst_enr)


  # 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(list(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.make_clust(dist_type='jaccard', views=views, dendro=False)
  else:
    net.make_clust(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['mat'] = inst_df['mat'][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.make_clust(dist_type='jaccard', views=['N_row_sum'], dendro=False)
      else:
        inst_net.make_clust(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 requests
  import json

  # 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 )
  userListId = str(inst_dict['userListId'])

  # return the userListId that is needed to reference the list later
  return 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, userListId, max_terms=50 ):
  import requests
  import json

  # convert userListId to string
  userListId = str(userListId)

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

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

  # 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 userListId: '+str(userListId)))

  while inst_status_code == 400 and num_try < 100:
    num_try = 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:
        print('retry get request')

    except:
      print('get requests failed')

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

  # get the key
  only_key = list(resp_json.keys())[0]

  # 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 userListId
  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 = len(response_list)
  if num_enr_term > max_terms:
    num_enr_term = 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 = {}

    # transfer term
    inst_dict['name'] = inst_enr[1]
    # transfer pval
    inst_dict['pval'] = inst_enr[2]
    # transfer zscore
    inst_dict['zscore'] = inst_enr[3]
    # transfer combined_score
    inst_dict['combined_score'] = inst_enr[4]
    # transfer int_genes
    inst_dict['int_genes'] = inst_enr[5]
    # adjusted pval
    inst_dict['pval_bh'] = inst_enr[6]

    # append dict
    enr.append(inst_dict)

  return enr

