#!/usr/bin/env python2.3
################################################################################
#
#       This file is part of the GQL (Graphical Query Language) Toolkit
#
#       file:   GQL.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: 1211 $
#                       from $Date: 2006-10-16 21:53:45 +0200 (Mon, 16 Oct 2006) $
#             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]"
MISSING_DATA = -9999.99

#-------------------------------------------------------------------------------
from Tkinter import *
import tkFont
import webbrowser
from tkFileDialog import askopenfilename, asksaveasfilename
from tkMessageBox import askokcancel, showerror, askyesno
import tkSimpleDialog
from ScrolledText import *
import os
import string
import numpy as Numeric
from GQLQuery import GQLQuery
from GQLQueryEditor import *
from GQLUtil import extension, stripPath
from GQLGui import WMExtrasGeometry, ImageCache, AboutBox, SplashScreen, \
     ScrolledText, ResultsBox, ProfileCanvas, ClassesColorScheme
from ghmm import SequenceSet, Float
import GQLIcons
from GQL import *


class ProfileDisplay(Frame):

    def __init__(self, no_states, parent = None):
        self.canvas_width = 700
        self.canvas_height = 500
	self.no_states=no_states
        Frame.__init__(self, parent, relief=GROOVE, bd=2)
        self.makeWidgets()
        self.profileSet = None
        self.parent = parent
        #self.lastRank = int(self.likelihoodCutoff.get())


    def makeWidgets(self):
        label = Label(self, text="Query Result", anchor=W)
        label.pack(anchor=W, side=TOP, padx = 4)

        sunkenFrame = Frame(self, relief=SUNKEN, bd=2)
        self.canvas = ProfileCanvas(self, sunkenFrame, self.canvas_width,
                                    self.canvas_height)

        self.canvas.pack(anchor=W, side=TOP, expand=0, padx=4, pady=4)
        sunkenFrame.pack(anchor=W, side=TOP, expand=0, padx=4, pady=4)

        self.framePath = Frame(self,relief=RIDGE, bd=2,height=15,width =self.canvas_width)

	self.canvasPath = Canvas(self.framePath,height=15,width=self.canvas_width)
	self.canvasPath.pack(side=LEFT, fill=X, expand=NO)
	self.canvasPathItens = []

	#self.btn1 = Button(self.framePath, text="", anchor=W, default=DISABLED)
        #self.btn1.pack(anchor=W, side=BOTTOM, expand=1, fill=X)
        self.framePath.pack(anchor=W, side=TOP, expand=1, fill=X, padx = 4, pady=0)

	self.likelihoodCutoff = IntVar()
        self.likelihoodScale = Scale(self, command = self.updateLikelihood,
                                     from_ = 4000, to = 1, resolution = 1,
                                     orient = HORIZONTAL, relief = FLAT,
                                     showvalue = 1,
                                     #tickinterval = 0.1,
                                     variable = self.likelihoodCutoff,
                                     length = 300, label="Similarity Rank")
        self.likelihoodScale.pack(anchor=E, side=TOP, expand=0, padx=4, pady=4)

        frame = Frame(self,relief=RIDGE, bd=2)
        self.profileInfo = Label(frame, text="", anchor=W)
        self.profileInfo.pack(anchor=W, side=BOTTOM, expand=1, fill=X)
        frame.pack(anchor=W, side=BOTTOM, expand=1, fill=X, padx = 4, pady=4)


    def DisplayProfileSet(self, set):
        if self.profileSet is not None:
            self.canvas.Clear()
        self.profileSet = set
        self.likelihoodScale.config(from_=len(set))
        self.likelihoodScale.set(len(set))
        self.canvas.DisplayProfileSet(set)
        self.parent.queryEditor.configureRange(self.canvas.yrange[0],self.canvas.yrange[1])
	self.no_states = self.parent.query.queryLength


    def ClearInfo(self):
        self.profileInfo.config(text="")
	self.HidePathSlider()

    def ProfileInfo(self,the_text,profile):
        self.profileInfo.config(text=the_text)
	# check is the query was modified (the viterbi paths are recalculated in this case)
	self.parent.query.isModified()
	self.UpdatePathSlider(profile)

    def UpdatePathSlider(self,profile_id):
        path = self.parent.query.viterbiPaths[0][profile_id]
    	pathLen = len(path) - 1 # ignoring the last state and the space not avaliable at the graph
	print "viterbi", path
	statesFrequency = Numeric.zeros(self.no_states,Numeric.float)

	for state in path[:-1]: # the end state is not drawn
            statesFrequency[state] +=1.0


        statelenght = self.canvas_width/(pathLen-1.0)
	# the first state have a smaler size (only half the original size)
	actualAux = 0
	previousX = (statesFrequency[0]-0.5)*(statelenght)
        self.canvasPath.create_rectangle(0,0,previousX,15,
	                                 outline=ClassesColorScheme(0,self.no_states),fill=ClassesColorScheme(0,self.no_states))
        i = 1

	for state in statesFrequency[1:-1]:
	    actualX = previousX + (statesFrequency[i])*statelenght
	    self.canvasPath.create_rectangle(previousX,0,actualX,15,outline=ClassesColorScheme(i,self.no_states),
	                                     fill=ClassesColorScheme(i,self.no_states))
	    previousX = actualX
	    i +=1

        self.canvasPath.create_rectangle(previousX,0,700,15,
	                                 outline=ClassesColorScheme(self.no_states-1,self.no_states),
					 fill=ClassesColorScheme(self.no_states-1,self.no_states))



    def HidePathSlider(self):
    	self.canvasPath.delete(ALL)

    def Update(self):
        # Only called for new data set
        self.likelihoodScale.config(from_=len(self.profileSet))

    def update(self,showProfiles,hideProfiles):
        if self.no_states != self.parent.query.queryLength:
		self.no_states = self.parent.query.queryLength
	self.canvasPath.delete(ALL)
        self.canvas.ProfileUpdate(showProfiles,hideProfiles)
        l = self.parent.query.LowestRankedLikelihood()
        self.likelihoodScale.config(label="Similarity Rank   (log-likelihood exceeds %3.2f)" % l)

    def updateLikelihood(self, newRank):
        self.parent.query.setRankCutoff(int(newRank))
        l = self.parent.query.LowestRankedLikelihood()
        self.likelihoodScale.config(label="Similarity Rank   (log-likelihood exceeds %3.2f)" % l)


class GQLApp(Frame):

    def __init__(self, parent=None):
        Frame.__init__(self,parent)
        GQLIcons.Init()
        Splash = SplashScreen(self.master)

        #------- App init ---------------------------------------------
        self.pack(expand = 0)
        self.makeMenuBar()
        self.queryEditor = GQLQueryEditor(3,self)
        self.queryEditor.pack(anchor=N, side=LEFT, padx=4, pady=4, expand=0)

        self.profileDisplay = ProfileDisplay(3,self)
        self.profileDisplay.pack(anchor=N, side=RIGHT, padx=4, pady=4, expand=0)
        self.master.title("GQL")

        # Containers
        self.profileSet = ProfileSet() # We keep one ...
        self.ghmmSeqSet = None
        self.query = GQLQuery()
        self.queryEditor.overrideQuery(self.query)

        self.query.addListener(self.profileDisplay)

        #------- 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()

        # Make AlgoWins requested size its minimal size to keep
        # toolbar from vanishing when changing window size
        # Packer has been running due to splash screen
        wmExtras = WMExtrasGeometry(self.master)
        if os.name == 'nt' or os.name == 'dos':
            self.master.minsize(self.master.winfo_reqwidth(),
                                self.master.winfo_reqheight() +
                                wmExtras[1])
        else: # Unix & Mac
            self.master.minsize(self.master.winfo_reqwidth(),
                                self.master.winfo_reqheight() +
                                wmExtras[0] + wmExtras[1])


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

        # Add file menu
        self.fileMenu = Menu(self.menubar, tearoff=0)
        self.fileMenu.add_command(label='Open Data Set...',
                                  command=self.OpenDataSet)
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Open Query...',
                                  command=self.OpenQuery)
        self.fileMenu.add_command(label='New Query...',
                                  command=self.NewQuery)
        self.fileMenu.add_command(label='Save Query...',
                                  command=self.SaveQuery)
        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)


        # 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 GQL',
                                   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 GQL',
                                      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
    #
    def OpenDataSet(self):
        fileName = askopenfilename(title="Open Expression Data Set ",
                                   defaultextension=".txt",
                                   filetypes = [("Cage compatible", ".txt"),
                                                ("GHMM SQD File", ".sqd")]
                                   )
        if fileName != '':

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

            self.profileDisplay.DisplayProfileSet(self.profileSet)
            self.query.setData(self.profileSet)



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

        if fileName != '':
            self.query.openQuery(fileName,extension(fileName))
            self.queryEditor.fromQuery(self.query)
            self.query.runQuery()


    def NewQuery(self):
        """ Reset all values: Currently this does maintain the number of steps """
        queryLength = self.query.queryLength
        # XXX Get default values elsewhere (config file?)
        mean = [0.0] * queryLength
        variance = [0.5] * queryLength
        duration = [1.1] * queryLength
        self.query.newQuery(queryLength, mean, variance, duration, 0)
        self.queryEditor.fromQuery(self.query)

    def SaveQuery(self):
        fileName = asksaveasfilename(title="Save Query",
                                     defaultextension=".xml",
                                     filetypes = [("GHMM XML File",".xml")]
                                     )
        if fileName != '':
            self.query.saveQuery(fileName)
        

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

        if fileName != '': 
            """ Produce an EPSF of canvas in fileName. Note: Graph gets scaled
            and rotated as to maximize size while still fitting on paper """ 
            bb = self.profileDisplay.canvas.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
                self.profileDisplay.canvas.postscript(file=fileName,
                                                      pageheight="%dm" % printablePageHeight,
                                                      x=x,y=y,height=height,width=width)
            else:
                self.profileDisplay.canvas.postscript(file=fileName,
                                                      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.CleanUp()

    def AboutBox(self):
        d = AboutBox(self)

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





