import networkx as nx
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import copy
from collections import defaultdict
import matplotlib.patches as pch
from swan_vis.utils import *
from swan_vis.graph import Graph

class PlottedGraph(Graph):

	def __init__(self,
				 sg,
				 combine,
				 indicate_dataset,
				 indicate_novel,
				 tid=None,
				 browser=False,
				 gid=None):

		# save settings so we know how much we need to recompute
		self.combine = combine
		self.indicate_dataset = indicate_dataset 
		self.indicate_novel = indicate_novel
		# self.path = path
		self.gid = gid # if we're plotting a set of transcripts belonging to a specific gene
		               # or a summary graph of a specific gene
		self.tid = tid
		self.browser = browser

		# other fields to copy over
		self.datasets = sg.datasets

		# assign dfs for plotted graph
		self.G = sg.G.copy()
		self.loc_df = sg.loc_df.copy(deep=True)
		self.edge_df = sg.edge_df.copy(deep=True)
		self.t_df = sg.t_df.copy(deep=True)

		# have to deepcopy lists from t_df separately because of how pandas
		# handles lists
		self.t_df['path'] = self.t_df.apply(
			lambda x: copy.deepcopy(x.path), axis=1)

		if gid: 
			self.subset_on_gene(self.gid)
			self.g_min, self.g_max = self.get_gene_min_max(gid)
		if self.tid: 
			gid = self.get_gid_from_tid(self.tid)
			self.gid = gid
			self.subset_on_gene(gid)
			self.path = self.get_path_from_tid(self.tid)
		else:
			self.path = None

		## TODO is this the best way to handle browser vs. not browser graphs?
		# if we're making a swan graph
		if not browser:

			# used for getting plotting settings 
			self.loc_df['combined'] = False

			# combine non-branching paths
			if combine:
				nbps = self.find_nb_paths()
				self.agg_nb_nodes(nbps)

			# get positions/sizes of nodes, edges, and labels
			self.calc_pos_sizes()

			# get color/shape plotting settings for nodes, edges
			self.get_plt_settings()

		# if we're just making browser track things
		# elif browser:
		# 	1

	###############################################################################
	################### Getting plotting settings for nodes/edges #################
	###############################################################################

	# get color/shape plotting settings
	def get_plt_settings(self):

		# plotting styles
		gray = '#DCDCDC'
		yellow = '#F0E442'
		blue = '#0072B2'
		light_blue = '#56B4E9'
		red = '#D55E00'
		orange = '#E69F00'
		pink = '#CC79A7'
		green = '#009E73'

		color_dict = {'intron': pink, 
					  'exon': green,
					  'TSS': blue,
					  'alt_TSS': light_blue,
					  'TES': red,
					  'alt_TES': orange,
					  'internal': yellow,
					  'gray': gray}

		# get the list of datasets we should be looking at to determine
		# node or edge uniqueness
		dataset_cols = self.get_dataset_cols()

		# TODO use this when doing unique_to_dataset
		# if self.indicate_dataset:
		# 	dataset_cols.remove(self.indicate_dataset)

		###############
		#### NODES ####
		###############

		# node plotting settings: color
		self.loc_df = self.loc_df.apply(
			lambda x: self.get_node_color(x, color_dict), axis=1)

		# node plotting settings: shape
		self.loc_df['node_shape'] = self.loc_df.apply(
			lambda x: self.get_node_shape(x, dataset_cols), axis=1)

		###############
		#### EDGES ####
		###############

		# edge plotting settings: color
		self.edge_df['color'] = self.edge_df.apply(
			lambda x: self.get_edge_color(x, color_dict), axis=1)

		edge_dict = {'pos': 'arc3,rad=',
					 'neg': 'arc3,rad=-',
					 'straight': None}
		ordered_nodes = self.get_ordered_nodes()
		ordered_edges = [(n,m) for n,m in zip(ordered_nodes[:-1],ordered_nodes[1:])]

		# edge plotting settings: curve type 
		self.edge_df['raw_index'] = [i for i in range(len(self.edge_df.index))]
		self.edge_df['curve'] = self.edge_df.apply(
			lambda x: self.get_edge_style(x, ordered_edges, edge_dict), axis=1)
		self.edge_df.drop('raw_index', axis=1, inplace=True)

		# edge plotting settings: line type (dashed or not)
		self.edge_df['line'] = self.edge_df.apply(
			lambda x: self.get_edge_line(x, dataset_cols), axis=1)
		
	# calculates the positions and sizes of edges/nodes based on the 
	# number of nodes in the graph
	def calc_pos_sizes(self):

		pos = defaultdict()

		ordered_nodes = self.get_ordered_nodes()

		# check if forward or reverse strand 
		# is the first node in the ordered list a TSS or TES? 
		if self.loc_df.loc[ordered_nodes[0], 'TES']:
			ordered_nodes.reverse()

		y_coord = 0
		x_coords = np.linspace(-1, 1, len(ordered_nodes))
		for x_coord, n in zip(x_coords, ordered_nodes): 
			pos[n] = [x_coord, y_coord]

		x = len(ordered_nodes)

		# area-related sizes need to be non-linearly scaled
		# calculated by fitting power series curve to handpicked sizes
		node_size = 19248*(x**-1.14) 
		sub_node_size = node_size/2
		label_size = 43.9*(x**-0.484)

		# linearly-related sizes
		# rad_scale = ((11/540)*x)#+(73/540)
		edge_width = -x/18 + (121/18)

		# assign fields to plotted graph object 
		self.pos = pos
		self.node_size = node_size
		self.sub_node_size = sub_node_size
		self.label_size = label_size
		# self.rad_scale = rad_scale
		self.rad_scale = 0.35
		self.edge_width = edge_width

	# get the shape of the node 
	def get_node_shape(self, x, dataset_cols):

		# default shape
		shape = 'o'

		# hexagonal nodes for combined nodes 
		if x.combined:
			shape = 'H'
			# unique to dataset
			# if is_novel(self.indicate_novel, x) or unique_to_dataset(self.indicate_dataset, x, dataset_cols): 
			# 	shape = 'h'
			# in dataset  TODO make these separate options in the future
			if is_novel(self.indicate_novel, x) or in_dataset(self.indicate_dataset, x):
				shape = 'h'
		# diamond nodes for novel or inidicate_dataset nodes
		# if is_novel(self.indicate_novel, x) or unique_to_dataset(self.indicate_dataset, x, dataset_cols):
		if is_novel(self.indicate_novel, x) or in_dataset(self.indicate_dataset, x):
			shape = 'D'

		return shape

	# get the node color 
	def get_node_color(self, x, color_dict):

		# if we're dealing with a path, color only according
		# to the node's role in the path
		if self.tid:
			self.path = self.get_path_from_tid(self.tid)
			if x.vertex_id == self.path[0]:
				x['color'] = color_dict['alt_TSS']
				x['sub_color'] = color_dict['alt_TSS']
			elif x.vertex_id == self.path[-1]:
				x['color'] = color_dict['alt_TES']
				x['sub_color'] = color_dict['alt_TES']
			elif x.vertex_id in self.path:
				x['color'] = color_dict['internal']
				x['sub_color'] = color_dict['internal']	
			else:
				x['color'] = color_dict['gray']
				x['sub_color'] = color_dict['gray']	

		# combined nodes
		elif x.combined:
			types = x.combined_types

			# did one or two types of node go into this node?
			if len(types) == 2:

				x['sub_color'] = color_dict[types[0]]
				x['color'] = color_dict[types[1]]
			else: 
				x['sub_color'] = np.nan
				x['color'] = color_dict[types[0]]

		# non combined nodes
		else:

			# we don't need a sub color
			x['sub_color'] = np.nan

			# colors should label nodes by their 
			# MOST UNIQUE type (yes I know this is subjective) 
			# to me, this means that the label priority for a node is 
			# internal < TSS/alt_TSS < TES/alt_TES
			if x.internal: color = color_dict['internal']
			if x.TSS: color = color_dict['TSS']
			if x.alt_TSS: color = color_dict['alt_TSS']
			if x.TES: color = color_dict['TES']
			if x.alt_TES: color = color_dict['alt_TES']
			x['color'] = color

		return x

	# get the color of the edge
	def get_edge_color(self, x, color_dict):

		# firstly, if we're given a path, 
		# only color the edges that are in the path
		if self.path:
			path_edges = [(self.path[i],self.path[i+1])
						   for i in range(len(self.path)-1)]
			if x.edge_id in path_edges:
				color = color_dict[x.edge_type]
			else: color = color_dict['gray']

		else:
			color = color_dict[x.edge_type]

		return color

	# get the curve/style of the edge
	def get_edge_style(self, x, ordered_edges, edge_dict):

		# over 20 nodes, all should be curved
		if len(ordered_edges) < 20:
			if x.edge_id in ordered_edges:
				return edge_dict['straight']

		# make the arcs pretty
		## TODO maybe should have the arc height vary with number of nodes, ie 
		## shorter arc with less nodes? idk, still looks weird
		dist = self.pos[x.v2][0] - self.pos[x.v1][0]
		rad = self.rad_scale/dist

		if x.raw_index % 2 == 0:
			return edge_dict['pos']+str(rad)
		else:
			return edge_dict['neg']+str(rad)

	# get the style of the line for an edge
	def get_edge_line(self, x, dataset_cols):
		style = None
		# dashed if novel or unique to dataset
		# if is_novel(self.indicate_novel, x) or unique_to_dataset(self.indicate_dataset, x, dataset_cols):
		if is_novel(self.indicate_novel, x) or in_dataset(self.indicate_dataset, x):
			style = 'dashed'
		return style

	###############################################################################
	########################## Actually plotting stuff ############################
	###############################################################################

	# plots input plotted graph according to user's choices
	def plot_graph(self):

		# swan graph
		if not self.browser:
			self.plot_swan_graph()
		# browser track
		else:
			self.plot_browser_path()

	###############################################################################
	############################ Swan graph plotting ##############################
	###############################################################################

	# plots swan graph of current plotted graph
	def plot_swan_graph(self):

		# plotting stuff
		plt.figure(1, figsize=(14,2.8), frameon=False)
		plt.xlim(-1.05, 1.05)
		plt.ylim(-1.05, 1.05) 

		# plot edges, nodes, and labels
		self.plot_edges()

		# relabel nodes with strings for the purpose of labelling them
		relabel_map = {k:(k if type(k) == str else int(k)) for k in self.G.nodes}
		self.G = nx.relabel_nodes(self.G, relabel_map)
		nx.draw_networkx_labels(self.G,
			self.pos,
			font_size=self.label_size)

		self.plot_nodes()

	# plots edges from edge_df
	def plot_edges(self):
		for _, entry in self.edge_df.iterrows():
			edge = entry.edge_id
			nx.draw_networkx_edges(self.G, self.pos,
				edgelist=[edge],
				width=self.edge_width,
				edge_color=entry.color,
				connectionstyle=entry.curve,
				style=entry.line)

	# plots nodes from loc_df
	def plot_nodes(self):
		for _, entry in self.loc_df.iterrows():
			node = entry.vertex_id
			nx.draw_networkx_nodes(self.G, self.pos,
				nodelist=[node],
				node_color=entry.color,
				node_size=self.node_size,
				node_shape=entry.node_shape)
			if entry.combined:
				if len(entry.combined_types) == 2:
					nx.draw_networkx_nodes(self.G, self.pos,
						nodelist=[node],
						node_color=entry.sub_color,
						node_size=self.sub_node_size,
						node_shape=entry.node_shape)


	###############################################################################
	######################## Browser track style plotting #########################
	###############################################################################

	# plots the browser track representation of the transcript from self.path
	def plot_browser_path(self):

		# which gene does this transcript come from?
		# what are the min/max coords in the gene?
		gid = self.get_gid_from_tid(self.tid)
		g_min, g_max = self.get_gene_min_max(gid)
		t_min, t_max = self.get_transcript_min_max(self.tid)
		g_len = g_max - g_min

		x_min = int(g_min-(6/(g_max-g_min)))
		x_max = int(g_max+(6/(g_max-g_min)))

		strand = self.get_strand_from_tid(self.tid)
		path = self.get_path_from_tid(self.tid)

		# plotting init
		plt.figure(1, figsize=(14,2.8), frameon=False)
		plt.xlim(x_min, x_max)
		plt.ylim(-1.05, 1.05)
		ax = plt.gca()
		teal = '#014753'

		# plot each exon as a rectangle
		y_coord = -0.1
		height = 0.2
		exons = [(v1,v2) for v1,v2 in zip(path[:-1],path[1:])][::2]
		introns = [(v1,v2) for v1,v2 in zip(path[:-1],path[1:])][1::2]
		for v1,v2 in exons:
			x_coord = self.loc_df.loc[v1, 'coord']
			width = self.loc_df.loc[v2, 'coord'] - x_coord
			rect = pch.Rectangle((x_coord,y_coord), width, height, color=teal)
			ax.add_patch(rect)

		# plot each intron as a hashed line
		dist = 0.001*g_len
		plt.plot([t_min+dist, t_max-dist], [0,0], color=teal)

		# reverse plot if we're on the minus strand
		if strand == '-':
			plt.gca().invert_xaxis()

		def get_tick_coords(loc_df, gene_len, exons, g_min, g_max):

			tick_coords = list(np.linspace(g_min, g_max, 40))

			# remove ticks that are before the start of the first exon
			start = loc_df.loc[exons[0][1], 'coord']
			tick_coords = [t for t in tick_coords if t > start]

			# remove ticks that are after the end of the last exon
			stop = loc_df.loc[exons[-1][1], 'coord']
			tick_coords = [t for t in tick_coords if t < stop]

			# remove ticks in and around the area of plotted exons
			dist = 0.001*gene_len
			for v1,v2 in exons:
				start = loc_df.loc[v1, 'coord']
				stop = loc_df.loc[v2, 'coord']
				tick_coords = [t for t in tick_coords
							   if t < start-dist or t > stop+dist]

			# if we only have one intron, and nothing made the cut, just stick
			# a tick in the middle of the intron
			if len(exons) == 2 and not tick_coords:
				tick_coords = [(loc_df.loc[exons[0][1],'coord']+loc_df.loc[exons[1][0],'coord'])/2]

			return tick_coords 

		# get coordinates for evenly-spaced ticks indicating strandedness
		# ala genome browser
		tick_coords = get_tick_coords(self.loc_df, g_len, exons, g_min, g_max)
		plt.plot(tick_coords, [0 for i in range(len(tick_coords))],
			color=teal, marker='4')

	# plots the scale of a browser style graph
	def plot_browser_scale(self):

		# get plot limits
		g_min = self.g_min
		g_max = self.g_max 

		plt.figure(1, figsize=(14,1), frameon=False)
		plt.xlim(g_min, g_max)
		plt.ylim(-1.05, 1.05)
		ax = plt.gca()

		y_coord = -0.5
		height = 1
		gene_len = g_max - g_min
		width = int((10/10000)*gene_len)

		# try different scale lengths until we've reached something optimal
		scale_len = 500
		while 1:
			if scale_len >= gene_len:
				break
			scale_len *= 10

		# dial it back so we show a nice chunk instead of the scale of the entire gene
		scale_len /= 10

		# are we going to indicate this in bp or kb?
		if scale_len < 1000: 
			scale_unit = 'bp'
			text_scale = scale_len
		elif scale_len < 10**6:
			scale_unit = 'kb'
			text_scale =  scale_len / 1000
		else:
			scale_unit = 'mb'
			text_scale = scale_len / 10**6
		text_scale = int(text_scale)

		# center the edges of the scale bar
		gene_mid_pt = (g_max+g_min)/2
		scale_start = gene_mid_pt - (scale_len/2)
		scale_end = gene_mid_pt + (scale_len/2)

		# start bar
		rect = pch.Rectangle((scale_start, y_coord),
			width=width,
			height=height,
			color='k')
		ax.add_patch(rect)

		# end bar
		rect = pch.Rectangle((scale_end, y_coord),
			width=width,
			height=height,
			color='k')
		ax.add_patch(rect)

		# plot the line in between the bars
		plt.plot([scale_start+width, scale_end-width], [0,0],
			color='k',
			linewidth=2)

		# add scale text
		x_coord = scale_start - (gene_len/9)
		y_coord = -0.2
		txt = '{} {}'.format(text_scale, scale_unit)
		ax.text(x_coord, y_coord, txt, fontsize=30)

	###############################################################################
	####################### Combining non-branching paths #########################
	###############################################################################

	# starting nodes:
	# 1. have only one outgoing edge AND
	# 2. have more than one incoming edge | is a TSS | parent is TES AND
	# 3. node is not already in a nbp AND
	# 4. node is not a TES

	def is_nbp_start(self,n,nbps):
		nbp_nodes = [n for p in nbps for n in p]
		if self.G.out_degree(n) == 1: # 1
			tes_parent = False
			parents = list(self.G.predecessors(n))
			if parents: 
				for parent in parents:
					if self.loc_df.loc[parent, 'TES']:
						tes_parent = True
			if self.G.in_degree(n) >= 1 or self.loc_df.loc[n, 'TSS'] or tes_parent: # 2
				if n not in nbp_nodes and not self.loc_df.loc[n, 'TES']: # 3 & 4
					return True
		return False

	# end nodes:
	# 1. have more than one outgoing edge |
	# 2. have more than one incoming edge |
	# 3. is a TSS |
	# 4. parent node is a TES
	def is_nbp_end(self,n):
		if self.G.out_degree(n) > 1: # 1
			return True
		if self.G.in_degree(n) > 1: # 2
			return True
		if self.loc_df.loc[n, 'TSS']: # 3 
			return True
		if self.G.in_degree(n) == 1: # 4
			parent = list(self.G.predecessors(n))[0]
			if self.loc_df.loc[parent, 'TES']:
				return True
		return False

	# http://rosalind.info/problems/ba3m/ ty pavel 
	# modified to not group up TES/TSSs 
	# see is_nbp_start/end to see complete set of conditions
	def find_nb_paths(self):
		nbps = []
		# for v in self.G.nodes:
		for v in self.loc_df.vertex_id.tolist():
			if self.is_nbp_start(v,nbps):
				if self.G.out_degree(v) > 0:
					for w in self.G.successors(v):
						nbp = [v]
						while not self.is_nbp_end(w):
							nbp.append(w)
							succ = list(self.G.successors(w))
							if succ: w = succ[0]
							else: break
						if len(nbp) > 1: nbps.append(nbp)
		return nbps

	# aggregate nonbranching nodes and add to graph. remove old nodes
	# update loc_df, edge_df, and t_df to reflect these changes
	def agg_nb_nodes(self, nbps):

		G = self.G
		loc_df = self.loc_df
		edge_df = self.edge_df
		t_df = self.t_df

		# loop through each transcript path and each non-branching path,
		# replace nodes that will be aggregated 
		# need to use this because pandas copy DOES NOT deepcopy lists!
		t_df['new_path'] = t_df.apply(lambda x: copy.deepcopy(x.path), axis=1)
		paths = t_df.new_path.tolist() # this gives me a reference to the paths
									   # so when updating I'm directly modifying t_df
		for path in paths: 
			for combined_index, nbp in enumerate(nbps):

				# get the nodes that need to be deleted from the path
				# in the order they need to be deleted 
				del_nodes = sorted([int(i) for i in list(set(path) & set(nbp))],
					key=lambda x: loc_df.loc[x, 'coord'])

				# remove each node that is in a non-branching path
				for i, n in enumerate(del_nodes):
					if i == 0: 
						insertion_index = path.index(n)
					path.remove(n)
				if not del_nodes: insertion_index = -1

				# add aggregate node tuple instead
				if insertion_index != -1:
					path.insert(insertion_index, 'c{}'.format(combined_index))

		# shuffle around path columns in dataframe
		t_df.drop('path', axis=1, inplace=True)
		t_df.rename({'new_path': 'path'}, axis=1, inplace=True)

		mod_G = nx.DiGraph(G)

		combined_index = 0
		loc_df['agg_path'] = np.nan
		loc_df['combined'] = False
		loc_df['combined_types'] = np.nan

		# loop through each nonbranching path
		for path in nbps: 

			# some beginning of new loop things we need
			path = tuple([int(n) for n in path])
			start = path[0]
			stop = path[-1]
			combined_node = 'c{}'.format(combined_index)

			# get the colors for each aggregate node
			# TODO need to fix how these are colored with alt TSS/TES vs singleton TSS/TES
			combined_types = [j[0] for j in sorted([i for i in 
			   [('alt_TSS',loc_df.loc[path,'alt_TSS'].tolist().count(True)),
			   ('alt_TES',loc_df.loc[path,'alt_TES'].tolist().count(True)),
			   ('internal',loc_df.loc[path,'internal'].tolist().count(True))]
			   if i[1] != 0], key=lambda x: x[1], reverse=True)][:2]

			# compose the entries for edge_df for new edges that will be added
			# that include the aggregate node
			edge_attrs = {}
			if mod_G.in_degree(start) > 0: 
				in_nodes = list(mod_G.predecessors(start))
				old_edges = [(v1, start) for v1 in in_nodes]
				edge_attrs.update({(edge_id[0], combined_node): item for edge_id,item in edge_df.loc[old_edges,
					['strand', 'edge_type']].to_dict(orient='index').items()})
			if G.out_degree(stop) > 0: 
				out_nodes = list(mod_G.successors(stop))
				old_edges = [(stop, v2) for v2 in out_nodes]
				edge_attrs.update({(combined_node, edge_id[1]): item for edge_id,item in edge_df.loc[old_edges,
					['strand', 'edge_type']].to_dict(orient='index').items()})

			# compose the entry for loc_df for the aggregate node
			coord = loc_df.loc[start, 'coord']
			chrom = loc_df.loc[start, 'chrom']
			strand = loc_df.loc[start, 'strand']
			tss = alt_tss = tes = alt_tes = internal = False
			for n in path:
				if loc_df.loc[n, 'TSS']: tss = True
				if loc_df.loc[n, 'TES']: tes = True
				if loc_df.loc[n, 'alt_TSS']: alt_tss = True
				if loc_df.loc[n, 'alt_TES']: alt_tes = True
				if loc_df.loc[n, 'internal']: internal = True
			loc_attrs = {'chrom': chrom, 'strand': strand,
				   'vertex_id': combined_node,
				   'combined_types': combined_types,
				   'vertex_id_back': combined_node,
				   'agg_path': list(path), 'combined': True,
				   'TSS': tss, 'TES': tes,
				   'alt_TSS': alt_tss, 'alt_TES': alt_tes,
				   'internal': internal, 'coord': coord}

			# # if we're indicating dataset, which datasets are these nodes present in?
			# if args['indicate_dataset']:
			# 	node_attrs = assign_combined_datasets(mod_G, path, node_attrs)

			# add the new node and edges to graph
			mod_G.add_node(combined_node)
			for edge,item in edge_attrs.items():
				mod_G.add_edge(edge[0], edge[1])

			# also add aggregate node and all associated edges to loc_df, edge_df
			loc_df.reset_index(drop=True, inplace=True)
			edge_df.reset_index(drop=True, inplace=True)
			for edge, edge_attr in edge_attrs.items():
				edge_attr.update({'edge_id': edge, 'strand': strand,
								  'v1': edge[0], 'v2': edge[1]})
				edge_df = edge_df.append(edge_attr, ignore_index=True)
			loc_df = loc_df.append(loc_attrs, ignore_index=True)
			loc_df = create_dupe_index(loc_df, 'vertex_id')
			edge_df = create_dupe_index(edge_df, 'edge_id')
			loc_df = set_dupe_index(loc_df, 'vertex_id')
			edge_df = set_dupe_index(edge_df, 'edge_id')

			# remove all old nodes from the graph
			for n in path:
				mod_G.remove_node(n)

			# increment combined node index
			combined_index += 1

		# remove all old nodes/edges from loc_df and edge_df
		del_nodes = [n for path in nbps for n in path]
		del_edges = []
		del_edges += edge_df.loc[edge_df.v1.isin(del_nodes), 'edge_id'].tolist()
		del_edges += edge_df.loc[edge_df.v2.isin(del_nodes), 'edge_id'].tolist()
		del_edges = list(set(del_edges))

		loc_df = loc_df.loc[~loc_df.index.isin(del_nodes)] 
		edge_df = edge_df.loc[~edge_df.index.isin(del_edges)]

		self.G = mod_G
		self.loc_df = loc_df 
		self.edge_df = edge_df 
		self.t_df = t_df

	# gets nodes ordered by genomic position 
	def get_ordered_nodes(self):
		loc_ids = self.loc_df.vertex_id.tolist()
		coords = self.loc_df.coord.tolist()
		ordered_nodes = [i[0] for i in sorted(zip(loc_ids, coords),
			key=lambda x: x[1])]
		return ordered_nodes

# is this entry unique from all other datasets?
# and in the provided dataset?
def unique_to_dataset(dataset_name, x, dataset_cols):
	if not dataset_name: return False
	if x[dataset_name] and not any(x[dataset_cols].tolist()):
		return True
	return False

# is this entry in the provided dataset?
def in_dataset(dataset_name, x):
	if not dataset_name: return False
	return x[dataset_name]

# is this entry annotated?
def is_novel(indicate_novel, x):
	if not indicate_novel: return False
	if x.annotation: return False
	return True



