#!n/usr/bin/env python2.3
################################################################################
#
#       This file is part of the GQL (Graphical Query Language) Toolkit
#
#       file:   GQLCluster.py
#       author: Alexander Schliep (alexander@schliep.org)
#
#       Copyright (C) 2003-2004 Alexander Schliep
#       Contact: alexander@schliep.org
#
#       Information: http://ghmm.org/gql
#
#	GQL is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	GQL is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with GQL; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
#
#       This file is version $Revision: 1543 $
#                       from $Date: 2007-03-26 15:49:21 +0200 (Mon, 26 Mar 2007) $
#             last change by $Author: filho $.
#
################################################################################
#
#
#
#----- Globals -----------------------------------------------------------------
gGQLVersion = 0.6
gGQLBuilddate = "1/15/2003"
gNCBIURL = "http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=search&db=nucleotide&term=%s[accn]"


#----- Imports -----------------------------------------------------------------
from Tkinter import *
import tkFont
from tkFileDialog import askopenfilename, asksaveasfilename
from tkMessageBox import askokcancel, showerror, askyesno, showwarning
import tkSimpleDialog
import os
import string
from GQL import ProfileSet,minWithoutMissing,MISSING_DATA
from GQLUtil import extension, stripPath
from GQLGui import WMExtrasGeometry, ImageCache, AboutBox, SplashScreen, \
     ResultsBox, ProfileCanvas, ScrolledText
import ghmm
from GQLMixture import estimate_mixture, decode_mixture, estimate_mixture_partials, \
     estimate_clustering, Entropy, estimate_mixture_ext
from ViterbiDecomposition import ViterbiDecomposition
from EditObjectAttributesDialog import ValidatingInt, ValidatingFloat, ValidatingString, \
     EditObjectAttributesDialog, TkFloatEntry, PopupableInt, Popupable, TkIntEntry, \
     TkStringPopupSelector
from GQLGOStatInterface import goStat
from GO.GODag import GODag
import GO.Comparirson
import GQLValidation
import math
import numpy.oldnumeric as Numeric
from tkSimpleDialog import askinteger
import random
from ProgressBarView import *
import webbrowser
from GQLCluster import *




minus_infinity = -float('Inf')


class SequencePanel(Frame):

    def __init__(self, parent, profileClustering, cluster_id, master, pc_width=700, pc_height=300):

	Frame.__init__(self,master,relief=RIDGE,borderwidth=2,width=pc_width+20)



        self.parent = parent
        self.profileClustering = profileClustering
        self.cluster_id = cluster_id
        cluster_aux = self.profileClustering[cluster_id]
        self.cluster = ProfileSubSet(parent.profileSet,cluster_aux)
        if self.profileClustering.type == 'HMM':
          self.model = self.profileClustering.modelList()[cluster_id]
        else:
          self.model = None #XXX - for now, I do no display of model parameters

        s = "Cluster Nr. %d of size %d (prior=%1.3f)" % (cluster_id + 1,
                                                         len(cluster_aux),
                                                         self.profileClustering.alpha[cluster_id])

        Label(self, text=s).pack(side=TOP, anchor=W, padx=10)
        self.profileCanvas = ProfileCanvas(self, self, pc_width, pc_height)
        self.profileCanvas.DisplayProfileSet(self.cluster)
        self.profileCanvas.pack(side=TOP, padx=10, pady=10)

	# a variable to keep the viterbi paths ...
	self.viterbiPaths = []
        self.subGroups = []


	self.popup = Menu(master,tearoff=0)        ## Popup-Menue
	self.popup.add_command(label='Gene List', command=self.ShowDetails)
	self.popup.add_separator()
        self.refinement = Menu(self.popup)
        self.popup.add_cascade(label="Cluster refinement",menu=self.refinement)
        self.popup.add_command(label="GO Enrichment",command=self.goEnrichment)
        self.refinement.add_command(label='Viterbi Decomposition', command=self.bestDecomposition)
        self.refinement.add_command(label='Kmeans Viterbi Decomposition', command=self.ViterbiSimple)
        self.viterbiCascade = Menu(self.refinement)  
        self.refinement.add_cascade(label="Viterbi Decomposition per States",menu=self.viterbiCascade)
	self.refinement.add_separator()                                  
	self.refinement.add_command(label='Show Viterbi Subgroups',command=self.ShowViterbi)
        if self.model == 'HMM':
            self.statesSelected = []
  	    for i in range(self.model.N-1):
		self.statesSelected.append(BooleanVar(self.popup))
		self.viterbiCascade.add_checkbutton(label='State %i'%(i+1), variable=self.statesSelected[i])
	self.viterbiCascade.add_separator()                                  
	self.viterbiCascade.add_command(label='Apply Viterbi',command=self.CallViterbi)
        
        #self.refinement.add_command(label='Viterbi Decomposition', command=self.bestDecomposition)
	## Maustaste an Event-Methode binden
	self.profileCanvas.bind('<Button-3>',self.popupMenu)

        if self.model == 'HMM':
	  Label(self, text=modelAsString(self.model)).pack(side=TOP, anchor=W, padx=10)
        #b = Button(self, text="Show Details", width=11, command=self.ShowDetails)
        #b.pack(side=BOTTOM, anchor=E, padx=10)


    def bestDecomposition(self):

         vitDecomp = ViterbiDecomposition(self.cluster.getSequenceSet(), self.model)
         class ViterbiArgs:
             def __init__(self):
                 self.desc = "Number of subgroups"
                 self.NoOfSubgroups = ValidatingInt(0)


         #a = ViterbiArgs()
         #pars = ['NoOfSubgroups']
         #d = EditObjectAttributesDialog(self, a, pars)

         #if not a.NoOfSubgroups:
         #    grouping = vitDecomp.computeBestDecomposition()
         #else:
         #    grouping = vitDecomp.computeFixedNumberDecomposition(a.NoOfSubgroups)

	 grouping = vitDecomp.computeBestDecomposition()
         i = 0

         self.subGroups = grouping

         for group in grouping:
             for element in group:
                 self.cluster.setclass(element,i)
             i += 1
         self.cluster.classes_no = i
         self.cluster.max_class = i
         # update the screen ...
         self.profileCanvas.ProfileUpdate(range(len(self.cluster)),[])

    def ViterbiSimple(self):

         vitDecomp = ViterbiDecomposition(self.cluster.getSequenceSet(), self.model)
         class ViterbiArgs:
             def __init__(self):
                 self.desc = "Number of subgroups"
                 self.NoOfSubgroups = ValidatingInt(0)


         a = ViterbiArgs()
         pars = ['NoOfSubgroups']
         d = EditObjectAttributesDialog(self, a, pars)

         #if not a.NoOfSubgroups:
         #    grouping = vitDecomp.computeBestDecomposition()
         #else:
         #    grouping = vitDecomp.computeFixedNumberDecomposition(a.NoOfSubgroups)

	 [groups,centroids] = vitDecomp.ViterbiSimple(a.NoOfSubgroups)
         self.subGroups = groups

         
         for i,g in enumerate(groups):
                 self.cluster.setclass(i,g)
         self.cluster.classes_no = max(groups)
         self.cluster.max_class = max(groups)
         # update the screen ...
         self.profileCanvas.ProfileUpdate(range(len(self.cluster)),[])


    def goEnrichment(self):
        db = {0:"sgd", 1:"goa_human",2:"goa_mouse"}
        class go_enrich_args:
             def __init__(self):
                 self.desc = "GO Enrichment"
                 self.database = PopupableInt()
                 self.database.setPopup({0:"sgd", 1:"goa_human", 2:"goa_mouse"},{"sgd":0, "goa_human":1, "goa_mouse":2},14)
                 self.max_number_terms = ValidatingInt(30)
                 self.max_pvalue = ValidatingFloat(0.01)
        a = go_enrich_args()
        pars = ['database', 'max_number_terms','max_pvalue']
        d = EditObjectAttributesDialog(self.master, a, pars)

        list = ""

        for i in self.cluster.ids:
            list += self.profileClustering.profileSet.acc[i]+" "
        
        goStat(list, database=db[a.database], pvalue= int(a.max_pvalue*100),numberOfNodes=a.max_number_terms)


    def bestDecompositionOld(self):

        vitDecomp = ViterbiDecomposition(self.cluster.getSequenceSet(), self.model)
        grouping = vitDecomp.computeBestDecomposition()

        i = 0
        for group in grouping:
            for element in group:
                self.cluster.setclass(element,i)
            i += 1

        self.cluster.classes_no = max(self.cluster.classes_no,i)
        self.cluster.max_class = max(self.cluster.max_class,i)

        # update the screen ...
        self.profileCanvas.ProfileUpdate(range(self.cluster.len()),[])

    def CallViterbi(self):
    	if (self.viterbiPaths == []):
            self.viterbiPaths = self.model.viterbi(self.cluster.getSequenceSet())[0]
        states = []
	for i in range(self.model.N-1):
		if( self.statesSelected[i].get()):
			states.append(i)

        if (states != []):
		grouping = self.profileClustering.pathGrouping(self.viterbiPaths, states)
		i = 0

                self.subGroups = grouping
                
		for group in grouping:
			for element in group:
				self.cluster.setclass(element,i)
			i += 1
		self.cluster.classes_no = max(self.cluster.classes_no,i)
		self.cluster.max_class = max(self.cluster.max_class,i)
		# update the screen ...
		self.profileCanvas.ProfileUpdate(range(self.cluster.len()),[])

## Eventhandler, liefert Maus-Koordinaten
    def popupMenu(self,event):
    	self.popup.post(event.x_root, event.y_root)

    def ShowDetails(self):
        """Callback routine: bring a up a dialog giving more infos"""
        (ids, ps) = self.cluster.SortByLikelihood(self.profileClustering, self.cluster_id)
        text = ""
        line2acc = []
        for i, p, j in zip(ids, ps, range(len(ids))):
	       path = ""
	       if (self.viterbiPaths != []):

	           path = " , the viterbi path: "
                   for state in self.viterbiPaths[j]:
                       path = path+"%i"%state
               text += "id=%s p=%1.2f\t" % (i,p) + self.profileClustering.profileSet.Info(i) + path + "\n"
               line2acc.append(self.profileClustering.profileSet.acc[i])
        d = ResultsBox(self,text,line2acc, "GQLCluster: Details for cluster %s" % self.cluster_id)

    def ShowViterbi(self):
        """Callback routine: bring a up a dialog giving more infos"""

        text = ""
        line2acc = []
   
        for (i,g) in enumerate(self.subGroups):
            for e in g:
	       path = ""
               text += "%s\t%d\n" % (self.profileClustering.profileSet.acc[e],i+1)
               line2acc.append(self.profileClustering.profileSet.acc[e])
               
        d = ResultsBox(self,text,line2acc, "GQLCluster: Viterbi groups for cluster %s" % self.cluster_id)


    def ProfileInfo(self, text, i):
        path = ""
        if self.viterbiPaths != []:
            for state in self.viterbiPaths[i]:
                path = path+"%i"%state 
        print text,i,path

    def ClearInfo(self):
        pass


    def update(self,showProfiles,hideProfiles):
        ''' this method translate the profilesSet id to the
	profileSubset id '''
	newShow = []
	newHide = []

    	for i,id in enumerate(self.profileClustering[self.cluster_id]):
		if id in showProfiles:
			newShow.append(i)
		else:
			newHide.append(i)
	self.profileCanvas.ProfileUpdate(newShow,newHide)


class ClusteringSumary(Frame):
# frame to show the statistics of the clustering results

    def __init__(self, parent, profileClustering, master, go=None,level=1):
        Frame.__init__(self,master,relief=RIDGE,borderwidth=2,width=200,height=100)
        self.parent = parent
        self.profileClustering = profileClustering
        self.sys = tkFont.Font( family="FixedSys", size="12")
	self.calculateIndices(self.profileClustering.profileSet.seq_classes,
                              self.profileClustering.cluster, geneOnt=go,l=level)
        self.go = go
        self.level = level


    def calculateIndices(self,classes,clusters,geneOnt=None,l=1,filter=[]):
        if geneOnt == None:
	    #(tp,fn,fp,tn) = GQLValidation.computeErrors(classes, clusters)
	    (contigencyTable,cRand,spec,sens,r, tp, fn, fp, tn, error, pp, np)= GQLValidation.computeExternalIndices(classes,clusters,
                                                                       self.profileClustering.profileSet.classes_no,
	                                                               len(self.profileClustering.clusters),withTP=True)
            s = "Cluster Statistics from Labels\n\n"
        else:
            tp = 0
            fn = 0
            fp = 0
            tn = 0
            # this is terribly not efective ... change this filter code later...
            if filter == []:
                filter = self.profileClustering.profileSet.genename
            # i still need to do a filter to show only the genes after the entropy
            (cRand,sens,spec,contigencyTable,tp,fn,fp,tn) = GO.Comparirson.clusterEvaluationMixtureSpecific(
                                                                 self.profileClustering.P,
                                                                 self.profileClustering.profileSet.genename,
                                                                 geneOnt,l,[0,1,2],contigency=1,genesFilter=filter)
            s = "Cluster Statistics from GO at level %i\n\n"%l


	s = "Cluster Statistics\n\n"
        
        self.title = Label(self, font=self.sys, text=s)
	self.title.pack(side=TOP, anchor=W, padx=10)

	s = "      Cor. Rand: %1.3f         TP: %8d         FN: %8d \n    Sensitivity: %1.3f         FP: %8d         TN: %8d             \n    Specificity: %1.3f\n Error: %1.3f PP: %1.3f  NP: %1.3f" %(cRand,tp,fn,sens,fp,tn,spec,error,pp,np)
        self.indices = Label(self, font=self.sys, text=s, justify=LEFT)
	self.indices.pack(side=TOP, anchor=W, padx=10)

        print cRand,tp,fn,sens,fp,tn,spec,error,pp,np

	self.contigencyTable = []

	saux = "------"
	s= 'Contigency Table (Classes X Clusters):\n     |'
	for j in range(len(contigencyTable[0])):
		s += "%4d" % (j+1)
		saux+="----"
	s += " \n"+saux
	self.contigencyTableHeader = Label(self, text=s, font=self.sys, justify=LEFT)
	self.contigencyTableHeader.pack(side=TOP, anchor=W, padx=10)
	for i in range(len(contigencyTable)):
            #print (contigencyTable[i]),sum(contigencyTable[i])
            if sum(contigencyTable[i]) > 0.0 :
		s = "%4d |" % (i+1)
		for j in range(len(contigencyTable[i])):
			s += "%4d" % contigencyTable[i,j]
                aux = Label(self, text=s, font=self.sys, justify=LEFT)
                aux.pack(side=TOP, anchor=W, padx=10)
		self.contigencyTable.append(aux)

	# extra info ... to be made better looking (tp,fn,fp,tn) =
	# GQLValidation.computeProbabilisticErrors(
	# GQLValidation.mixtureFromPartition(classes,self.profileClustering.profileSet.classes_no),
	# self.profileClustering.P, type=3) s = " Mix. Cor. Rand:
	# %1.3f Likehood: %1.3f \n"
	# %(GQLValidation.correctedRand(tp,fn,fp,tn),self.profileClustering.ml)
	# self.indices = Label(self, font=self.sys, text=s,
	# justify=LEFT) self.indices.pack(side=TOP, anchor=W, padx=10)


    def update(self,showProfiles,hideProfiles):
    	newClasses = []
	newClusters = []
        accs = []
	for prof in showProfiles:
		newClasses.append(self.profileClustering.profileSet.seq_classes[prof])
		newClusters.append(self.profileClustering.cluster[prof])
                accs.append(self.profileClustering.profileSet.genename[prof])
	self.ClearInfo()
	self.calculateIndices(newClasses,newClusters,geneOnt=self.go,l=self.level,filter=accs)

    def ClearInfo(self):
    	self.title.pack_forget()
    	self.indices.pack_forget()
	self.contigencyTableHeader.pack_forget()
	for item in self.contigencyTable:
    	  item.pack_forget()
    	del self.title
    	del self.indices
	del self.contigencyTableHeader
    	del self.contigencyTable



class ModelPanel(Frame):

    def __init__(self, modelEditor, model_id, model_info, master):
        Frame.__init__(self,master,relief=RIDGE,borderwidth=2,width=600)

        self.modelEditor = modelEditor
        self.profileClustering = self.modelEditor.profileClustering
        self.model = self.profileClustering.model(model_id)
        self.model_id = model_id

        gridframe = Frame(self)
        Label(gridframe, text=model_info).grid(row=0, sticky=W)

        self.mu = [] # Widgets
        self.sigma = []
        self.duration = []

        Label(gridframe, text="mean +/- var:").grid(row=1,column=0,sticky=E,padx=5)
        Label(gridframe, text="duration:").grid(row=2,column=0,sticky=E,padx=5)

        entry_width = 7
        for i in range(self.model.N - 1): #XXX IGNORING End-marker state
            Label(gridframe, text="%d" % i).grid(row=0,column=1+2*i)
            # Mean

            if isinstance(self.model,ghmm.ContinuousMixtureHMM):
                (t, m, s, w) = self.model.getEmission(i,0)
            elif isinstance(self.model,ghmm.GaussianMixtureHMM):
                (m, s, w) = self.model.getEmission(i,0)
          
            else:
                (m, s) = self.model.getEmission(i)
            w = TkFloatEntry(gridframe, entry_width)
            w.tkWidget().grid(row=1, column=1+2*i)
            w.set(m)
            self.mu.append(w)
            # Variance
            w = TkFloatEntry(gridframe, entry_width)
            w.tkWidget().grid(row=1, column=1 + 2*i + 1)
            w.set(s)
            self.sigma.append(w)
            # Duration
            a = min(0.9999, self.model.getTransition(i, i))
            duration = 1.0 / (1.0 - a)
            w = TkFloatEntry(gridframe, entry_width)
            w.tkWidget().grid(row=2, column=1+2*i)
            w.set(duration)
            self.duration.append(w)

        gridframe.pack(side=TOP,fill=BOTH)

        # FIX model
        box = Frame(self)
        c = Checkbutton(box, text="Fix model", width=10, command=self.Fix)
        c.pack(side=LEFT, padx=5, pady=5)
        self.fix = 0

        lb = Label(box, text="Alpha (prior):")
        lb.pack(side=LEFT, padx=5, pady=5)

	self.alpha = DoubleVar()
	self.alpha.set(self.profileClustering.alpha[model_id])
        self.aentry = Entry(box,width=entry_width,textvariable=self.alpha,state=DISABLED)
        self.aentry.pack(side=LEFT, padx=5, pady=5)

        if self.profileClustering.modelIsFixed(self.model_id):
            c.invoke() # Sets self.fix to 1

##        # XXX: The matrix-based file-format is fucked. Non-cyclic models
##        # will have no transition from last to first whose prob we could change.
##        # Leave out until we switch to XML
##        # Cyclic model
##        c = Checkbutton(box, text="Cyclic", width=10, command=self.Cyclic)
##        c.pack(side=LEFT, padx=5, pady=5)
##        self.cyclic = 0
##        if self.profileClustering.modelIsFixed(self.model_id):
##            c.invoke() # Sets self.fix to 1

        b = Button(box, text="Delete", width=10, command=self.Delete)
        b.pack(side=RIGHT, padx=5, pady=5)
        box.pack(side=BOTTOM,fill=BOTH)

    def Delete(self):
        self.modelEditor.Delete(self.model_id)


    def DisableEntries(self):
        self.aentry.configure(state=NORMAL)
        for  i in range(self.model.N - 1):
            self.mu[i].tkWidget().configure(state=DISABLED)
            self.sigma[i].tkWidget().configure(state=DISABLED)
            self.duration[i].tkWidget().configure(state=DISABLED)

    def EnableEntries(self):
        self.aentry.configure(state=DISABLED)
        for  i in range(self.model.N - 1):
            self.mu[i].tkWidget().configure(state=NORMAL)
            self.sigma[i].tkWidget().configure(state=NORMAL)
            self.duration[i].tkWidget().configure(state=NORMAL)

    def Fix(self):
        if self.fix == 0: # Unfixed
            self.DisableEntries()
            self.fix = 1
        else:
            self.EnableEntries()
            self.fix = 0

    def commitChanges(self):
        for i in range(self.model.N - 1):
            print "# ModelPanel:commitChanges", (i,self.mu[i].get(), self.sigma[i].get()), \
                  self.duration[i].get()
            if isinstance(self.model,ghmm.ContinuousMixtureHMM):
                # XXX - by now, we only allow normal distributions ...
                self.model.setEmission(i,0, 0,(self.mu[i].get(),
                                            self.sigma[i].get(),
                                            1- self.profileClustering.missingRate,
                                            0.0))
                self.model.setEmission(i,1, 0, (-9999.99,0.01,
                                            self.profileClustering.missingRate,
                                            0.0))           
            elif isinstance(self.model,ghmm.GaussianMixtureHMM):
                self.model.setEmission(i,0,(self.mu[i].get(), self.sigma[i].get(), 1- self.profileClustering.missingRate))
                self.model.setEmission(i,1,(-9999.99,0.01,self.profileClustering.missingRate))
            else:
                self.model.setEmission(i,(self.mu[i].get(), self.sigma[i].get()))
            # We want to avoid a == 0, since the stupid file format eats our emission otherwise
            d = max(1.0001, float(self.duration[i].get())) # XXX Should be fixed in the Panel
            a = 1.0 - 1.0/d
            self.model.setTransition(i, i, a)
            self.model.setTransition(i, i+1, 1.0 - a)
        if self.fix:
            self.profileClustering.fixModel(self.model_id)
	    self.profileClustering.alpha[self.model_id]=self.alpha.get()
        else:
            self.profileClustering.unfixModel(self.model_id)


class NewModelDialog(tkSimpleDialog.Dialog):
    def __init__(self, master):
        self.nr_states = None
        self.randomize_pars = None
	tkSimpleDialog.Dialog.__init__(self, master, "New Model")

    def body(self, master):
	self.resizable(0,0)
        Label(master, text="Nr. of states").grid(row=0, column=0, sticky=E)
        self.n_widget = TkIntEntry(master, 3)
	self.n_widget.tkWidget().grid(row=0, column=1, sticky=W)
        self.n_widget.set(3)

        Label(master, text="Initial Parameters").grid(row=1, column=0, sticky=E)
        self.pars_widget = TkStringPopupSelector(master, ['Default values', 'Random'])
   	self.pars_widget.tkWidget().grid(row=1, column=1, sticky=W)

        Label(master, text="Nr. of timepoints").grid(row=2, column=0, sticky=E)
        self.timepoint_widget = TkIntEntry(master, 10)
   	self.timepoint_widget.tkWidget().grid(row=2, column=1, sticky=W)

    def validate(self):
        self.nr_states = self.n_widget.get()
        self.randomize_pars = self.pars_widget.get() == 'Random'
        self.nr_timepoints = self.timepoint_widget.get()
        return 1

class ModelsEditor(Toplevel):

    def __init__(self, profileClustering, master):
        Toplevel.__init__(self, master)
        self.master = master
        self.protocol('WM_DELETE_WINDOW',self.Cancel)
        color = self.config("bg")[4]
        borderFrame = Frame(self, relief=SUNKEN, bd=2) # Extra Frame
        self.text = ScrolledText(borderFrame, relief=FLAT,
                                 padx=3, pady=3,
                                 #background='white',
                                 background=color,
                                 #foreground="black",
                                 wrap='word',
                                 width=60, height=12,
                                 font="Times 10")
        self.text.pack(expand=1, fill=BOTH)
        #self.text.insert('0.0', text)
        borderFrame.pack(side=TOP,expand=1,fill=BOTH)
        box = Frame(self)
        b = Button(box, text="OK", width=10, command=self.OK, default=ACTIVE)
        b.pack(side=RIGHT, padx=5, pady=5)
        b = Button(box, text="Cancel", width=10, command=self.Cancel)
        b.pack(side=RIGHT, padx=5, pady=5)
        b = Button(box, text="Add model...", width=10, command=self.AddModel)
        b.pack(side=LEFT, padx=5, pady=5)
        #self.bind("<Return>", self.withdraw)
        box.pack(side=BOTTOM,fill=BOTH)

        self.profileClustering = profileClustering
        self.mps = {}
        for id in self.profileClustering.modelIDs():
            mp = ModelPanel(self, id, "Model %s" % id, self.text)
            self.text.window_create(END,window = mp)
            self.text.insert(END,"\n")
            self.mps[id] = mp
        self.text['state'] = DISABLED


    def Delete(self, id):
        self.profileClustering.deleteModel(id)
        self.text['state'] = NORMAL
        self.text.delete(self.mps[id])
        self.text['state'] = DISABLED
        del(self.mps[id])

    def OK(self):

        for id in self.mps.keys():
            self.mps[id].commitChanges()
	self.profileClustering.normalizeAlphas()
        #self.master.commitChanges()
        self.withdraw()

    def Cancel(self):
        self.withdraw()

    def AddModel(self):
        d = NewModelDialog(self)
        if d.nr_states == None:
            return
        N = d.nr_states
        randomize = d.randomize_pars
        #N = askinteger("New Model", "Number of states", parent=self)
        if N > 0:
            self.text['state'] = NORMAL
            id = self.profileClustering.addModel(N, d.nr_timepoints, randomize)
            self.profileClustering.setMixtureFromHMMs()
            mp = ModelPanel(self, id, "Model %s" % id, self.text)
            self.text.window_create(END,window = mp)
            self.text.insert(END,"\n")
            self.mps[id] = mp
            self.text['state'] = DISABLED
        else:
            print "# XXX AddModel: N > 0 required"


class GQLClusterApp(Frame):

    def __init__(self, master=None):

        self.master = master
        import GQLIcons
        GQLIcons.Init()
        
	#Frame.__init__(self,parent)

	Splash = SplashScreen(self.master)
        color = master.config("bg")[4]

        # Containers
        self.profileSet = ProfileSet()
        self.ghmmSeqSet = None
        self.profileClustering = ProfileClustering()
        self.profileCanvas = []
	self.sumary = None
        self.go = None


	#------- App init ---------------------------------------------
        #self.pack(expand=YES, fill=BOTH)
	for i in range(20):
            master.grid_rowconfigure(i,weight=1)
	master.grid_columnconfigure(0,weight=1)
        
        self.makeMenuBar()

        f = tkFont.Font(master, ("Courier", "10", tkFont.NORMAL))

        self.out = ScrolledText(self.master, relief=FLAT,
                                padx=3, pady=3,
                                background=color,
                                wrap=CHAR,
                                width=100, height=52,
                                font="Helvetica 9",
                                )
        
#        self.out.pack(expand=YES, fill=BOTH)
	self.out.grid(row=0,rowspan=19,sticky=NSEW,padx=1)
        self.out.delete('0.0', END)
        self.out.insert(END, "Welcome to GQLCluster")
        self.out.configure(state=DISABLED)

        self.master.title("GQLCluster")
        self.master.maxsize(width=self.master.winfo_screenwidth(),
                            height=self.master.winfo_screenheight())

	self.entropyCutoff = DoubleVar()
	self.entropyCutoff.set(1.0)
        self.entropyScale = Scale(master,from_ = 1.0, to = 0.0, resolution = 0.0025,
                                     orient = HORIZONTAL, relief = FLAT, showvalue = 1.0,
                                     variable = self.entropyCutoff, label="Entropy Cutoff", state='disabled',
                                     length = 600)

        self.entropyScale.bind('<ButtonRelease-1>',lambda e, x=self.entropyCutoff
                                                   : self.profileClustering.setEntropyCutoff(e,x.get()))


        #------- Tk vodoo ---------------------------------------------
        Splash.Destroy()

        # Fix focus and stacking
        #if os.name == 'nt' or os.name == 'dos':
        self.master.tkraise()
        self.master.focus_force()
        #else:
        #    self.tkraise()


    ############################################################
    #
    # Create GUI
    #
    def makeMenuBar(self):
        """ *Internal* Now using Tk 8.0 style menues """
        self.menubar = Menu(self.master, tearoff=0)

        # ---------- File menu
        self.fileMenu = Menu(self.menubar, tearoff=0)
        self.fileMenu.add_command(label='Open Data Set...',
                                  command=self.OpenDataSet)
        self.fileMenu.add_command(label='Open Partial Assignment...',
                                  command=self.OpenPartialAssignment)
        self.fileMenu.add_command(label='Open Positive Constraints...',
                                  command=lambda s=self:s.OpenConstraints('Positive'))
        self.fileMenu.add_command(label='Open Negative Constraints...',
                                  command=lambda s=self:s.OpenConstraints('Negative'))
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Save Clustered Dataset...',
                                  command=self.SaveClusteredDataset)
        self.fileMenu.add_command(label='Save Clustered Dataset with Entropy Cutoff...',
                                  command=self.SaveClusteredDatasetWithCutoff)
        self.fileMenu.add_command(label='Save Cluster Assignment...',
                                  command=self.SaveClusterAssignment)   
        self.fileMenu.add_command(label='Save Cluster Assignment with Posteriors...',
                                  command=self.SaveClusterAssignmentWithPosteriors)   
        self.fileMenu.add_command(label='Save Mixture Distributions of Sequences...',
                                  command=self.SaveMixtureDistributions)
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Open Model Set...',
                                  command=self.OpenModelSet)
        self.fileMenu.add_command(label='Append Model Set...',
                                  command=self.AppendModelSet)
        self.fileMenu.add_command(label='Save Model Set...',
                                  command=self.SaveModelSet)
        self.fileMenu.add_separator()
        #self.fileMenu.add_command(label='Preferences...',
        #                          command=self.Preferences)
        #self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Export Result as EPS...',
                                  command=self.ExportEPSF)
        #self.fileMenu.add_command(label='Show Result...',
        #                          command=self.ShowResult)
        #self.fileMenu.add_command(label='Save Result...',
        #                          command=self.SaveResult)
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Quit',
                                  command=self.Quit)
        self.menubar.add_cascade(label="File", menu=self.fileMenu,
                                 underline=0)

        # ---------- Filters menu
        self.filtersMenu = Menu(self.menubar, tearoff=0)
   
        for filterName, filter in FilterFunctions.iteritems():
            callback = lambda s=self, f=filter: s.filterPanel(f)
        
            self.filtersMenu.add_command(label=filterName,
                                         command=callback)

        self.menubar.add_cascade(label="Filters", menu=self.filtersMenu,
                                 underline=0)

        # ---------- Models menu
        self.modelsMenu = Menu(self.menubar, tearoff=0)
        self.modelsMenu.add_command(label='Edit...',
                                    command=self.EditModels)
        self.modelsMenu.add_command(label='Estimate from Sequences',
                                    command = self.EstimateFromSequences)
        self.modelsMenu.add_command(label='Random HMM Models',
                                    command=self.RandomModels)
        self.modelsMenu.add_command(label='Random Gaussian Models',
                                    command=self.RandomGaussianModels)
#old functions substituted by benjamins library        
#        self.modelsMenu.add_separator()
#        self.modelsMenu.add_command(label='Estimate from Random Weights',
#                                    command=self.EstimateFromRandomWeights)
#        self.modelsMenu.add_command(label='Estimate from Random Assignment',
#                                    command=self.EstimateFromRandomAssignment)
        self.modelsMenu.add_separator()
        self.modelsMenu.add_command(label='Estimate from Random Weights',
                                    command=self.EstimateFromRandomWeightsExt)
        self.modelsMenu.add_command(label='Estimate from Random Assignment',
                                    command=self.EstimateFromRandomAssignmentExt)
        self.modelsMenu.add_separator()
        self.modelsMenu.add_command(label='Estimate from Partial Assignment',
                                    command=self.EstimateFromPartialAssignment)
        self.modelsMenu.add_command(label='View Partial Assignment...',
                                    command=self.PartialAssignment)
        self.modelsMenu.add_separator()
        self.modelsMenu.add_command(label='Estimate Number Of Clusters',
                                    command = self.EstimateNumberOfClusters)
        self.menubar.add_cascade(label="Models", menu=self.modelsMenu,
                                 underline=0)


        # ---------- Cluster menu
        self.clusterMenu = Menu(self.menubar, tearoff=0)
#old functions substituted by benjamins library         
#        self.clusterMenu.add_command(label='Estimate Mixture...',
#                                     command=lambda s=self:s.computeClustering('Mixture'))
        self.clusterMenu.add_command(label='Estimate Mixture...',
                                     command=lambda s=self:s.computeClustering('MixtureExt'))      
        self.clusterMenu.add_command(label='Estimate Mixture w/ Constraints...',
                                     command=lambda s=self:s.computeClustering('MixtureConstrained'))
        self.clusterMenu.add_command(label='Estimate Mixture w/ Partial Labels...',
                                     command=lambda s=self:s.computeClustering('PartiallySupervisedMixture'))
        self.clusterMenu.add_command(label='Estimate Clustering...',
                                     command=lambda s=self:s.computeClustering('Clustering'))
        self.clusterMenu.add_separator()       
        self.clusterMenu.add_command(label='K-means...',
                                     command=lambda s=self:s.computeClustering('Kmeans'))
        self.clusterMenu.add_command(label='Hierarchical...',
                                     command=lambda s=self:s.computeClustering('Hierarchical'))        
        self.clusterMenu.add_separator()
        self.clusterMenu.add_command(label='View Model Assignment',
                                     command=self.ModelAssignment)
        self.menubar.add_cascade(label="Cluster", menu=self.clusterMenu,
                                 underline=0)

        # ----------- Validation menu

        self.evaluationMenu =  Menu(self.menubar, tearoff=0)
        self.evaluationMenu.add_command(label='Open GO Ontology',
                                        command=self.OpenGO)
        self.evaluationMenu.add_command(label='Open GO Annotation',
                                        command=self.OpenGOAnnotation)
        self.evaluationMenu.add_separator()
        self.evaluationMenu.add_command(label='Evaluate GO',
                                     command=self.GOEvaluation)

        
        self.menubar.add_cascade(label="Evaluation", menu=self.evaluationMenu,
                                 underline=0)

        # On a Mac we put our about box under the Apple menu ...
        if os.name == 'mac':
            self.apple=Menu(self.menubar, tearoff=0, name='apple')
            self.apple.add_command(label='About GQLCluster',
                                   command=self.AboutBox)
            self.apple.add_command(label='Help',
                                   command=self.HelpBox)
            self.menubar.add_cascade(menu=self.apple)

        else: # ... on other systems we add a help menu
            self.helpMenu=Menu(self.menubar, tearoff=0, name='help')
            self.helpMenu.add_command(label='About GQLCluster',
                                      command=self.AboutBox)
            self.helpMenu.add_command(label='Help',
                                   command=self.HelpBox)
            self.menubar.add_cascade(label="Help", menu=self.helpMenu,
                                     underline=0)

        self.master.configure(menu=self.menubar)


    ############################################################
    #
    # Menu call backs
    #
    #====== File Menu ==========================================
    def OpenDataSet(self):
        fileName = askopenfilename(title="Open Expression Data Set ",
                                   defaultextension=".txt",
                                   filetypes = [("Cage compatible", ".txt"),
                                                ("GHMM SQD File", ".sqd")]
                                   )

        if fileName != '' and fileName!=():

	    self.RemoveClusteringView()
            if self.ghmmSeqSet is not None:
                del(self.ghmmSeqSet)
            if extension(fileName) == 'sqd': # READ from SQD
                self.ghmmSeqSet = ghmm.SequenceSet(ghmm.Float(),fileName)
                self.profileSet.ReadDataFromDSequences(self.ghmmSeqSet,fileName)
	        self.profileClustering.setProfileSet(self.profileSet)
            elif extension(fileName) == 'txt': # READ from CAGED
                self.ghmmSeqSet = self.profileSet.ReadDataFromCaged(fileName)
	        self.profileClustering.setProfileSet(self.profileSet)
            else:
                print "# GQL::OpenDataSet: invalid file type '%s'" % fileName

            
    def OpenPartialAssignment(self):
        fileName = askopenfilename(title="Open Partial Assignment",
                                   defaultextension=".lab",
                                   filetypes = [("Labels File", ".lab")]
                                   )
        if fileName != '' and fileName!=():
            if not self.profileClustering.readyToRun():
              showwarning("Message","You need to load models and a data set first")
              return 
            self.profileClustering.ReadPartialAssignment(fileName)


    def OpenConstraints(self,type):
        fileName = askopenfilename(title="Open Constraints",
                                   defaultextension=".const",
                                   filetypes = [("Constraints File", ".const")]
                                   )
        if fileName != '' and fileName!=():
            if type=='Negative':
              self.profileClustering.ReadConstraints(fileName,neg=1)
            else:
              self.profileClustering.ReadConstraints(fileName,neg=0)
        
    def SaveClusteredDataset(self):
        """Write out an SQD-File compatible with Oldstyle-Tools
           like Viterbi-decomp
        """
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:
            fileName = asksaveasfilename(title="Save Clustered Dataset",
                	                     defaultextension=".sqd",
	                                     filetypes = [("GHMM SQD File",".sqd"),("Cage compatible",".txt")]
        	                             )
	    if fileName != '' and fileName!=():
                # XXX SequenceSet.write does append to existing files, so we manually
	        # delete it
        	if os.path.exists(fileName):
                    os.remove(fileName)
            	if extension(fileName) == 'sqd': # READ from SQD
               	    for j, c in enumerate(self.profileClustering.clusters):
                	seqs = self.ghmmSeqSet.getSubset(c)
	                for i in range(len(seqs)):
                            seqs.setSeqLabel(i,j)
        	           #ghmmwrapper.set_sequence_d_label(seqs.cseq, i, j)
                	seqs.write(fileName)
	        elif extension(fileName) == 'txt': # READ from CAGED
        	    self.profileClustering.writeClusteredDataCAGED(fileName)
            	else:
                    print "# GQL::OpenDataSet: invalid file type '%s'" % fileName

    def SaveClusteredDatasetWithCutoff(self):
        """Write out an SQD-File compatible with Oldstyle-Tools
           like Viterbi-decomp
        """
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:
            fileName = asksaveasfilename(title="Save Clustered Dataset",
                	                     defaultextension=".txt",
	                                     filetypes = [("Cage compatible",".txt")]
        	                             )
	    if fileName != '' and fileName!=():
                # XXX SequenceSet.write does append to existing files, so we manually
	        # delete it
        	if os.path.exists(fileName):
                    os.remove(fileName)
                if extension(fileName) == 'txt': # READ from CAGED
        	    self.profileClustering.writeClusteredDataCAGED(fileName,entropy=1)
            	else:
                    print "# GQL::OpenDataSet: invalid file type '%s'" % fileName

    def SaveClusterAssignment(self):
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:
            fileName = asksaveasfilename(title="Save Cluster Assignment",
                                     defaultextension=".clu",
                                     filetypes = [("Cluster Assignment",".clu")]
                                     )
            if fileName != '' and fileName!=():
                self.profileClustering.writeClusterAssignment(fileName)

    def SaveClusterAssignmentWithPosteriors(self):
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:
            fileName = asksaveasfilename(title="Save Cluster Assignment with posteriors",
                                     defaultextension=".clup",
                                     filetypes = [("Cluster Assignment",".clup")]
                                     )
            if fileName != '' and fileName!=():
                self.profileClustering.writeClusterAssignment(fileName,allPosteriors=1)

    def SaveMixtureDistributions(self):
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:
            fileName = asksaveasfilename(title="Save Mixture Distributions of Sequences",
                                         defaultextension=".spd",
                                         filetypes = [("Sequence Distributions", ".spd")]
                                         )
            if fileName != '' and fileName!=():
                self.profileClustering.writeSeqMixDistributions(fileName)


    def OpenModelSet(self):
        fileName = askopenfilename(title="Open Model Set",
                                   defaultextension=".xml",
                 	                     filetypes = [("GHMM XML File",".xml"),
                                                         ("GHMM SModel File",".smo")]                                  
                                   )

	print fileName
        if fileName != '' and fileName!=():
            self.profileClustering.readModels(fileName,extension(fileName))

    def AppendModelSet(self):
        fileName = askopenfilename(title="Append Model Set",
                                   defaultextension=".smo",
                                   filetypes = [("GHMM SModel File",".smo")]
                                   )

	print fileName
        if fileName != '' and fileName!=():
            self.profileClustering.readModels(fileName,extension(fileName),append=1)


    def NewModelSet(self):
        """ Reset all values: Currently this does maintain the number of steps """
        pass

    def SaveModelSet(self):
        if not self.profileClustering.modelLoaded():
            showwarning("Message","You need to create/open a model first")
	else:
	        fileName = asksaveasfilename(title="Save Model Set",
        	                             defaultextension=".xml",
                	                     filetypes = [("GHMM XML File",".xml"),
                                                         ("GHMM SModel File",".smo")]
                                             
                        	             )
	        if fileName != '':
        	    self.profileClustering.writeModels(fileName,extension(fileName))


    def ExportEPSF(self,baseName=None):
        if not self.profileClustering.alreadyRunned():
            showwarning("Message","You need to run a clustering first")
	else:

    	    if baseName == None:
                fileName = asksaveasfilename(title="Export EPSF",
                                         defaultextension=".eps",
                                         filetypes = [  ("Encapsulated PS", ".eps") ]
                                         )
            else:
                fileName = baseName + ".eps"

            if fileName != '':
	        baseName = fileName[:-4]
                """ Produce an EPSF of each cluster in fileName. Note: Graph gets scaled
                and rotated as to maximize size while still fitting on paper """

	        counter = 0

	        for p in self.profileCanvas:

	            counter += 1

	            bb = p.profileCanvas.bbox("all") # Bounding box of all elements on canvas
        	    # Give 10 pixels room to breathe
            	    x = max(bb[0] - 10,0)
	            y = max(bb[1] - 10,0)
	            width=bb[2] - bb[0] + 10
	            height=bb[3] - bb[1] + 10

	            printablePageHeight=280 #mm
	            printablePageWidth =190 #mm

	            printableRatio=printablePageHeight/printablePageWidth

	            bbRatio = height/width

        	    if bbRatio > printableRatio: # Height gives limiting dimension
	                bb = p.profileCanvas.postscript(file=baseName+'%i.eps'%counter,
                                                      pageheight="%dm" % printablePageHeight,
                                                      x=x,y=y,height=height,width=width)
            	    else:
	                bb = p.profileCanvas.postscript(file=baseName+'%i.eps'%counter,
                                                      pagewidth="%dm" % printablePageWidth,
                                                      x=x,y=y,height=height,width=width)

    def ShowResult(self):
        text = self.profileSet.tabDelim(self.query)
        line2acc = []
        for i in self.query.Result():
            line2acc.append(self.profileSet.acc[i])
        d = ResultsBox(self,text,line2acc)


    def SaveResult(self,baseName=None):
        if baseName == None:
            fileName = asksaveasfilename(title="Save Result as plain text",
                                         defaultextension=".txt",
                                         filetypes = [("Text", ".txt")]
                                         )
        else:
            fileName = baseName + ".txt"
        if fileName != '':
            file = open(fileName, 'w')
            file.write(self.profileSet.tabDelim(self.query))
            file.close()

    def Preferences(self):
        pass

    def Quit(self):
        if askokcancel("Quit","Do you really want to quit?"):
            #Frame.quit(self)
            self.master.destroy()
            #self.CleanUp()
            
    #====== Filters Menu ========================================

    def filterPanel(self,filter):
        """ Panel for filter parameters
        """
        class filter_arg:
            def __init__(self):
                self.desc = "Filter"
                self.param =  ValidatingFloat(2)
        a = filter_arg()
        pars = ["param"]
        d = EditObjectAttributesDialog(self.master, a, pars)
        #print d.result
        if not d.result:
                return              
        self.profileClustering.filterProfiles(filter,a.param)

    #====== Models Menu ========================================
    def EditModels(self):
#        print "# XXX EditModels: need to pass a copy of profileClustering"
        d = ModelsEditor(self.profileClustering, self.master)

    def commitChanges(self):
        print "# XXX commitChanges: too late"

    def RandomModels(self):
        class random_models_args:
            def __init__(self):
                self.desc = "Parameters for random model collection"
                self.number_of_states = ValidatingString("range(2,10,2)")
                self.number_of_models = ValidatingInt(2)
                self.parameters = PopupableInt()
                self.parameters.setPopup({0:"Default values", 1:"Randomize"},
                                             {"Default values":0, "Randomize":1},
                                             14)
                self.default_mean = ValidatingFloat(0.0)
                self.default_variance = ValidatingFloat(1.0)
                self.noiseModel = PopupableInt()
                self.noiseModel.setPopup({0:"Without", 1:"With"},
                                             {"Without":0, "With":1},
                                             14)
                self.dimension = ValidatingInt(1)
        a = random_models_args()
        pars = ['number_of_states', 'number_of_models', 'parameters',
                'default_mean','default_variance','noiseModel','dimension']
        d = EditObjectAttributesDialog(self.master, a, pars)
        a.total_duration = len(self.profileSet[0])/(a.dimension)
        print 'tamanho', len(self.profileSet[0])
        if not d.result:
                return 
        self.profileClustering.randomModels(a)


    def RandomGaussianModels(self):
        #XXX - define here also other types of parametrization
        class random_models_args:
            def __init__(self):
                self.desc = "Parameters for random model collection"
                self.number_of_models = ValidatingInt(2)
                self.parameters = PopupableInt()
                self.parameters.setPopup({0:"Default values", 1:"Randomize"},
                                             {"Default values":0, "Randomize":1},
                                             14)
                self.default_mean = ValidatingFloat(0.0)
                self.default_variance = ValidatingFloat(1.0)
                self.noiseModel = PopupableInt()
                self.noiseModel.setPopup({0:"Without", 1:"With"},
                                             {"Without":0, "With":1},
                                             14)
                self.dimension=1

        a = random_models_args()
        pars = ['number_of_models', 'parameters',
                'default_mean','default_variance','noiseModel']
        d = EditObjectAttributesDialog(self.master, a, pars)
        if not d.result:
                return 
        self.profileClustering.randomGaussianModels(a)
        
    def EstimateFromSequences(self):
        estimatedNoOfStates = len(self.profileClustering.sequenceSet[0]) / 2 
        class EstimatedModelArgs:
            def __init__(self):
                self.desc = "Delayed response parameter"
                self.Synchronicity = 1.0
                self.Method = 3
                self.number_of_clusters = ValidatingInt(20)
                self.number_of_states = ValidatingInt(estimatedNoOfStates)
        a = EstimatedModelArgs()
        pars = ['number_of_states', 'number_of_clusters']
        d = EditObjectAttributesDialog(self.master, a, pars)
        if not d.result:
                return 
        self.profileClustering.estimateFromSequences(a)

    def EstimateNumberOfClusters(self):
        estimatedNoOfStates = len(self.profileClustering.sequenceSet[0]) / 2 
        class EstimatedNoClusters:
            def __init__(self):
                self.desc = "Number of Clusters"
                self.start_number = ValidatingInt(1)
                self.end_number = ValidatingInt(10)
                self.number_of_repetitions = ValidatingInt(10)
                self.number_of_states = ValidatingInt(estimatedNoOfStates)
                self.estimation = PopupableInt()
                self.estimation.setPopup({0:"Mixture", 1:"Clustering"},
                                             {"Mixture":0, "Clustering":1},
                                             14)
                self.noiseModel = PopupableInt()
                self.noiseModel.setPopup({0:"Without", 1:"With"},
                                             {"Without":0, "With":1},
                                             14)        
        a = EstimatedNoClusters()
        pars = ['start_number','end_number','number_of_repetitions','number_of_states','estimation','noiseModel']
        d = EditObjectAttributesDialog(self.master, a, pars)
        if not d.result:
                return
            
        # clean the canvas
	self.RemoveClusteringView()

        self.NumberOfClusters(a)

    def NumberOfClusters(self,a):
        class random_models_args:
            def __init__(self,nr_clusters,nr_states,duration,noise):
		self.number_of_states = "["+str(nr_states)+"]"
		self.number_of_models = nr_clusters
		self.default_mean =0.0
		self.default_variance = 1.0
		self.total_duration = duration
		self.parameters = 0
                self.noiseModel = noise
                self.dimension=1
        duration = len(self.profileSet[0])

        bics = []

        
        bar = self.ProgressBar()
        
        bar.updateProgress(0)
        rate = 100/(float(a.end_number+1-a.start_number)*a.number_of_repetitions)
         
        bestProfiles = []
                
        for i in range(a.start_number,a.end_number+1):
            model = random_models_args(i,a.number_of_states,duration,a.noiseModel)
            profiles = estimation_with_replications(self.profileSet,model,a.number_of_repetitions,
                                    'R',a.estimation,"",0,progressRate=rate,progressBar=bar)

            print "profiles", profiles
            
            auxBic = []

            for j,p in enumerate(profiles):
	    	auxBic.append(GQLValidation.computeBIC(p.ml,p,len(self.profileSet)))
            #print auxBic
            bics.append(sum(auxBic)/float(a.number_of_repetitions))
            max= Numeric.argmin(auxBic)

            #bar.updateProgress(100*(rate*i))
            bestProfiles.append(profiles[max])

        self.profileClustering = bestProfiles[Numeric.argmin(bics)]
        

        panel = NoOfClustersPanel(self,bics,range(a.start_number,a.end_number+1),self.out)
        self.out.window_create(END,window = panel)        
        self.pb.destroy()
        

    def EstimateFromRandomAssignment(self):
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        else:
            self.profileClustering.estimateFromRandomWeights(1)

    def EstimateFromRandomWeights(self):
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        else:
            self.profileClustering.estimateFromRandomWeights()

            
    def EstimateFromRandomAssignmentExt(self):
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        else:
            self.profileClustering.estimateFromRandomWeightsExternal(1)

    def EstimateFromRandomWeightsExt(self):
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        else:
            self.profileClustering.estimateFromRandomWeightsExternal()
            
    def EstimateFromPartialAssignment(self):
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        elif not self.profileClustering.partialLabelsLoaded():
            showwarning("Message","You need to load the partial labels first")
	else:
	    self.profileClustering.estimateFromPartialAssignment()

    def PartialAssignment(self):
        if not self.profileClustering.partialLabelsLoaded():
            showwarning("Message","You need to load the partial labels first")
            return
        line2acc = []
        result = ""
        for i, partialset in enumerate(self.profileClustering.partials):
            if partialset is not None:
                line2acc.append(None)
                result += "# Model %d has %d profiles assigned\n" % (i, len(partialset))
                for p in partialset:
                    result += "id=%s\t" % p + self.profileClustering.profileSet.Info(p) + \
                              "\t%s\n" % self.profileClustering.partial_info[p]
                    line2acc.append(self.profileClustering.profileSet.acc[p])

        d = ResultsBox(self.master,result,line2acc, "Partial Assignment to models")


    #====== Cluster Menu =======================================
    def computeClustering(self, method):
        if not self.profileClustering.readyToRun(method):
            showwarning("Message","You need to load models and a data set first")
	else:
            #self.master.config(cursor="watch")
            pars = self.profileClustering.clusteringParameters(method=method)
            pars_dialog = EditObjectAttributesDialog(self.master, self.profileClustering, pars)
            if not pars_dialog.result:
                return

            #bar = ProgressBarView(self.master, value=0, labelText="Carrying Estimation")
            #bar.pack()
            bar = self.ProgressBar()
            
	    self.out.configure(state=NORMAL)
            self.out.delete('0.0', END)
	    self.profileCanvas = []

            self.profileClustering.computeClusteringView(method,progressBar=bar)
 
            self.pb.destroy()
            self.ShowNewClustering()
            
            #self.master.config(cursor="")

    def ProgressBar(self):
        
       self.pb = Toplevel(self.master) 
       self.pb.title("Progress bar")
       # this could be improved! place in the middle?
       self.pb.geometry("%dx%d+%d+%d"%(120,40,500,400))
       label = Label(self.pb,text="Estimation Progress", anchor=NW, justify=LEFT, width=30)
       label.pack()
       #bar = ProgressBarView(self.pb, value=33, orientation="vertical", height=200)
       pbv = ProgressBarView(self.pb, value=0)
       pbv.pack()
       return pbv

    def PrintMessage(self,string):
        string = string
        self.out.insert(END,string)
        self.out.insert(END,"\n")
        self.out.update()
        self.out.update_idletasks()

    def ModelAssignment(self):
        """ this method only call the mixture extimation with 0 interations, to carry a cluster assignment"""
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to load models and a data set first")
        else:
	    self.out.configure(state=NORMAL)
            self.out.delete('0.0', END)
	    self.profileCanvas = []

            if self.profileClustering.type == 'HMM' or self.profileClustering.type == 'Gaussian':
  	      pars = self.profileClustering.clusteringParameters()
 	      self.profileClustering.eps = 0.1
	      self.profileClustering.max_iter = 0
	      self.profileClustering.use_partial_labels = 0
              self.profileClustering.computeClustering(method="MixtureExt")
              
            self.ShowNewClustering()

    #====== Evaluation Menu ==========================================
    
    def OpenGO(self):
        fileName = askopenfilename(title="Open GO Ontology Data Set ",
                                   defaultextension=".obo",
                                   filetypes = [("GO Ontology", ".obo")]
                                   )

        if fileName != '' and fileName!=():

            if self.go is not None:
                del(self.go)
                
            self.go = GODag()
            self.go.loadTermsFromFile(fileName)

    def OpenGOAnnotation(self):
        fileName = askopenfilename(title="Open GO Ontology Data Set ",
                                   defaultextension=".sgd",
                                   filetypes = [("GO Ontology Annotation", ".sgd")]
                                   )

        if fileName != '' and fileName!=():

            if self.go is None:
                showwarning("Message","You need to load the ontology first")              
            self.go.loadGenesFromFile(fileName)

    def GOEvaluation(self):
        """ Panel for filter parameters
        """
        if not self.profileClustering.readyToRun():
            showwarning("Message","You need to perfom a clustering first")
            return
        
        class go_val_arg:
            def __init__(self):
                self.desc = "GO Evaluation"
                self.level =  ValidatingInt(2)
        a = go_val_arg()
        pars = ["level"]
        d = EditObjectAttributesDialog(self.master, a, pars)

        if not d.result:
                return 
        
        if self.sumary is not None:
	    self.profileClustering.removeListener(self.sumary)

        self.out.delete('0.0', END)
        cs = ClusteringSumary(self,self.profileClustering,self.out,go=self.go,level=a.level)
        self.profileClustering.addListener(cs)
        self.sumary = self.out.window_create(END,window = cs)
        #redo sumary
        #self.profileClustering.filterProfiles(filter,a.level)

    #====== Help Menu ==========================================
    def AboutBox(self):
        d = AboutBox(self.master)

    def HelpBox(self):
        webbrowser.open("http://ghmm.org/gql")


    def RemoveClusteringView(self):
 	for pc in self.profileCanvas:
	    self.profileClustering.removeListener(pc)
        if self.sumary is not None:
	    self.profileClustering.removeListener(self.sumary)
        self.profileCanvas = []
        self.out.configure(state=NORMAL)
        self.out.delete('0.0', END)
        if self.entropyScale is not None:
            self.entropyScale.pack_forget()


    def ShowNewClustering(self):
        """ this method creates a sequence canvas to each cluster encountered in the data set
        """

        # removing old profile canvas
	self.RemoveClusteringView()
        #models = self.profileClustering.modelList()
        for i, cluster in enumerate(self.profileClustering):
            sp = SequencePanel(self, self.profileClustering,i, self.out,350,225)
            self.out.window_create(END,window = sp)
            self.profileCanvas.append(sp)
	    self.profileClustering.addListener(sp)
        self.out.configure(state=DISABLED)

#        self.entropyScale.pack(anchor=W, side=TOP, expand=0, padx=4, pady=4)
	self.entropyScale.grid(row=19, stick=EW, padx=4, pady=4)

	# +0.001 hack ... how to round the maxEntropy to a uper value?
	self.entropyScale.configure(from_=(self.profileClustering.maxEntropy+0.001),state=NORMAL)
	self.entropyCutoff.set(self.profileClustering.maxEntropy+0.001)

	# if there are classes in the input sequence, show results statistics ...
	if(self.profileClustering.profileSet.classes_no > 1):
		cs = ClusteringSumary(self,self.profileClustering,self.out)
   	        self.profileClustering.addListener(cs)
       		self.sumary = self.out.window_create(END,window = cs)


    def ProfileInfo(self, text):
        print text+"\n"

    def ClearInfo(self):
        pass


class NoOfClustersPanel(Frame):

    def __init__(self, parent, bic, clusters_range,  master, pc_width=700, pc_height=300):
        
        Frame.__init__(self,master,relief=RIDGE,borderwidth=2,width=pc_width+20)
    
        self.parent = parent
        self.bic = bic
        self.clusters_range = clusters_range
        
        s = "BIC vs. Number of CLusters %d to %d"%(clusters_range[0],clusters_range[-1])
        Label(self, text=s).pack(side=TOP, anchor=W, padx=10)
            
        # HACK ...I create a profile set with the bic results
        
        self.profile = ProfileSet()
        self.profile.addProfile(1,bic,"BIC",classe=0)
        
        self.profileCanvas = ProfileCanvas(self, self, pc_width, pc_height)
        self.profileCanvas.DisplayProfileSet(self.profile,centered=0)
        self.profileCanvas.pack(side=TOP, padx=10, pady=10)
        
        enubic = {}
        for c,i in enumerate(bic):  
            enubic[i] = c
        
        s = "\n Min. BIC: %f at %d clusters"%(min(bic),(enubic[min(bic)]+1))
        Label(self, text=s).pack(side=TOP, anchor=W, padx=10)
        
    def ProfileInfo(self, text, i):
        path = ""
        
        print text,self.profile[i]
 
    def ClearInfo(self):
        pass


def estimation_with_replications(profileSet, model,repetitions,
                                 initializationMethod,estimationMethod,
                                 inputmodel,perlabels,progressRate=0.0,progressBar=None):
        currentProfile = None
	profiles = []

	if(initializationMethod in ('L1','L2')):
	    labels = profileSet.seq_classes
    	    labelsClasses = []
            for i in range(max(labels)):
                labelsClasses.append([])
            for i,l in enumerate(labels):
                labelsClasses[l-1].append(i)

	for j in range(repetitions):
  	    try:
		alpha = []


		currentProfile = ProfileClustering()
		currentProfile.setProfileSet(profileSet)

		if(initializationMethod == 'R'):
                    states= eval(model.number_of_states)
                    if (len(states) == 1) and (states[0] == len(profileSet[0])):
                      currentProfile.randomGaussianModels(model)                         
                    else:                           
 		      currentProfile.randomModels(model)
		    currentProfile.estimateFromRandomWeights()
		elif(initializationMethod in ('L1','L2','L3')):
		     # estimate from kmeans results
		    currentProfile.randomModels(model)
		    if(initializationMethod == 'L1'):
		        set_random_partial_labels(currentProfile,perlabels,labelsClasses)
                    elif(initializationMethod == 'L2'):
		        set_random_partial_labels_equal(currentProfile,perlabels,labelsClasses)
                    elif(initializationMethod == 'L3'):
		    	currentProfile.ReadPartialAssignment(perlabels)
                    currentProfile.estimateFromPartialAssignment() # estimate from random assigments
		else:
		    currentProfile.readModels(inputmodel)

		if(initializationMethod in ('L1','L2','L3')):
  		    [currentProfile.ml,
		     currentProfile.alpha,
		     currentProfile.p] = estimate_mixture_partials(currentProfile.modelList(),
			    					        profileSet.ghmm_seqs, 10, 0.01, 					 			                        	currentProfile.fixed_models,
									currentProfile.partial_label)
	            #currentProfile.cluster = decode_mixture(currentProfile.p, num_clusters)
		elif(estimationMethod == 0):
                    

                    currentProfile.max_iter=20
                    currentProfile.eps=0.1
               
		    currentProfile.computeClustering('MixtureExt')
                    
  		    #[currentProfile.ml,
		    # currentProfile.alpha,
		    # currentProfile.p] =    estimate_mixture(currentProfile.modelList(),
      		    #	    					        profileSet.ghmm_seqs, 20, 0.01, 					 			                        	  currentProfile.fixed_models)
	            #currentProfile.cluster = decode_mixture(currentProfile.p, num_clusters)
		elif(estimationMethod == 1):
		    [currentProfile.ml,
		     currentProfile.cluster,
		      currentProfile.p] = estimate_clustering(currentProfile.modelList(), profileSet.ghmm_seqs,
			    				      10, 0.01, currentProfile.fixed_models)
		profiles.append(currentProfile)

                #if progressBar != None:
                print "progress", progressBar.value+progressRate
                progressBar.updateProgress(progressBar.value+progressRate)
            except OverflowError:
	        print "repetition failed %i"%j
	return (profiles)



#if __name__ == '__main__':
#    random.seed() # Timeseed Python RNG
#    root = Tk()
#    GQLClusterApp(root)
#    root.mainloop()
#    #app = GQLClusterApp()
#    #app.mainloop()


