#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 14 09:04:47 2018
@copyright: Copyright 2019, HITS gGmbH
@license: EUPL v. 1.2
@author: richtesn
"""
from rpy2.robjects import r
# import rpy2's package module
import rpy2.robjects.packages as rpackages
#import rpy2.robjects.numpy2ri
from rpy2.robjects import pandas2ri
import pandas as pd
import rpy2.robjects as ro
from rpy2.robjects.packages import importr

from rpy2.robjects.conversion import localconverter


#import rpy2
#import rpy2.robjects as robjects
#import rpy2.robjects.packages as rpackages
import numpy as np
import math
from multipipsa.pipsatypes import *
import re

#import pandas as pd

regex_sims_log_dict = {
    'start': re.compile(r'#\s+(\d+)'),
    'comp': re.compile(r'(\w+)\s(\w+)'),
    'val': re.compile(r'([0-9.]+)\s+([0-9.]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)\s+([0-9.Ee+-]+)')}


def _parse_line(line):
    """  Do a regex search against all defined regexes and
    return the key and match result of the first matching regex
    """
    # print(line)
    for key, rx in regex_sims_log_dict.items():
        match = rx.search(line)
        if match:
            return key, match
    return None, None


R_REPO = "https://cloud.r-project.org"


class ClusterPipsa:
    """ Providing method for statistical analysis of PIPSA results.
    """

    def importfromR(self, packname):
        try:
            rpack = rpackages.importr(packname)
        except Exception:
            utils = rpackages.importr("utils")
            # , contriburl = R_REPO)
            utils.install_packages(packname, dependencies=True,repos="https://cloud.r-project.org")
            rpack = rpackages.importr(packname)
        return rpack

    def __init__(self,  structures=[], pipsaRoot="pipsa", dataDir=".", referenceCluster=None, distanceType=DistanceType.PIPSA, graphicsFileRoot="file"):
        self.__pipsa_root = pipsaRoot
        self.__distanceType = distanceType
        self.__data_dir = dataDir
        self.__structures = structures
        self.__sims_log = "sims.log"
        self.__number_of_proteins = None
        self.__sims_comparison = None
        self.__clusterResult = {}
        self.__referenceCluster = referenceCluster
        self.__graphicsFileRoot = graphicsFileRoot

        print("Installing required R-libraries into a user directory")
        print("In case you get asked answer 'y', also in case you get asked ")
        print("to create the directory.")
        print("To avoid this, create a file called '.Renviron' in your home")
        print("directory and add 'R_LIBS_USER=~/.R/lib' in this file")

        utils = rpackages.importr("utils")

        # if (!requireNamespace("gplots", quietly = TRUE))
        #   install.packages("gplots")

#        utils.install_packages(package_gplots, repos=ClusterPipsa.R_REPO)
#        package_cluster = "cluster"
#        utils.install_packages(package_cluster, repos=ClusterPipsa.R_REPO)
#        package_graphics="graphics"
#        utils.install_packages(package_graphics, repos=ClusterPipsa.R_REPO)
#        package_stats = "stats"
#        utils.install_packages(package_stats, repos=ClusterPipsa.R_REPO)
#        package_heatmap3="heatmap3"
#        utils.install_packages(package_heatmap3, repos=ClusterPipsa.R_REPO)
# package_fpc="fpc"
#        utils.install_packages(package_fpc, repos=ClusterPipsa.R_REPO)
        # import R packages

    def _getStructureOrdinal(self, name):
        """ Retuns the number of a structure, given its name.
        """
        i = 0
        for struct in self.__structures:
            if name == struct:
                return i
            i = i + 1
        return -1

    def clusterSingleRun(self, points=[], similarityType=SimilarityType.HODGKIN,
                         pipsaLog="sims.log",
                         clusterType=ClusterType.AGNES):
        """ Generates heatmap and tree images of a PIPSA run. 
        """
        self.__sims_log = pipsaLog
        self.__similarity_type = similarityType
        self.__points = points
        if len(points) == 0:
            self.__number_of_points = 1
        else:
            self.__number_of_points = len(points)
        self._parseSimslog()
        # pandas dataframes can more easily be converted into R structures
#        pandas2ri.activate()
        self.__clusterResult = []
        # print(self.__sims_comparison)
        base = self.importfromR('base')
#        gplots = self.importfromR('ggplot2')
#        cluster = self.importfromR('cluster')
        graphics = self.importfromR('graphics')
        stats = self.importfromR('stats')
#        fastclust = self.importfromR('fastcluster')
        heatmap3 = self.importfromR('heatmap3')
#        fpc = rpackages.importr('fpc')
        for i in range(0, self.__number_of_points):
            subarray = self.__sims_comparison[:, :, i]
            # convert the similarity matrix into a distance matrix
            self.__dist = pd.DataFrame(self._getDistance(np.nan_to_num(subarray),
                                                         self.__distanceType),
                                       columns=self.__structures, index=self.__structures)
            with localconverter(ro.default_converter + pandas2ri.converter):
              self.__rdist=ro.conversion.py2rpy(self.__dist)

            # rpy2.robjects.numpy2ri.activate()
            # import the R libraries

            #pvclust = rpackages.importr('pvclust')
    #        distclust= stats.hclust(stats.as_dist(self.__dist),method="ward.D2")
            #distclust= stats.hclust(stats.as_dist(self.__dist),method="complete")
            #distclust= stats.hclust(stats.as_dist(self.__dist), method="ward.D2")
            distclust = None
            # do the clustering
#            if clusterType==ClusterType.DIANA:
#                distclust= cluster.diana(stats.as_dist(self.__dist), diss=True,stand=False,method="complete")
#            else:
#                distclust= cluster.agnes(stats.as_dist(self.__dist), diss=True,stand=False,method="ward")
            # Print out a image file with the cluster according to the atom position
            distclust = stats.hclust(
                stats.as_dist(self.__rdist), method="ward.D2")
            grdevices = self.importfromR('grDevices')
            grdevices.png(file=self.__data_dir+"/"+self.__graphicsFileRoot +
                          "_"+str(i+1)+".png", width=512, height=512)
#            graphics.plot(distclust, main="", sub="")a
            stats.plot_hclust(distclust, main="",sub="")
            grdevices.dev_off()
            grdevices.png(file=self.__data_dir+"/"+self.__graphicsFileRoot +
                          "_heat3_"+str(i+1)+".png", width=512, height=512)
#            gplots.heatmap_2(base.as_matrix(self.__dist),margins=base.c(14,14),scale="none",
#                             distfun="function(n) n",hclustfun="function(x) hclust(self.__dist,method='ward.D2')",
#                             trace="none",revC=True,symkey=False,denscol="white",
#                             density_info='density',densadj=1)
            heatmap3.heatmap3(base.as_matrix(stats.as_dist(self.__rdist)), diss=True, na_rm=True, Colv=stats.as_dendrogram(
                distclust), Rowv=stats.as_dendrogram(distclust), ColSideLabs=self.__structures, symm=True)
#            heatmap3.heatmap3(base.as_matrix(stats.as_dist(self.__dist)),diss=True,na_rm=True,hclustfun=cluster.agnes,method="ward",ColSideLabs=self.__structures,symm=True)
#            heatmap3.heatmap3(base.as_matrix(stats.as_dist(self.__dist)))
            grdevices.dev_off()
            # Cut the tree into two clusters and compare to the reference cluster
            groups = stats.cutree(distclust, k=2)
            if self.__referenceCluster is not None:
                tree = self.__referenceCluster
            else:
                # if no reference cluster is given, use the two cluster solution
                tree = stats.cutree(distclust, k=2)
            #groupnames = base.names(groups)
            groupnames = self.__structures
            #print ("allgroups: "+str(groupnames))
            clusterResult = {'ingroup': [], 'outgroup': [], 'score': [
            ], 'position': i, 'tree': tree, 'groups': groups, 'correctedRand': 0.0}
            # print(groups)
            # print(groupnames)
            for i in range(0, len(groups)):
                if groups[i] == 1:
                    clusterResult['outgroup'].append(groupnames[i])
                else:
                    clusterResult['ingroup'].append(groupnames[i])
            if clusterType == ClusterType.DIANA:
                clusterResult['score'].append(distclust[2][0])
            else:
                clusterResult['score'].append(distclust[2][0])
            self.__clusterResult.append(clusterResult)
        clResulti = self.__clusterResult[0]
        for j in range(1, self.__number_of_points+1):
            clResultj = self.__clusterResult[j-1]
            #print("Comparing: "+str(i) +" with: "+str(j))
#            clResultj['correctedRand']=fpc.cluster_stats(stats.as_dist(self.__dist),clResulti['tree'],
#                                    clResultj['groups'],compareonly=True)[0][0]
        # print(self.__clusterResult)

    def getClusterResult(self):
        return self.__clusterResult

    def _getDistance(self, similarity, distanceType=DistanceType.PIPSA):
        """ Returns the distance within a PIPSA comparison based on 
        different methods to calculate them.
        """
        if distanceType == DistanceType.PIPSA:
            return np.nan_to_num(np.sqrt(2-2*similarity))
        if distanceType == DistanceType.LINEAR:
            return np.nan_to_num(1-similarity)
        if distanceType == DistanceType.SIGMOID:
            # return 1 + 0.5*(math.sqrt(2 - 2 * similarity) - math.sqrt(2 + 2 * similarity))
            return np.nan_to_num(0.5*(2-np.sqrt(2+2*similarity)+np.sqrt(2-2*similarity)))

    def _parseSimslog(self, similarityType=SimilarityType.HODGKIN):
        """ internal method to parse the sims.log file from pipsa and construct 
        numpy objects of the results.
        """
        fsimslog = open(self.__data_dir+"/"+self.__sims_log)
        currentComp1 = None
        currentComp2 = None
        point = 0
        for line in fsimslog.readlines():
            (key, match) = _parse_line(line.strip())
            # determine if the sims.log is at the beginning, or specifies two proteins or a result line
            if key is None:
                continue
            if key == 'start':
                # initialize the numpy array and count the proteins
                self.__number_of_proteins = int(str(match.group(1)))
                #print("Working on "+str(self.__number_of_proteins))
                self.__sims_comparison = np.zeros(
                    (self.__number_of_proteins, self.__number_of_proteins, self.__number_of_points))
                # print(self.__sims_comparison)
            if key == 'comp':
                currentComp1 = match.group(1)
                currentComp2 = match.group(2)
                point = 0
            if key == 'val':
                structOrdinal1 = self._getStructureOrdinal(currentComp1)
                structOrdinal2 = self._getStructureOrdinal(currentComp2)
                dist = 0
                if similarityType == SimilarityType.HODGKIN:
                    dist = match.group(1)  # hodkin
                if similarityType == SimilarityType.CARBO:
                    dist = match.group(2)  # carbo
                self.__sims_comparison[structOrdinal1][structOrdinal2][point] = dist
                self.__sims_comparison[structOrdinal2][structOrdinal1][point] = dist
                point = point + 1
                # self.__sims_comparison[structOrdinal1][structOrdinal2][2]=match.group(12) #common points
        fsimslog.close()


if __name__ == "__main__":
    print("clusterpipsa does not have a main function yet.")
