#!/usr/bin/en python2.2
################################################################################
#
#       This file is part of the GQL (Graphical Query Language) Toolkit 
#
#       file:   GQLQueryEditor.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: 718 $
#                       from $Date: 2005-12-28 08:16:05 -0300 (Wed, 28 Dec 2005) $
#             last change by $Author: filho $.
#
################################################################################

from Tkinter import *
#from ProbEditorContinuous import gauss_function # Also imports PyGSL!
from EditObjectAttributesDialog import TkPopupSelector
from GQLQuery import GQLQuery
import pygsl.rng
import math
import colorsys

# plot_object, gauss_function were orginally written by Achim Gaedke.
# They are part of Gato (gato.sf.net), which is released under the
# LGPL. Gato is copyright Alexander Schliep and Winfried Hochstaettler
class plot_object:
    """
    object that can be plotted
    """

    def __init__(self,color=None):
        """
       init color
        """
        self.color=color

    def resolution(self,x_1,x_2):
        """
        needed resolution for this object in the given interval
        """
        return 10

    def sample_values(self,x_1,x_2):
        """
        values needed for a good plots
        """
        sample=[]
        res=self.resolution(x1,x2)
        if res<2:
            res=2
        step_diff=(x2-x1)/(res-1)
        for step in range(0,res):
            x=step*step_diff+x1
            sample.append(x)
        return sample

    def get_values(self, list_x):
        """
        calculate values from a list
        """
        list_y=[]
        for x in list_x:
            list_y.append((x,self.get_value(x)))
        return list_y

    def get_value(self,x):
        """
        get one value
        """
        return None

    def get_points(self,x1,x2):
        """
        calculates sufficient pairs for a spline approximation
        """
        return self.get_values(self.get_sample_values(x1,x2))

    def set_parameters(self,values):
        """
        sets parameters from a tuple
        """
        pass

    def get_parameters(self):
        """
        gets parameters as a tuple
        """
        return None

    def get_color(self):
        """
        get saved color
        """
        return self.color

    def set_color(self,color=None):
        """
        save color
        """
        self.color=color
        return color

    def __repr__(self):
        return "plot_object"


class gauss_function(plot_object):
    """
    gauss function
    """

    def __init__(self,mu=0,sigma=1,a=1,color=None):
        "init parameters"
        plot_object.__init__(self,color)
        self.mu=float(mu)
        self.sigma=float(sigma)
        self.a=float(a)
        self.norm=1.0/math.sqrt(2.0*math.pi)

    def get_sample_values(self,x1,x2):
        """
        make different resolutions
        """
        if x1>x2:
            x_max=x1
            x_min=x2
        else:
            x_max=x2
            x_min=x1

        # epiric values
        res_map={5: 3, 4: 3, 3: 3,2: 5,1: 5, 0: 7}
        res_map_start=5.0
        # renormed distances
        n_min=(x_min-self.mu)/self.sigma
        n_max=(x_max-self.mu)/self.sigma
        n_step=(n_max-n_min)/10.0

        n_list=[]
        n=n_min

        # boring range <5
        while n<n_max and n<-res_map_start:
            n_list.append(n)
            n+=n_step

        # interesting range
        n=max(-res_map_start,n_min)
        while n<n_max and n<res_map_start:
            n_list.append(n)
            n+=1.0/float(res_map[abs(int(n))])

        # boring range >5
        while n<n_max:
            n_list.append(n)
            n+=n_step

        n_list.append(n_max)

        # convert n_list to x_list
        i=0
        l=len(n_list)
        while i<l:
            n_list[i]=n_list[i]*self.sigma+self.mu
            i+=1

        return n_list

    def get_parameters(self):
        """
        returns (mu,sigma,a)
        """
        return (self.mu,self.sigma,self.a)

    def set_parameters(self,values):
        """
        sets parameters from (mu,sigma,a)
        """
        self.mu=float(values[0])
        self.sigma=float(values[1])
        self.a=float(values[2])

    def get_value(self,x):
        "gauss function"
        return self.a*pygsl.rng.gaussian_pdf(x-self.mu,self.sigma)
#        return self.a*random.gauss(x-self.mu,self.sigma)
##        n=(float(x)-self.mu)/self.sigma
##        return math.exp(n*n/-2.0)*self.a/self.sigma*self.norm

    def __repr__(self):
        return "gauss_function: x->%f/(%f*sqrt(2*pi))*exp(-(x-%f)**2/2*%f**2)"%(self.a,self.sigma,self.mu,self.sigma)






class GaussianState(Frame):
    def __init__(self, step, parent = None, queryEd = None, color="red"):
        #Frame.__init__(self, parent, relief=GROOVE, bd=2)
        Frame.__init__(self, parent)
        self.parent = parent
        self.queryEd = queryEd
        self.canvas_width = 60
        self.vslider_length = self.canvas_width + 30
        self.slider_width = 8
        self.canvas_height = 500
        self.ymin = -3
        self.ymax = 3

        self.step = step - 1 # GQLQuery numbers from 0
        self.makeWidgets()

        self.gaussian = gauss_function()
        self.updateGaussian()

        self.drawGrid( -3,3,0) # assuming -3 to 3 yrange for profiles
        self.plot_gaussian = self.canvas.create_polygon(0,0,10,10,20,0,smooth=1,fill=color)

    def colorUpdate(self,color):
	self.canvas.itemconfigure(self.plot_gaussian,fill=color)

    def makeWidgets(self):
        self.framelabel = Label(self, text="Step %d" % (self.step+1), anchor=W)
        self.framelabel.pack(anchor=W, side=TOP, padx = 4)

        frame = Frame(self)
        self.mean = DoubleVar()

        self.meanScale = Scale(frame, command = self.updateMean,
                               from_ = -3, to = 3, resolution = 0.02,
                               orient = VERTICAL, relief = GROOVE,
                               showvalue = 0,
                               #tickinterval = 0.1,
                               variable = self.mean,
                               length = self.canvas_height,
                               width= self.slider_width)
        self.meanScale.pack(anchor=E, side=LEFT, expand=0, padx=4, pady=4)

        sunkenFrame = Frame(frame, relief=SUNKEN, bd=2)
        self.canvas = Canvas(sunkenFrame, width=self.canvas_width, height=500,
                             background="white")

        # Call configureRange
        self.xscale = self.canvas_height / 6.0 # assuming -3 to 3
        self.xoffset = self.canvas_height / 2

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

        self.density = IntVar()
        self.densityGaussian = Radiobutton(self, text="Gaussian",
                                           variable=self.density,
                                           value=0,
                                           command=self.updateDensity)
        self.densityUniform = Radiobutton(self,
                                          text="Uniform",
                                          variable=self.density,
                                          value=4,
                                          command=self.updateDensity)
        self.density.set(0)

        self.densityUniform.pack(anchor=S, side=BOTTOM, expand=0, padx=4, pady=4)
        self.densityGaussian.pack(anchor=S, side=BOTTOM, expand=0, padx=4, pady=4)

        self.duration = DoubleVar()
        self.durationsScale = Scale(self, command = self.updateDuration,
                                    from_ = 1.1, to = 80, resolution = 0.1,
                                    #from_ = 1.0, to = 10, resolution = 0.1,
                                    orient = HORIZONTAL, relief = FLAT,
                                    showvalue = 0,
                                    label = "Duration",
                                    #tickinterval = 0.1,
                                    variable = self.duration,
                                    length = self.vslider_length,
                                    width=self.slider_width)
        self.durationsScale.pack(anchor=S, side=BOTTOM, expand=0, padx=4, pady=4)
        self.duration.set(1.1)


        self.variance = DoubleVar()
        self.varianceScale = Scale(self, command = self.updateVariance,
                                   from_ = 0.01, to = 3, resolution = 0.02,
                                   orient = HORIZONTAL, relief = FLAT,
                                   showvalue = 0,
                                   label = "Variance",
                                   #tickinterval = 0.1,
                                   variable = self.variance,
                                   length = self.vslider_length,
                                   width = self.slider_width)
        self.varianceScale.pack(anchor=S, side=BOTTOM, expand=0, padx=4, pady=4)
        self.variance.set(0.5)



    def drawGrid(self, ymin, ymax, delete=1):
        if delete:
            self.canvas.delete("grid")

        self.canvas.create_line(0,self.xoffset,
                                self.canvas_width,self.xoffset,
                                tag="grid",fill="grey",width=1.5)
        if self.canvas_height / self.xscale > 5:
            stepwidth = 2
            i = 2
        else:
            i = 1
            stepwidth = 1
        while self.xoffset + i * self.xscale < self.canvas_height:
            #if i % 2:
            #    width_ = 0.5
            #else:
            #    width_ = 1.5

            width_ = 1.0
            self.canvas.create_line(0,self.xoffset + i * self.xscale,
                                    self.canvas_width,self.xoffset + i * self.xscale,
                                    tag="grid",fill="grey",width=width_)
            self.canvas.create_line(0,self.xoffset - i * self.xscale,
                                    self.canvas_width,self.xoffset - i * self.xscale,
                                    tag="grid",fill="grey",width=width_)
            i += stepwidth
        self.canvas.lower('grid')



    def configureRange(self, ymin, ymax):
        """ NOTE: ymin, ymax wrt to profiles. GaussianState's x/y are swapped """
        yrange = ymax - ymin
        self.ymin = ymin
        self.ymax = ymax
        old_mean = float(self.meanScale.get())
        self.xscale = self.canvas_height / yrange
        self.meanScale.config(from_ = ymin, to = ymax)
        self.drawGrid(self, ymin, ymax)
        self.updateGaussian()
        self.plotGaussian()
        self.meanScale.set(old_mean)


    def updateGaussian(self):
        """ Read current parameter values and set into Gaussian function """
        mean = -self.mean.get() # Slider has wrong orientation in y coord
        variance = self.variance.get()
        self.gaussian.set_parameters((mean, variance, 1.0))


    def plotGaussian(self):
        """ Plot the Gaussian with its current set """
        sample_x = self.gaussian.get_sample_values(self.ymin,self.ymax)
        mean_y  = self.gaussian.get_value(-self.mean.get()) # wrong orientation in y coord
        yscale = (self.canvas_width - 5) / mean_y # Extra padding
        coords = ()
        for x in sample_x:
            coords += (yscale*self.gaussian.get_value(x),
                       -self.xscale * x + self.xoffset) # wrong orientation in y coord
        # Duplicate last coordinates to force straight line
        # Coordinates are the corners
        coords = (0,1000,0,1000) + coords + (0,-1000,0,1000) # XXX
        self.canvas.coords(self.plot_gaussian,coords)

    def Mean(self):
        return -self.mean.get() # Slider has wrong orientation in y coord

    def Variance(self):
        return self.variance.get()

    def Duration(self):
        return self.duration.get()

    def updateMean(self, value):
        self.updateGaussian()
        self.plotGaussian()
        self.queryEd.query.setMean(self.step,-float(value))

    def updateVariance(self, value):
        self.updateGaussian()
        self.plotGaussian()
        self.queryEd.query.setVariance(self.step,float(value))

    def updateDuration(self, value):
        self.queryEd.query.setDuration(self.step,float(value))

    def updateDensity(self):
        self.queryEd.query.setDensity(self.step,self.density.get())

    def Set(self,mean,variance,duration,density):
        self.mean.set(-mean) # Sliders have opposite orientation
        self.variance.set(variance)
        self.duration.set(duration)
        self.density.set(density)
        self.updateGaussian()
        self.plotGaussian()



class nullQuery:
    def setMean(self, step, value):
        pass
    def setVariance(self, step, value):
        pass
    def setDuration(self, step, value):
        pass

class GQLQueryEditor(Frame):

    def __init__(self, nrSteps, parent = None):
        #Frame.__init__(self, parent,relief=GROOVE, bd=2)
        Frame.__init__(self, parent)
        self.parent = parent
        self.pack(expand = 0)
        self.query = nullQuery()
        self.nrSteps = nrSteps


        self.stepFrame = Frame(self)
        self.states = []
        for step in xrange(nrSteps):
            self.appendGaussianState(step + 1)
        self.stepFrame.pack(side=TOP)

        spacer = Frame(self)
        spacer.pack(anchor = N, side = TOP, padx=0, pady = 10, expand=0, fill=X)

        generalFrame = Frame(self)

        b = Button(generalFrame,text="Append Step", command=self.appendStep)
        b.pack(side = LEFT)
        b = Button(generalFrame,text="Delete Last Step", command=self.deleteLastStep)
        b.pack(side = LEFT)

        self.cyclic = IntVar()
        self.cyclicWidget = Checkbutton(generalFrame,variable=self.cyclic,text="Cyclic",
                                        command=self.ToggleCyclic)
        self.cyclicWidget.pack(side=RIGHT)
        generalFrame.pack(anchor = S, side = BOTTOM, padx=0, pady = 2, expand=0, fill=X)


    def appendGaussianState(self,step):
        # internal
        s = GaussianState(step,self.stepFrame,self,color=ClassesColorScheme(step-1,self.nrSteps))
        s.pack(anchor=N, side=LEFT, padx=0, pady=0, expand=0)
        self.states.append(s)

    def overrideQuery(self,query):
        """ Set the values of the query from the editor (widget) values """
        self.query = query
        mean = []
        variance = []
        duration = []
        for s in self.states:
            mean.append(s.Mean())
            variance.append(s.Variance())
            duration.append(s.Duration())
        cyclic = self.cyclic.get()
        self.query.newQuery(self.nrSteps,mean,variance,duration,cyclic)

    def fromQuery(self,query):
        if query.queryLength >  len(self.states):
            for i in xrange(len(self.states), query.queryLength):
                self.nrSteps += 1        
                self.appendGaussianState(self.nrSteps)
        elif query.queryLength < len(self.states):
            for i in xrange(query.queryLength, len(self.states)):
                self.nrSteps -= 1
                self.states[-1].pack_forget()
                self.states = self.states[:-1]                
                self.stepFrame.pack_propagate()
                self.parent.pack_propagate()
        self.updateColors()
        self.query = query
        for i in xrange(query.queryLength):
            self.states[i].Set(self.query.mean[i],
                               self.query.variance[i],
                               self.query.duration[i],
                               self.query.densityType[i])
            self.SetCyclic(self.query.cyclic)


    def configureRange(self, ymin, ymax):
        for s in self.states:
            s.configureRange(ymin, ymax)

    def deleteLastStep(self):
        if len(self.states) > 1:
            self.states[-1].pack_forget()
            self.nrSteps -= 1
            self.states = self.states[:-1]
            self.stepFrame.pack_propagate()
            self.parent.pack_propagate()
            self.overrideQuery(self.query)
	    self.updateColors()


    def appendStep(self):
        self.nrSteps += 1
        self.appendGaussianState(self.nrSteps)
        self.states[-1].configureRange(self.states[0].ymin,self.states[0].ymax)
	self.updateColors()
        self.overrideQuery(self.query)

    def updateColors(self):
        for i,state in enumerate(self.states):
		state.colorUpdate(color=ClassesColorScheme(i,self.nrSteps))

    def ToggleCyclic(self):
        c = int(self.cyclic.get())
        self.query.setCyclic(c)

    def SetCyclic(self,newValue):
        """ Set the GUI to reflect the new cyclicity value """
        self.cyclic.set(newValue)

def ClassesColorScheme(classe,classes_no):
        rgb_tuple = colorsys.hsv_to_rgb(float(classe)/float(classes_no),1.0,1.0)
        hexcolor = '#%02x%02x%02x' % (rgb_tuple[0]*255,rgb_tuple[1]*255,rgb_tuple[2]*255)
        # that's it! '%02x' means zero-padded, 2-digit hex values
        return hexcolor

if __name__ == '__main__':
    app = GQLQueryEditor(3) # 3 states/steps initially
    # q = GQLQueryKludge()
    q = GQLFasterQuery()
    app.overrideQuery(q)
    app.mainloop()

