#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Created on Thu Nov 29 11:11:30 2018
@copyright: Copyright 2019, HITS gGmbH
@license: EUPL v. 1.2
@author: richtesn
"""
from __future__ import unicode_literals
from subprocess import check_output

import os
from glob import glob
import re
from string import Template
import itertools as it
import numpy as np
import math
from Bio.PDB import PDBParser
from Bio.PDB.PDBIO import PDBIO
import warnings
from pprint import pformat
from multipipsa.clusterpipsa import ClusterPipsa
from multipipsa.pipsatypes import ScoreType
from multipipsa.pipsatypes import SimilarityType
from multipipsa.pipsatypes import DistanceType
from gridData import Grid
warnings.filterwarnings("ignore")


class ResultProcessor:
    """ Class to normalize the B-Factor column between a given range using a
    numpy array."""

    def __init__(self):
        self.__do_normalize = False
        self.__useAbsoluteValues = False
        self.__normalize_min = 0
        self.__normalize_max = 99

    def setDoNormalize(self, doNormalize=False):
        """ Determines if a normalization should take place at all.

        :param doNormalize:  (Default value = False)

        """
        self.__do_normalize = doNormalize

    def setUseAbsoluteValues(self, useAbsoluteValues):
        """ Determines if negative values should be considered as positives

        :param useAbsoluteValues:

        """
        self.__useAbsoluteValues = useAbsoluteValues

    def setNormalizeRange(self, normalizeMin=0, normalizeMax=99):
        """Determines the minimum and maximum numbers used in the normalization

        :param normalizeMin:  (Default value = 0)
        :param normalizeMax:  (Default value = 99)

        """
        self.__normalize_max = normalizeMax
        self.__normalize_min = normalizeMin

    def setNormalize(self, doNormalize, normalizeMin=0, normalizeMax=99,
                     useAbsoluteValues=False):
        """ Convenience function to set all the parameters at once.

        :param doNormalize:
        :param normalizeMin:  (Default value = 0)
        :param normalizeMax:  (Default value = 99)
        :param useAbsoluteValues:  (Default value = False)

        """
        self.setDoNormalize(doNormalize)
        self.setNormalizeRange(normalizeMin=normalizeMin,
                               normalizeMax=normalizeMax)
        self.setUseAbsoluteValues(useAbsoluteValues)

    def normalize(self, rawpoints):
        """ Function actually performing the normalization on a numpy array.

        :param rawpoints: mumpy array of values

        """
        if self.__useAbsoluteValues is True:
            rawpoints = np.absolute(rawpoints)
        if (self.__do_normalize is False):
            return rawpoints
        mins = np.min(rawpoints, axis=0)
        maxs = np.max(rawpoints, axis=0)
        rng = maxs - mins
        return self.__normalize_max - (
            ((self.__normalize_max - self.__normalize_min) *
             (maxs - rawpoints)) / rng)


class PipsaResult:
    """ Storage class that holds the results of the comparision. This class
    is used within PipsaRun"""

    def __init__(self, resultProcessor=None):
        self.__use_weights = False
        self.__result_outgroup = [None]*4
        self.__result_ingroup = [None]*4
        self.__resultProcessor = None
        self.__result_ingroup_avgH = None
        self.__result_ingroup_avgC = None
        self.__result_outgroup_avgH = None
        self.__result_outgroup_avgC = None
        self.__result_outgroup_avgH_Dist = None
        self.__result_outgroup_avgC_Dist = None
        self.__result_ingroup_avgH_Dist = None
        self.__result_ingroup_avgC_Dist = None
        for i in range(0, 4):
            self.__result_ingroup[i] = []
            self.__result_outgroup[i] = []
        self.setResultProcessor(resultProcessor)

    def setResultProcessor(self, resultProcessor=None):
        """ Determines if the result should be processed by a normalization.
        If none, this will be replaced by a processor with normailzation=False

        :param resultProcessor:  (Default value = None)

        """
        if (resultProcessor is None):
            self.__resultProcessor = ResultProcessor()
        else:
            self.__resultProcessor = resultProcessor

    def setUseWeights(self, useWeights=False):
        """ Determines if the result should take into account that some 
        comparisons have higher weight, due to more points in the common area.

        :param useWeights:  (Default value = False)

        """
        self.__use_weights = useWeights

    def pushIngroupResult(self, x, y, r):
        """ Save result from ingroup comparision.

        :param x: protein 1 
        :param y: protein 2
        :param r: numpy array of result

        """
        self.__result_ingroup[0].append(x+" x "+y)
        self.__result_ingroup[1].append(np.asarray(r[0, :]))  # Hodgkin index
        self.__result_ingroup[2].append(np.asarray(r[1, :]))  # Carbo index
        self.__result_ingroup[3].append(np.asarray(r[4, :]))  # Common points
        if (self.__use_weights):
            self.__result_ingroup_avgH = np.nan_to_num(np.ma.average(
                np.array(self.__result_ingroup[1], dtype=np.float16),
                axis=0, weights=np.array(self.__result_ingroup[3],
                                         dtype=np.float16)).filled(0.0))
            self.__result_ingroup_avgC = np.nan_to_num(np.ma.average(
                np.array(self.__result_ingroup[2], dtype=np.float16),
                axis=0, weights=np.array(self.__result_ingroup[3],
                                         dtype=np.float16)).filled(0.0))
        else:
            self.__result_ingroup_avgH = np.nan_to_num(np.ma.average(
                np.array(self.__result_ingroup[1], dtype=np.float16),
                axis=0).filled(0.0))
            self.__result_ingroup_avgC = np.nan_to_num(np.ma.average(
                np.array(self.__result_ingroup[2], dtype=np.float16),
                axis=0).filled(0.0))

    def pushOutgroupResult(self, x, y, r):
        """ Save result from outgroup comparision.

        :param x:  protein 1
        :param y:  protein 2
        :param r:  numpy array of result

        """
        self.__result_outgroup[0].append(x+" x "+y)
        self.__result_outgroup[1].append(np.asarray(r[0, :]))  # Hodgkin index
        self.__result_outgroup[2].append(np.asarray(r[1, :]))  # Carbo index
        self.__result_outgroup[3].append(np.asarray(r[4, :]))  # Common points
        if (self.__use_weights):
            self.__result_outgroup_avgH = np.nan_to_num(np.ma.average(
                np.array(self.__result_outgroup[1], dtype=np.float16),
                axis=0, weights=np.array(self.__result_outgroup[3],
                                         dtype=np.float16)).filled(0.0))
            self.__result_outgroup_avgC = np.nan_to_num(np.ma.average(
                np.array(self.__result_outgroup[2], dtype=np.float16),
                axis=0, weights=np.array(self.__result_outgroup[3],
                                         dtype=np.float16)).filled(0.0))
        else:
            self.__result_outgroup_avgH = np.nan_to_num(np.ma.average(
                np.array(self.__result_outgroup[1], dtype=np.float16),
                axis=0).filled(0.0))
            self.__result_outgroup_avgC = np.nan_to_num(np.ma.average(
                np.array(self.__result_outgroup[2], dtype=np.float16),
                axis=0).filled(0.0))

    def _convertSimilaritiesToDistances(self):
        """ Convert the similarities from -1 to 1 (anticorrelated to  
        correlated) to positive distances 2 to 0 """
        self.__result_outgroup_avgH_Dist = np.sqrt(
            2-2*self.__result_outgroup_avgH)
        self.__result_outgroup_avgC_Dist = np.sqrt(
            2-2*self.__result_outgroup_avgC)
        self.__result_ingroup_avgH_Dist = np.sqrt(
            2-2*self.__result_ingroup_avgH)
        self.__result_ingroup_avgC_Dist = np.sqrt(
            2-2*self.__result_ingroup_avgC)

    def getCompactnessScore(self, similarityType=SimilarityType.HODGKIN):
        """ Get the Compactnessscore 
        (DIST(inter)-DIST(intra))/(max(DIST(inter),DIST(intra)))

        :param similarityType:  (Default value = SimilarityType.HODGKIN)

        """
        print ("Returning Compactness:")
        self._convertSimilaritiesToDistances()
        outAvgDist = self.__result_outgroup_avgH_Dist
        inAvgDist = self.__result_ingroup_avgH_Dist
        if similarityType == SimilarityType.CARBO:
            outAvgDist = self.__result_outgroup_avgC_Dist
            inAvgDist = self.__result_ingroup_avgC_Dist
        return self.__resultProcessor.normalize(
            np.divide(np.subtract(outAvgDist, inAvgDist),
                      np.maximum(outAvgDist, inAvgDist)
                      )
        )

    def getSimilarityScore(self, similarityType=SimilarityType.HODGKIN):
        """ Get the Similarity Matrix score:
            0.5*(SI(intra)-SI(inter))

        :param similarityType:  (Default value = SimilarityType.HODGKIN)

        """
        print ("Returning Similarity:")
        outAvgSim = self.__result_outgroup_avgH
        inAvgSim = self.__result_ingroup_avgH
        if similarityType == SimilarityType.CARBO:
            outAvgSim = self.__result_outgroup_avgC
            inAvgSim = self.__result_ingroup_avgC
        return self.__resultProcessor.normalize((0.5*(inAvgSim-outAvgSim)))


class PipsaStructure:
    """ Holds the template structure that is used to extract the points where
    a pipsa run is performed. Also used to annotate the resulting scores
    as B-Factors."""

    def __init__(self, dataDir=".", pointsTemplate="point"):
        self.__residues = []
        self.__pointsTemplate = dataDir+"/"+pointsTemplate+".pdb"
        self.__pipsaResult = None
        self.__data_dir = dataDir
        self.__ca_atoms = []
        self.__template_structure = self.getPointsFromTemplate()

    def getPointsFromTemplate(self):
        """ Return the structure and store all C-Alpha Atoms in an array"""
        parser = PDBParser()
        structure = parser.get_structure('myTemplate', self.__pointsTemplate)
        for model in structure.get_list():
            for chain in model.get_list():
                for residue in chain.get_list():
                    if residue.has_id("CA"):
                        ca_atom = residue["CA"].get_coord()
                        self.__residues.append(residue)
                        self.__ca_atoms.append(ca_atom)
        return structure

    def getCAAtoms(self):
        """ Return stored C-Alpha atoms. """
        return self.__ca_atoms

    def setPipsaResult(self, pipsaResult):
        """ Set an object that holds the pipsa results. This is needed to
        be able to retrieve the scores when the annotated structure is written.

        :param pipsaResult: PipsaResult object instance

        """
        self.__pipsaResult = pipsaResult

    def generateNewPdb(self, filename, scoreType=ScoreType.MS,
                       similarityType=SimilarityType.HODGKIN,
                       clusterObject=None):
        """ Write new PDB file of the template structure with B Factors 
        according to the selcted score type and similarity type.

        :param filename: File name used for new PDB structure
        :param scoreType:  (Default value = ScoreType.MS)
        :param similarityType:  (Default value = SimilarityType.HODGKIN)

        """
        temp = None
        if clusterObject != None:
            temp = []
#            for i in clusterObject.getClusterResult():
#                if scoreType==ScoreType.CORRECTED_RAND:
#                    temp.append(i['correctedRand'])
#                else:
#                    if scoreType==ScoreType.CLUSTER_INFORMATION:
#                        temp.append(i['score'][0])
        else:
            if (scoreType == ScoreType.MS):
                temp = self.__pipsaResult.getSimilarityScore(similarityType)
            if (scoreType == ScoreType.CS):
                temp = self.__pipsaResult.getCompactnessScore(similarityType)
        count_res = 0
        if len(self.__residues) != len(temp):
            print("Unequal Resiudues and attributes:")
            print("Residues: "+str(len(self.__residues)))
            print("Attributes: "+str(len(temp)))
        for res in self.__residues:
            for ato in res.get_atoms():
                if count_res < len(temp):
                    ato.set_bfactor(temp[count_res])
            count_res = count_res+1
        io = PDBIO()
        io.set_structure(self.__template_structure)
        io.save(self.__data_dir+"/"+filename)


class ApbsRun:
    """
    """
    apbs_in_template = Template("""
read
mol pqr $STRUCT.pqr
end
 
elec name viz
mg-manual
dime $DIM $DIM $DIM
grid 1.0 1.0 1.0
mol 1
gcent $X $Y $Z
lpbe
bcfl $BCFL
ion charge 1  conc $IOS radius 1.500
ion charge -1 conc $IOS radius 1.500
pdie $PDIE
sdie 78.00
chgm spl0
srfm smol
srad 0.0
swin 0.3
temp $TEMP
write pot uhbd $STRUCT.ascii
write pot dx $STRUCT
write ivdw dx ${STRUCT}_surf
end
 
quit
""")

    def __init__(self,
                 apbsRoot=None,
                 apbsExe="apbs",
                 pipsaRoot="pipsa",
                 pdb2PqrExe="pdb2pqr30",
                 dataDir=".",
                 temp="298.15",
                 ios="0.100",
                 pdie="1.0",
                 pH="7.0",
                 dimension="65",
                 forceField="AMBER",
                 bcfl="sdh",
                 structures=[]):
        if apbsRoot is not None:
            self.__apbs_exe = apbsRoot+"/bin/"+apbsExe
            self.__pdb2pqr_exe = apbsRoot+"/bin/"+pdb2PqrExe
        else:
            self.__apbs_exe = apbsExe
            self.__pdb2pqr_exe = pdb2PqrExe
        if not (bcfl == "sdh" or bcfl == "mdh"):
            print("%s is an unknown boundary condition setting %s " %
                  (bcfl, "sdh"))
            bcfl = "sdh"
        self.__pipsa_root = pipsaRoot
        self.__apbs_root = apbsRoot
        self.__data_dir = dataDir
        self.__structures = structures
        self.__data_dir = dataDir
        self.__ios = float(ios)
        self.__temp = float(temp)
        self.__center = [0, 0, 0]
        self.__pdie = float(pdie)
        self.__bcfl = bcfl
        self.__forceField = forceField
        self.__pH = pH
        self.__dimension = dimension
        self.__bjerrum_length = (1.602e-19**2) / \
            (4*3.14159*78*8.854e-12*1.380e-23*self.__temp)
        self.__debye_length = math.sqrt(
            1/(self.__bjerrum_length*8*3.14159*1e3*6.022e23*self.__ios))

    def _getCenter(self):
        xval = np.array([])
        yval = np.array([])
        zval = np.array([])
        maxdim = 0
        for i in self.__structures:
            with open(self.__data_dir+"/"+i+'.pdb') as fin:
                for line in fin:
                    if line.startswith('ATOM'):
                        line_vals = line.strip().split()
                        xval = np.append(xval, float(line_vals[6]))
                        yval = np.append(yval, float(line_vals[7]))
                        zval = np.append(zval, float(line_vals[8]))
            fin.close()
        self.__center[0] = np.mean(xval)
        self.__center[1] = np.mean(yval)
        self.__center[2] = np.mean(zval)
        self.__xmin = np.min(xval)
        self.__xmax = np.max(xval)
        maxdim = self.__xmax - self.__xmin
        self.__ymin = np.min(yval)
        self.__ymax = np.max(yval)
        if (maxdim < (self.__ymax-self.__ymin)):
            maxdim = self.__ymax-self.__ymin
        self.__zmin = np.min(zval)
        self.__zmax = np.max(zval)
        if (maxdim < (self.__zmax-self.__zmin)):
            maxdim = self.__zmax-self.__zmin
        self.__maxdim = maxdim

    def _generateApbsIn(self):
        self._getCenter()
        self._getDimension()
        for i in self.__structures:
            with open(self.__data_dir+"/"+i+'_apbs.in', "w") as fout:
                fout.write(
                    ApbsRun.apbs_in_template.
                    substitute(STRUCT=i,
                               IOS=float(self.__ios),
                               TEMP=float(self.__temp),
                               PDIE=float(self.__pdie),
                               BCFL=self.__bcfl,
                               X=self.__center[0],
                               Y=self.__center[1],
                               Z=self.__center[2],
                               DIM=self._getDimension()))
                fout.close()

    def _getDimension(self):
        apbs_dims = [97, 129, 161, 193, 225, 257, 289, 321]
        use_dims = apbs_dims[-1]
        # if hasattr(self, '__max_abs_charge'):
        print("Max Dim of Protein: "+str(self.__maxdim))
        print("Debye Length in A: "+str(self.__debye_length*1e10))
        if self.__debye_length*1e10 < 10:
            screen_length = self.__maxdim+60
        else:
            screen_length = self.__maxdim+6*self.__debye_length*1e10
        print("Selected screen length: "+str(screen_length))
        for i in apbs_dims:
            if screen_length < i:
                print("Using Dimension: "+str(i))
                return i
        print("Using Dimension: "+str(use_dims))
        return use_dims

    def runPdb2Pqr(self):
        min_charge = 1000000.0
        max_charge = -1000000.0
        for i in self.__structures:
            cmdpqr = [self.__pdb2pqr_exe,  "--ff",
                      self.__forceField, "--with-ph", self.__pH, i+".pdb", i+".pqr"]
            print("running:" + " ".join(cmdpqr))
            my_env = os.environ
            my_env["PYTHONIOENCODING"] = "UTF-8"
            out = check_output(cmdpqr, cwd=self.__data_dir,
                               universal_newlines=True, stderr=None, env=my_env)
            fpqr = open(self.__data_dir+"/"+i+".pqr")
            for line in fpqr.readlines():
                line = line.strip()
                if line.startswith("REMARK   5 "):
                    print(line+"here")
                if line.startswith("REMARK   6 Total charge on this protein:"):
                    m = re.search(
                        r'REMARK   6 Total charge on this protein:\s([-\d\.]+)\se', line)
                    total_charge = m.group(1)
                    print("Total charge is: "+str(total_charge))
                    if len(total_charge) > 0 and total_charge is not None:
                        if float(total_charge) < min_charge:
                            min_charge = float(total_charge)
                        if float(total_charge) > max_charge:
                            max_charge = float(total_charge)
        print("Maximum TOTAL CHARGE: "+str(max_charge) +
              "\nMinimum TOTAL CHARGE: "+str(min_charge))
        self.__max_abs_charge = abs(max_charge) if abs(
            max_charge) > abs(min_charge) else abs(min_charge)

    def runApbs(self):
        self._generateApbsIn()
        for i in self.__structures:
            cmdapbs = [self.__apbs_exe, i+"_apbs.in"]
            print("running:" + " ".join(cmdapbs))
            my_env = os.environ
            my_env["PYTHONIOENCODING"] = "UTF-8"
            out = check_output(cmdapbs, cwd=self.__data_dir,
                               universal_newlines=True, stderr=None, env=my_env)
            if os.path.isfile(self.__data_dir+"/"+i+".ascii-PE0.grd"):
                os.rename(self.__data_dir+"/"+i+".ascii-PE0.grd",self.__data_dir+"/"+i+".ascii.grd")
            if os.path.exists(self.__data_dir+"/"+i+".grd"):
                os.remove(self.__data_dir+"/"+i+".grd")
            cmdconvert = [self.__pipsa_root +
                          "/bin/uhbd_asc2bin", i+".ascii.grd", i+".grd"]
            print("running:" + " ".join(cmdconvert))
            out = check_output(cmdconvert, cwd=self.__data_dir,
                               universal_newlines=True, stderr=None, env=my_env)
            if os.path.isfile(self.__data_dir+"/"+i+"-PE0.dx"):
                os.rename(self.__data_dir+"/"+i+"-PE0.dx",self.__data_dir+"/"+i+".dx")
            if os.path.isfile(self.__data_dir+"/"+i+"_surf-PE0.dx"):
                os.rename(self.__data_dir+"/"+i+"_surf-PE0.dx",self.__data_dir+"/"+i+"_surf.dx")
            potentialGrid = Grid(str(self.__data_dir+"/"+i+".dx"))
#            potentialGrid.load(self.__data_dir+"/"+i+".dx")
            accesibilityGrid = Grid(str(self.__data_dir+"/"+i+"_surf.dx"))
#            accesibilityGrid.load(self.__data_dir+"/"+i+"_surf.dx")
            visualizeGrid = accesibilityGrid * potentialGrid
            visualizeGrid.export(self.__data_dir+"/"+i+"_vis.dx")


class PipsaRun:
    """ Runs the pipsa comparision and stores result in the PipsaResult
    object."""

    def __init__(self,
                 pipsaRoot="pipsa",
                 dataDir=".",
                 pointsTemplate="point",
                 radius=10.0):
        self.__pipsa_root = pipsaRoot
        self.__data_dir = dataDir
        self.__radius = radius
        self.__pipsaStructure = PipsaStructure(dataDir=dataDir,
                                               pointsTemplate=pointsTemplate)
        self.__pipsaResult = PipsaResult()
        self.__pipsaStructure.setPipsaResult(self.__pipsaResult)
        self.__cluster = None

    def _writeSphereFile(self, points=None, filesuffix=""):
        if points is None or not type(points) == list or len(points) == 0:
            self._removeSphereFile()
            return
        if len(points[0]) == 3:
            atoms = points
        else:
            atoms = [points]
        fil = open(self.__data_dir+'/spheres'+filesuffix, 'w')
        for ca_atom in atoms:
            fil.write('%s  %s  %s  %s\n' % (ca_atom[0], ca_atom[1], ca_atom[2],
                                            self.__radius))
        fil.close()

    def getStructure(self):
        return self.__pipsaStructure

    def _removeSphereFile(self):
        if os.path.exists(self.__data_dir+'/spheres'):
            os.remove(self.__data_dir+'/spheres')

    def getResultObject(self):
        """ Return a reference to the PipsaResult class instance."""
        return self.__pipsaResult

    def _runPipsaComparison(self, pr1, pr2):
        """ Run a comparision between protein 1 and protein 2.

        :param pr1: Name of Protein 1
        :param pr2: Name of Protein 2

        """
        self._writeSphereFile(points=self.__pipsaStructure.getCAAtoms())
        cmd = [self.__pipsa_root + "/bin/2potsim_skin_spheresNN", "-g1", pr1 +
               ".grd", "-g2", pr2+".grd", "-p1", pr1+".pdb", "-p2", pr2+".pdb",
               "-pa", "spheres", "-pr", "3.000", "-sk", "4.000"]
        print("running:" + " ".join(cmd))
        my_env = os.environ
        my_env["PYTHONIOENCODING"] = "UTF-8"
        out = check_output(cmd, cwd=self.__data_dir,
                           universal_newlines=True, stderr=None, env=my_env)
        count = len(out.splitlines())
        r = np.zeros((5, count), dtype=float)
        count = 0
        for line in out.splitlines():
            oList = line.split()
            if (oList[11] != 0):
                r[0, count] = float(oList[0])  # Hodgkin
                r[1, count] = float(oList[1])  # Carbo
                r[2, count] = float(oList[9])  # points in Protein 1
                r[3, count] = float(oList[10])  # points in Protein 2
                r[4, count] = float(oList[11])  # points in common
            count = count+1
        return np.nan_to_num(r, copy=True)

    def _runPipsaWithinGroup(self, ingroup):
        """ Run all comparisions between the proteins given in ingroup

        :param ingroup: List of proteins to run comparisions.

        """
        for x in it.combinations(ingroup, 2):
            r = self._runPipsaComparison(x[0], x[1])
            self.__pipsaResult.pushIngroupResult(x[0], x[1], r)

    def _runAcrossGroup(self, ingroup, outgroup):
        """ Run all comparisons from proteins in ingroup to proteins 
        in outgroup.

        :param ingroup: List of protein names to compare within
        :param outgroup: List of proteins names to compare to

        """
        print("Compare: "+str(ingroup))
        print("with   : "+str(outgroup))
        for x in ingroup:
            for y in outgroup:
                print("Comparing "+str(x)+" with "+str(y))
                r = self._runPipsaComparison(x, y)
                self.__pipsaResult.pushOutgroupResult(x, y, r)

    def setUseWeights(self, setWeights):
        """ Determine if the result should be weighted with the number of
        common points in the comparision.

        :param setWeights: True or False.

        """
        self.getResultObject().setUseWeights(setWeights)

    def runClusterPipsa(self, structures, cluster=None, points=None):
        if cluster is not None:
            self.__cluster = cluster
        if points is None:
            points = self.__pipsaStructure.getCAAtoms()
        self.runPipsa(structures, points=points)
#        residueNumber = 1
#        for p in self.__pipsaStructure.getCAAtoms():
#            residueNumber = residueNumber + 1

    def runBindingScorePipsa(self, ingroup, outgroup):
        """ Run Comparision, first within Group then Across Groups.
        Results are stored in the referenced PipsaResult instance.
        This is according to Tong, Wade and Bruce, Proteins 2016; 84:1844-1858

        :param ingroup: Defines the ingroup structures in the score calculation
        :param outgroup: Defines the outgroup structures of the score calculation

        """
#        if cluster is not None:
#            allProteins = ingroup + outgroup
#            self.runClusterPipsa(allProteins, cluster = cluster)
        self._runPipsaWithinGroup(ingroup)
        self._runAcrossGroup(ingroup, outgroup)

    def runPipsa(self, structures, points=[]):
        """ Runs a single pipsa run. 
        :param structures: a list of aligned structures to be used in the analysis
        :param points: a list of 3 coordinates for a single point or a list such coordinate lists.
        """
        if (structures is None or len(structures) == 0):
            print("No structures defined"+str(structures))
            return
        structf = open(self.__data_dir + "/names", "w")
        for struct in structures:
            structf.write(struct+"\n")
        structf.close()
        filesuffix = ""
        if points is None or len(points) == 0:
            self._removeSphereFile()
        else:
            filesuffix = "_" + str(len(points))
            self._writeSphereFile(points=points, filesuffix=filesuffix)
        cmd = [self.__pipsa_root + "/bin/npotsim", "-pg", self.__pipsa_root +
               "/bin/2potsim_skin_spheresU", "-fp", self.__data_dir, "-fn", "names", "-pa",
               "spheres"+filesuffix, "-lg", "sims"+filesuffix+".log",
               "-pr", "3", "-sk", "4"]
        print(" ".join(cmd))
        my_env = os.environ
        my_env["PYTHONIOENCODING"] = "UTF-8"
        check_output(cmd, cwd=self.__data_dir, universal_newlines=True,
                     stderr=None, env=my_env)
        if self.__cluster is not None:
            self.__cluster.clusterSingleRun(points=points, similarityType=SimilarityType.HODGKIN,
                                            pipsaLog="sims"+filesuffix+".log")
#        cmdPipsa2R = ["perl", self.__pipsa_root + "/aux/pipsa2R.pl",
#                      "-s", "sims.log", "-t", "h", "-m", pictFile,
#                      "-g", "-a"]
#        print(" ".join(cmdPipsa2R))
#        check_output(cmdPipsa2R, cwd=self.__data_dir, universal_newlines=True,
#                     stderr=None, env=my_env)
#        return pictFile+".png"

    def savePDBResult(self, filename="Similarities.pdb",
                      scoreType=ScoreType.MS,
                      similarityType=SimilarityType.HODGKIN,
                      clusterObject=None):
        """ Store result scores as B-Factor column in a PDB file.
        The PDB file used is the same as set as template structure.

        :param filename:  (Default value = "Similarities.pdb")
        :param scoreType:  (Default value = ScoreType.MS)
        :param similarityType:  (Default value = SimilarityType.HODGKIN)

        """
        if clusterObject != None:
            self.__pipsaStructure.generateNewPdb(filename, scoreType=scoreType,
                                                 similarityType=similarityType,
                                                 clusterObject=clusterObject)
        else:
            self.__pipsaStructure.generateNewPdb(filename, scoreType=scoreType,
                                                 similarityType=similarityType)


if __name__ == "__main__":
    cwd = os.getcwd()

    # This assumes that the script is run from within the package directory
    # and the user has write access here.
    localPIPSA = os.path.join(cwd, 'data', 'pipsa')
    examplePDBs = os.path.join(cwd, 'data', 'example', 'pdbs')

    ingrp = ["AC1", "AC5", "AC6"]
    outgrp = ["AC2", "AC3", "AC4", "AC7", "AC8", "AC9"]
#    apbs = ApbsRun(dataDir=examplePDBs, pipsaRoot=localPIPSA,
#                   temp=298.15,ios=0.100, structures=ingrp+outgrp)
#    apbs.runPdb2Pqr()
#    apbs.runApbs()
    pr = PipsaRun(pipsaRoot=localPIPSA,
                  dataDir=examplePDBs,
                  pointsTemplate="AC5")
    pr.setUseWeights(False)  # Optional
    referenceCluster = np.array([1, 1, 1, 2, 2, 2, 2, 2, 2])
    cl = ClusterPipsa(structures=ingrp+outgrp, pipsaRoot=localPIPSA,
                      dataDir=examplePDBs,
                      referenceCluster=referenceCluster)

#    #rp = ResultProcessor()  #Optional
#    #rp.setNormalize(True)  #Optional
#    #rp.setNormalize(False, useAbsoluteValues=True) #OPtional
#    #pr.getResultObject().setResultProcessor(rp)    #Optional
    print("Run PIPSA over whole Protein:")
    pr.runPipsa(ingrp+outgrp)  # runs a pipsa over the whole protein
    print("Run PIPSA over the first C-Alpha Atom")
    pr.runPipsa(ingrp+outgrp, points=pr.getStructure().getCAAtoms()[10])
    print("Run PIPSA over all C-Alpha Atoms and cluster each")
    pr.runClusterPipsa(ingrp+outgrp)
#    pr.savePDBResult(filename="Cluster.pdb",clusterObject=cl)
    print("Run ingroup and outgroup comparision and also over all C-Alpha Atoms")
    pr.runBindingScorePipsa(ingrp, outgrp)

    pr.savePDBResult(filename="SimHodgkin.pdb",
                     scoreType=ScoreType.MS,
                     similarityType=SimilarityType.HODGKIN)
    pr.savePDBResult(filename="SimCarbo.pdb",
                     scoreType=ScoreType.MS,
                     similarityType=SimilarityType.CARBO)
    pr.savePDBResult(filename="CompactHodgkin.pdb",
                     scoreType=ScoreType.CS,
                     similarityType=SimilarityType.HODGKIN)
    pr.savePDBResult(filename="CompactCarbo.pdb",
                     scoreType=ScoreType.CS,
                     similarityType=SimilarityType.CARBO)
