# Vahid Mirjalili
# Clustering Validation and Analysis

import numpy as np
import pyprind
import sys
from ..cluster import Cluster
import editdist

def cal_distance(x1, x2, method='euclidean'):
    if method == 'euclidean':
        diff = x1 - x2
        sq_diff = diff * diff
        return(np.sqrt(np.sum(sq_diff)))
    elif method == 'editdist':
	return(editdist.distance(x1, x2))


class PairwiseDistanceSampler(Cluster):
    """
    Class for sampling intra and inter cluster pairwise distances

    Class Methods:

    intra_sampler()
    inter_sampler()

    """

    def __init__(self, X, y):
        self.set_data(X, y)

    def intra_sampler(self, cinx, size=None, method='euclidean', return_ind=False, outfile=None):
	"""
	Sampling intra-cluster pairwise distances.

	Arguments: 
		X (feature vectors/data)
		y (cluster memberships)
		cinx (distinct cluster index) to run sampling for a particular cluster
		size (amount of sampling), if None, all exclusive pairwise distances are computed.
		method: distance method, such as 'euclidean', 'mahalanobis', ...

	"""

	X_clust = self.X[self._get_members(cinx)]

        nsize = X_clust.shape[0]

	totpw = size
	if totpw is None:
	   totpw = nsize*(nsize-1)/2
	sys.stderr.write("##\tCluster %s  --> Size: %d\n" %(cinx, nsize))


	rand_pair_ind = np.random.randint(low=0, high=nsize, size=(totpw,2))

	if outfile is None:
	    dary = np.zeros(shape=totpw, dtype='float')
	    n = 0
	    for i,j in rand_pair_ind:
		dary[n] = cal_distance(X_clust[i], X_clust[j], method=method)
        	n += 1
	    return(dary)
	else:
	    with open(outfile, 'w') as fp:
		for i,j in rand_pair_ind:
		   dist_ij = cal_distance(X_clust[i], X_clust[j], method=method)
		   fp.write("%-d %-d %.4f\n" %(i, j, dist_ij))

	    return(None)




    def inter_sampler(self, cinx, size=None, method='euclidean', return_ind=False, outfile=None):
	"""
	Sampling inter-cluster pairwise distances 
	for a particular cluster (cinx) with respect to all other clusters.


	"""
        memb_indx = self._get_members(cinx)
	rest_indx = self._get_non_members(cinx)

        nsize_memb = memb_indx.shape[0]
	nsize_rest = rest_indx.shape[0]

	totpw = size
	if totpw is None:
	    totpw = nsize_memb * nsize_rest

	sys.stderr.write("##\tCluster %s w.r.t. rest --> Size: %d x %d  Total pairs: %d\n" 
		%(cinx, nsize_memb, nsize_rest, totpw))

	rand_memb_ind = memb_indx[np.random.randint(low=0, high=nsize_memb, size=totpw)]
	rand_rest_ind = rest_indx[np.random.randint(low=0, high=nsize_rest, size=totpw)]

	
	if outfile is None:
	    # No file is given, so save the output in a numpy array
	    dary = np.zeros(shape=totpw, dtype='float')
            n = 0 
            for i,j in zip(rand_memb_ind, rand_rest_ind):
                dary[n] = self.distance(i, j) #, method=method)
                n += 1
	    return(dary)
	
	else:
	    # Save the output in the given file
	    with open(outfile, 'w') as fp:
	       for i,j in zip(rand_memb_ind, rand_rest_ind):
	          dist_ij = self.distance(i, j, method=method)
	          fp.write("%-d %-d %.4f\n" %(i, j, dist_ij))

	    return(None)
