import numpy as np
from scipy.stats import johnsonsu, kstest


class strain:
	"""Class defines a cytological profiling feature. """
	def __init__(self, name):
		self.name = name
		self.val = dict()

	def __getitem__(self, run):
		return self.val[run]
	
	def __setitem__(self, run, value):
		self.val[run] = value
	
	def __delitem__(self, run):
		del self.val[run]
		
	def __repr__(self):
		return self.name
		
	def values(self):
		return self.val.values()
		
	def keys(self):
		return self.val.keys()
		
	def items(self):
		return self.val.items()
		
	def merge(self, strn):
		"""Function takes another feature object and merges it into this feature object if the
		names match."""
		if strn.name == self.name:
			for run, val in strn.items():
				self.val[run] = val

class prefraction:
	"""Class defines a cytological fingerprint for one compound/well."""
	def __init__(self, name):
		self.name = name
		self.fingerprint = dict()
		self.pearson = dict()

	def __getitem__(self, strn):
		return self.fingerprint[strn]
	
	def __setitem__(self, strn, value):
		self.fingerprint[strn] = value
		
	def __delitem__(self, strn):
		del self.fingerprint[strn]
		
	def __repr__(self):
		return self.name
		
	def values(self):
		return self.fingerprint.values()
	
	def keys(self):
		return self.fingerprint.keys()
		
	def items(self):
		return self.fingerprint.items()
		
	def activity_score(self):
		return sum(self.fingerprint.values())

		
class synleth:
	def __init__(self, tabfile, metric='absorptiondiff'):
		self.metric = metric
		self.prefs, self.strns, self.mets, self.raw_data = self.parse_tab(open(tabfile))
		self.data = self.binarify_dataset(self.raw_data, self.strns, metric)
		
		self.prefs = [prefraction(pref) for pref in self.prefs if pref in self.data]
		self.strns = [strain(strn) for strn in self.strns if strn in self.data]
		
		self.map = dict([(str(pref), pref) for pref in self.prefs] + [(str(strn), strn) for strn in self.strns])
		
		for pref in self.prefs:
			for strn in self.strns:
				self.map[str(pref)][str(strn)] = self.map[str(strn)][str(pref)] = self.data[str(pref)][str(strn)][metric]
				
	def __getitem__(self, key):
		return self.map[key]
	
	def __setitem__(self, key, val):
		self.map[key] = val
		
	def __delitem__(self, key):
		del self.map[key]
				
		
	def parse_tab(self, tabfile):
		one = tabfile.readline()
		two = tabfile.readline()
		
		labels = []
		strain = ""
		for tb, spec in zip(one.strip().split("\",\""), two.strip().split("\",\"")):
			tb = tb.strip().strip("\"")
			if not tb == "":
				strain = tb
			spec = spec.strip().strip("\"")
			labels.append(strain + "_" + spec)
		
		data = [[c.strip().strip("\"") for c in l.strip().strip(",").split("\",\"")] for l in tabfile]
		
		data_dict = dict()
		prefs = []
		strns = []
		mets = []
		
		for row in data:
			pref = row[3]
			data_dict.setdefault(pref, dict())
			if pref not in prefs:
				prefs.append(pref)
			for label, value in zip(labels[11:-1], row[11:]):
				strain, metric = label.split("_")
				data_dict[pref].setdefault(strain, dict())
				data_dict[pref].setdefault(metric, dict())
				data_dict.setdefault(strain, dict())
				data_dict[strain].setdefault(pref, dict())
				data_dict[strain].setdefault(metric, dict())
				if metric not in mets:
					mets.append(metric)
				if strain not in strns:
					strns.append(strain)
				
				if "," in value:
					value = map(float, [ele.strip() for ele in value.split(",")])
				elif value == "":
					value = 0
				else:
					value = float(value)
					
				data_dict[pref][metric][strain] = data_dict[pref][strain][metric] = data_dict[strain][pref][metric] =\
					data_dict[strain][metric][pref] = value
		return prefs, strns, mets, data_dict
		
	def append_tab(self, tabfile):
		prefs, strns, mets, raw_data = self.parse_tab(tabfile)
		
		binary_data = self.binarify_dataset(raw_data)
		
		prefs = [prefraction(pref) for pref in prefs]
		strns = [strain(strn) for strn in strns]
		
		self.prefs.extend(prefs)
		self.strns.extend(strns)
		self.mets.extend(mets)
		self.raw_data = dict(self.raw_data.items() + raw_data.items())
		self.binary_data = dcit(self.binary_data.items() + binary_data.items())
		
		for pref in prefs:
			self.map[run.name] = run
		for feat in feats:
			if str(feat) in self.map:
				continue
			else:
				self.map[str(feat)] = feat
				
		for pref in prefs:
			for strn in strns:
				self.map[str(pref)][str(strn)] = self.map[str(strn)][str(pref)] = binary_data[str(pref)][str(strn)][metric]
				
	def binarify_dataset(self, raw_data, strns, metric):
		"""Function accepts a data dictionary generated by the parse_tab function, a list of strains of interest, and a metric of
		interest. function fits the data series for each strain to a johnsonsu distribution, identifies a live/dead cutoff value
		and the mean - 1/2 the std deviation, and replaces all 'live' values with 0 and all 'dead' values with 1.
		"""
		binary_data = dict()
		for strn in strns:
			cutoff = self.curvefit(raw_data[strn][metric].values())
			if not cutoff:
				continue
			binary_data.setdefault(strn, dict())
			for pref in raw_data[strn][metric].keys():
				binary_data[strn].setdefault(pref, dict())
				binary_data[strn].setdefault(metric, dict())
				binary_data.setdefault(pref, dict())
				binary_data[pref].setdefault(metric, dict())
				binary_data[pref].setdefault(strn, dict())
				if raw_data[strn][pref][metric] >= cutoff:
					binary_data[strn][metric][pref] = binary_data[strn][pref][metric] = binary_data[pref][metric][strn] = binary_data[pref][strn][metric] = 0
				else:
					binary_data[strn][metric][pref] = binary_data[strn][pref][metric] = binary_data[pref][metric][strn] = binary_data[pref][strn][metric] = 1
		return binary_data
				
	def curvefit(self, series):
		"""Function accepts an iterable series of datapoints representing a johnson su distribution. The series is fit, tested
		for goodness of fit (p value greater than 0.1 and data mean value greater than 0.5) and the mean - 1/2 the std deviation
		is returned. None is returned if fit is bad.
		"""
		parameters = johnsonsu.fit(series)
		func = johnsonsu(*parameters)
		D, p = kstest(series, 'johnsonsu', N = len(series), args=parameters)
		if p < 0.1 or func.mean() < 0.5:
			return None
		return func.mean() - func.std() / 2
