# -*- pyton-mode -*-
#
# pysga.py
#
# Written by Chris Kuklewicz <chrisk@mit.edu>
#
# This is designed to encapsulate the functions exposed by
# Scigraphica to its python terminal.
#

# Some constants. May need to be moved to a separate file once this becomes
# a large list.
TRUE=1
FALSE=0
SG_PLOT_2D=0
SG_PLOT_3D=1
SG_PLOT_POLAR=2

GTK_PLOT_AXIS_BOTTOM=0
GTK_PLOT_AXIS_TOP=2
GTK_PLOT_AXIS_RIGHT=3
GTK_PLOT_AXIS_LEFT=1

import sg
from sg import *

# Scigraphica often considers columns to be objects
# Columns even get their own names
# There is currently less need for an SGrow object
worksheets={}
class SGcolumn:
    """This represents a specific named column of a worksheet in Scigraphica.
    """
    def __init__(self,sheetName,columnName):
        self.name=sheetName
        self.col=columnName
    def get_values(self):
        "Calls col. Returns an array of the values in this column (doubles)."
        return col(self.col,self.name)
    def set_all(self,theValues):
        """Calls set_column to set all the values at once. Takes an
        object that can be a value, sequence, or array.
        """
        return set_column(self.col,theValues,self.name)
    def value(self,aRow):
        "Calls get_cell_value, Takes a row (int) and returns a double."
        return get_cell_value(self.name,self.col,aRow)
    def set_value(self,aRow,theValue):
        "Calls set_cell_value, Takes a row (int) and value (double)."
        return set_cell_value(self.name,self.col,aRow,theValue)
    def text(self,aRow):
        "Calls get_cell_text. Takes a row (int) and returns a string."
        return get_cell_text(self.name,self.col,aRow)
    def set_text(self,aRow,theText):
        "Calls set_cell_text. Takes a row (int) and text (string)."
        return set_cell_text(self.name,self.col,aRow,theText)
    def set_name(self,newName):
        """Calls set_column_name. Takes the new name (string).
        This SGcolumn object will be updated to still point at same column.
        """
        self.col=newName
        return set_column_name(self.name,self.col,newName)
    def set_expression(self,newExp,fromRow,toRow):
        """Calls set_column_values. Takes an expression to evaluate,
        and optional start and stop row numbers (int >=1).
        The expression can return a value, sequence, or array.
        """
        return set_column_values(self.name,self.col,newExp,fromRow,toRow)
    def get_expression(self):
        """Returns the expression for this column, None if not set."""
        return get_column_exp(self.col,self.name)
    def get_precision(self):
        """Returns the display precision for this column."""
        return get_column_precision(self.col,self.name)
    def set_type(self,newType):
        """Calls set_column_type.  Takes the type as an integer.
        Type is 0=SG_TYPE_NUMBER
                1=SG_TYPE_TEXT
                2=SG_TYPE_DATE
                3=SG_TYPE_TIME
        """
        return set_column_type(self.name,self.col,newType)
    def set_numbers(self,internal,format,precision):
        "Calls set_column_numbers. Takes 3 integer arguments: internal, format, precision."
        return set_column_numbers(self.name,self.col,internal,format,precision)
    def column_names(self):
        "Returns a list of column names in this worksheet."
        return column_names(self.name)

# All worksheets get a name

class SGworksheet:
    """This represents a specific named worksheet in Scigraphica.

    Upon creation it will call new_worksheet.  All data is being
    stored in Scigraphica.
    """
    def __init__(self,sheetName):
        "Call new_worksheet - returns a new object if a sheet with that "\
         "name alrady exists, but does not create a new sheet in that case."
        # GOTCHA : should use a way to test if sheet already exists
        self.name=sheetName
        new_worksheet(self.name)
    def column(self,columnName):
        "Return a SGcolumn object that refers to the named column."
        return SGcolumn(self.name,columnName)
    def remove(self):
        "Call remove_worksheet - kind of a destructor."
        return remove_workseet(self.name)
    def rename(self,newName):
        "Call rename_worksheet."
        self.name=newName
        return rename_worksheet(self.name,newName)
    def cell(self,aRow,aColumn):
        "Calls cell to get the value.  aRow and/or aColumn can be names."
        return cell(aRow,aColumn,self.name)
    def col(self,aColumn):
        "Calls col to get the values. aColumn can be a name."
        return col(aColumn,self.name)
    def row(self,aRow):
        "Callc row to get the values. aRow can be a name."
        return row(aRow,self.name)
    def setcell(self,aRow,aColumn,anObject):
        "Calls setcell.  aRow and/or aColumn can be names."
        return setcell(aRow,aColumn,anObject,self.name)
    def setcol(self,aColumn,anObject):
        "Calls setcol.  aColumn can be a name."
        return setcol(aColumn,anObject,self.name)
    def clearcol(self,aColumn):
        "Calls clear_column.  aColumn can be a name."
        return clear_column(aColumn,self.name)
    def setrow(self,aRow,anObject):
        "Calls setrow.  aRow can be a name."
        return setrow(aRow,anObject,self.name)
    def update(self):
        "Recalculates formulas"
        return update_worksheet(self.name)
    def setarray(self,anArray,aRow=0,aColumn=0):
        "Set sheet from 2D Numeric array."\
        "This is equivalent to setcell(0,0,anArray,sheetname)."
        return setcell(aRow,aColumn,anArray,self.name)
    def row0(self):
        "Gets the index of the first row selected."
        return get_selection(self.name)['row0']
    def rowi(self):
        "Gets the index of the last row selected."
        return get_selection(self.name)['rowi']
    def col0(self):
        "Gets the index of the first column selected."
        return get_selection(self.name)['col0']
    def coli(self):
        "Gets the index of the last column selected."
        return get_selection(self.name)['coli']
    def selection(self):
        "Returns a list of indices denoting the reactangle of the sheet's"\
        "selection area: [row0,col0,rowi,coli]"
        temp=get_selection(self.name)
        return [temp["row0"],temp["col0"],temp["rowi"],temp["coli"]]
    def add_columns(self,num):
        sg.add_columns(num,self.name)
    # TODO: Things that would be good to add in the future
    # def array(self): "Get sheet as 2D Numeric array"

# An axis can be x, y, or z.  Number those as 1, 2, and 3
class SGaxis:
    """Pass 1, 2, or 3 to init for x, y, or z axis.
    Note that only 3D Plot layers have a z axis.
    """
    def __init__(self,plotName,layerIndex,xyz):
        self.name=plotName
        self.index=layerIndex
        self.axis=xyz
    def set_range(self,newMin,newMax):
        "Takes newMin (double) and newMax (double)"
        if (1==self.axis):
            return set_xrange(self.name,self.index,newMin,newMax)
        elif (2==self.axis):
            return set_yrange(self.name,self.index,newMin,newMax)
        elif (3==self.axis):
            return set_zrange(self.name,self.index,newMin,newMax)
    def set_ticks(self,newMajor,newMinor):
        "Takes newMajor (double) and newMinor (int)"
        if (1==self.axis):
            return set_xticks(self.name,self.index,newMajor,newMinor)
        elif (2==self.axis):
            return set_yticks(self.name,self.index,newMajor,newMinor)
        elif (3==self.axis):
            return set_zticks(self.name,self.index,newMajor,newMinor)
    def set_title(self,newTitle):
        "Takes the newTitle (string)"
        if (1==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_TOP,title=newTitle)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,title=newTitle)
        elif (2==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_LEFT,title=newTitle)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_RIGHT,title=newTitle)
        elif (3==self.axis):
            return set_ztitle(self.name,self.index,newTitle)
    def show_titles(self,first=TRUE,second=TRUE):
        "Takes the newTitle (string)"
        print "layer index=",self.index
        if (1==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,show_title=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_TOP,show_title=second)
        elif (2==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_LEFT,show_title=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_RIGHT,show_title=second)
    def show_labels(self,first=TRUE,second=TRUE):
        "Takes the newTitle (string)"
        if (1==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,show_labels=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_TOP,show_labels=second)
        elif (2==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_LEFT,show_labels=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_RIGHT,show_labels=second)
    def label_digits(self,first=TRUE,second=TRUE):
        "Takes the newTitle (string)"
        if (1==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,label_prec=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_TOP,label_prec=second)
        elif (2==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_LEFT,label_prec=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_RIGHT,label_prec=second)
    def label_style(self,first=TRUE,second=TRUE):
        "Takes the newTitle (string)"
        if (1==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,label_style=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_TOP,label_style=second)
        elif (2==self.axis):
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_LEFT,label_style=first)
            set_axis_text_properties(self.name,self.index,GTK_PLOT_AXIS_RIGHT,label_style=second)
    def label_pos(self,offset=0.,justify_axis=0.5,angle=-999,justify=2):
        "Takes the newTitle (string)"
        if (1==self.axis):
            if angle==-999:
              angle=0.
            set_axis_label_pos(self.name,self.index,GTK_PLOT_AXIS_BOTTOM,offset,justify_axis,angle,justify)
            set_axis_label_pos(self.name,self.index,GTK_PLOT_AXIS_TOP,offset,justify_axis,angle,justify)
        elif (2==self.axis):
            if angle==-999:
              angle=90.
            set_axis_label_pos(self.name,self.index,GTK_PLOT_AXIS_LEFT,offset,justify_axis,angle,justify)
            set_axis_label_pos(self.name,self.index,GTK_PLOT_AXIS_RIGHT,offset,justify_axis,angle,justify)
    def show_grid(self):
        if (1==self.axis):
            return show_xgrids(self.name,self.index)
        elif (2==self.axis):
            return show_ygrids(self.name,self.index)
        elif (3==self.axis):
            return show_zgrids(self.name,self.index)
    def hide_grid(self):
        if (1==self.axis):
            return hide_xgrids(self.name,self.index)
        elif (2==self.axis):
            return hide_ygrids(self.name,self.index)
        elif (3==self.axis):
            return hide_zgrids(self.name,self.index)
    # TODO: add way to enable autoscaling an axis

# Layers in a Plot have numbers instead of names.
# This seems somehow inconsistant with the column / sheet scheme.
class SGlayer:
    def __init__(self,plotName,layerIndex):
        self.name=plotName
        self.index=layerIndex
    def remove(self):
        "Erase this layer and its contents"
        return remove_layer(self.name,self.index)
    def clear(self):
        "Erase the plot contents of this layer"
        return clear_layer(self.name,self.index)
    def move(self,newX,newY):
        "Takes the new absolute X (double) and Y (double)"
        return move_layer(self.name,self.index,newX,newY)
    def resize(self,newW,newH):
        "Takes the new absolute width (double) and height (double)"
        return resize_layer(self.name,self.index,newW,newH)
    def show_legend(self):
        "Display the legend box for this layer"
        return show_legends(self.name,self.index)
    def hide_legend(self):
        "Hide the legend box for this layer"
        return hide_legends(self.name,self.index)
    def move_legend(self,newX,newY):
        "Takes the new absolute X (double) and Y (double)"
        return move_legends(self.name,self.index,newX,newY)
    def axis(self,xyz):
        "Takes 1 or 2 or 3 to get the x or y or z axis."
        return SGaxis(self.name,self.index,xyz)
    def xaxis(self):
        "Return an object that represents the xaxis of this layer"
        return SGaxis(self.name,self.index,1)
    def yaxis(self):
        "Return an object that represents the yaxis of this layer"
        return SGaxis(self.name,self.index,2)
    def zaxis(self):
        "Return an object that represents the zaxis of this layer"
        return SGaxis(self.name,self.index,3)
    def plot_expression(self,newExp):
        "Takes an expression of 'x' to plot (string)"
        return plot_exp(self.name,self.index,newExp)
    def plot_python(self):
        "XXX Not yet implimented"
        pass
    def plot_columns(self):
        "XXX Not yet implimented"
        pass
    def plot_expression_xy(self,expX,expY):
        "Takes expressions for 'x' and 'y' to plot in this layer"
        return plot_exp_xy(self.name,self.index,expX,expY)
    def plot_expression_xy_bars(self,expX,expY,width):
        "Takes expressions for 'x' and 'y' to plot in this layer as vertical"
        "bars"
        return plot_exp_xy_bars(self.name,self.index,expX,expY,width)
    def set_symbol(self,symbolNumber):
        "Set default symbol, by number, for plotting in this layer"
        return set_symbol(self.name,self.index,symbolNumber)
    def set_symbol_style(self,symbolStyle):
        "Set default symbol style, by number, for plotting in this layer"
        return set_symbol_style(self.name,self.index,symbolStyle)
    def set_symbol_color(self,symbolColor):
        "Set default symbol color, for plotting in this layer"
        return set_symbol_color(self.name,self.index,symbolColor)
    def set_line_style(self,lineStyle):
        "Set default line style, by number, for plotting in this layer"
        return set_line_style(self.name,self.index,lineStyle)
    def set_line_color(self,lineColor):
        "Set default line color, for plotting in this layer"
        return set_line_color(self.name,self.index,lineColor)
    def set_connector(self,plotConnector):
        "Set default connector for plotting in this layer"
        return set_connector(self.name,self.index,plotConnector)
    def is_active(self):
        "Returns TRUE if this layer is the active layer, FALSE if not"
        if plot_active_layer(self.name)==self.index:
          return TRUE
        else:
          return FALSE
    def datasets(self):
        "Returns a list of datasets in this layer"
        return plot_layer_datasets(self.name,self.index)
    def refresh(self):
        "Redraws the layers in this plot"
        return plot_layer_refresh(self.name,self.index)
    def autoscale(self):
        "Autoscales this layer"
        return plot_layer_autoscale(self.name,self.index)



# Plots all have a unique name
class SGplot:
    def __init__(self,layerType=0,plotName=None):
        "layerType 0 is SG_PLOT_2D, 1 is SG_PLOT_3D, 2 is SG_PLOT_POLAR.\n"
        if plotName==None:
          self.name=sg.active_plot()
        else:
          self.name=plotName
        sg.new_plot(layerType,self.name)
    def remove(self):
        return remove_plot(self.name)
    def clear(self):
        return clear_plot(self.name)
    def rename(self,newName):
        "Takes the new name (string). This object will still refer the same plot"
        self.name=newName
        return rename_plot(self.name,newName)
    def new_layer(self,layerType):
        # TODO: This should return an index to the new layer or an SGlayer
        "Takes a layer type (integer), where 0 is SG_PLOT_2D, 1 is SG_PLOT_3D, 2 is SG_PLOT_POLAR.\n"
        return new_layer(self.name,layerType)
    def layer(self,layerIndex):
        "Returns an object which refers to a particular layer number"
        return SGlayer(self.name,layerIndex)
    def layers(self):
        "Returns the number of layers in the plot"
        return plot_layers(self.name)
    def active_layer(self):
        "Returns the number of the active layer in the plot"
        return plot_active_layer(self.name)
    def refresh_canvas(self):
        "Redraws the current canvas"
        return plot_refresh_canvas(self.name)
    def freeze(self):
        "Suspends visual updates to the plot"
        return plot_freeze_canvas(self.name)
    def thaw(self):
        "Resumes visual updates to the plot"
        return plot_thaw_canvas(self.name)

# Configuration object
class SGconfig:
    "A configuration object, determined by an object and a group name.\n"
    "Create a new object using the object and group names, as well as two\n"
    "callable objects:\n"
    "def   : to set the default value.\n"
    "commit: to register any side-effects required by a change in the configuration\n"   
    "        object value.\n"
    def __init__(self,name,group,default,commit):
      self.name=name
      self.group=group
      config_register(name,group,default,commit)
    def value(self):
      "Returns the configuration object value"
      return config_get_value(self.name,self.group)
    def set(self,value,overwrite=TRUE):
      "Sets the value of the configuration object. Takes the value as an argument."
      config_set_value(self.name,self.group,value,overwrite)
    def set_default(self):
      "Calls the function that sets the default value"
      config_exec_default(self.name,self.group)
    def commit(self):
      "Calls the function that activates any side-effects the configuration object"
      "might have."
      config_exec_commit(self.name,self.group)
    def set_commit(self,value):
      "Sets the object value, and calls the function that activates any side-effects\n"
      "the configuration object might have."
      config_set_value(self.name,self.group,value)
      config_exec_commit(self.name,self.group)

# Simple example. Plotting expressions:
#   from pysga import *
#   k=SGplot(0,'K-Plot')
#   l=k.layer(1)
#   l.plot_expression('x*(x-10.0)')
#   l.xaxis().set_range(0,10)
#   l.xaxis().set_ticks(1,0)
#   l.yaxis().set_range(0,30)
#   l.yaxis().set_ticks(6,1)

# Another example. Plotting columns:
#   from pysga import *
#   w=SGworksheet('W1')
#   w.setcol('A',range(0,10))
#   w.setcol('B',w.col('A')**2)
#   p=SGplot(0,'Columns')
#   l=p.layer(1)
#   l.set_symbol_style(1)
#   l.set_symbol_color("blue")
#   l.set_line_style(1)
#   l.set_line_color("red")
#   l.plot_expression_xy("col('A','W1')","col('B','W1')")

