import os
import string

from quixote import get_field, get_path, get_session, get_request, redirect
from quixote.directory import Directory
from quixote.errors import TraversalError, PublishError, AccessError, QueryError
from quixote.util import StaticFile

from simpletal import simpleTALES

import compClust.config
from compClust.gui.ClusteringUI import ClusteringManagerUI
from compClust.gui.LabelingUI import LabelingManagerUI
from compClust.gui.LabelingSource import LabelingSource
from compClust.gui.PlotUI import PlotUI
from compClust.gui.TemplateUtils import template_header, template_footer, \
                                        get_template, _path_join
from compClust.gui.DataSource import DataSource

from compClust.util import DistanceMetrics

templates = compClust.config.config.template_dir

class NotLoggedInError(PublishError):
  def __init__(self, description, source_url):
    PublishError.__init__(self, description)
    self.source_url = source_url

class DatasourceUI(Directory):
  """Wrap a datasource object and give it a web interface
  """
  _q_exports = ['', 'edit', 'view', 'save', 'unload', 'delete', 'plots', 'labelplot', 'trajectory_plot', 'cluster_detail_plot', 'cluster_trajectories', 'confusion_matrix_plot', 'roc', 'labels', 'cluster', 'pca_projection', 'pca_eigenvector', 'pceg_list', 'pc_vs_pc_with_extreme_genes', 'pceg_condition_list', 'pceg_in_sig_order', 'pceg_in_native_order', 'pceg_condition_scores']

  def __init__(self, datamanagerui, datasource):
    self.datamanagerui = datamanagerui
    self.datasource = datasource
    self.baseurl = _path_join(self.datamanagerui.root_url, self.datasource.id)
    self.ploturl = _path_join(self.baseurl, "plots")
    self.downloadploturl = _path_join(self.baseurl, "plots", "download")
    self.__labels = LabelingManagerUI(self.datamanagerui, self.datasource, self.baseurl)
    self.cluster = ClusteringManagerUI(self.datamanagerui, self.datasource, self.baseurl)
    self.plots = PlotUI(self.datasource)

  def __get_labels(self):
    return self.__labels
  labels = property(__get_labels, doc="Return labelingmanager")

  def _q_index(self):
    context = self.datamanagerui.create_context(self.datasource)
    return get_template('Datasource', context)

  
  def view(self):
    # render page
    context = self.datamanagerui.create_context(self.datasource)
    context.addGlobal('datasourcename', self.datasource.name)
    if self.datasource.is_loaded:
      shape = (self.datasource.dataset.numRows,self.datasource.dataset.numCols)
      context.addGlobal('datasourcesize', "%s x %s"%(shape))
    else:
      context.addGlbal('datasourcesize', "not loaded")

    labelings = self.datasource.get_labeling_sources()
    addlabel_qs = _path_join(self.baseurl, "labels/%s")
    editlabel_qs = _path_join(self.baseurl, "labels/%s/")
    annotations = [ (editlabel_qs % (l.id), l) for l in labelings
                    if l.isannotation == True]
    clusterings = [ (editlabel_qs % (l.id), l) for l in labelings
                    if l.isannotation==False]
    context.addGlobal('annotations', annotations)
    context.addGlobal('clusterings', clusterings)
    context.addGlobal('datasourcesource', self.datasource.source)
    context.addGlobal('add_annotation_url', addlabel_qs % ('add_annotation'))
    context.addGlobal('add_clustering_url', addlabel_qs % ('add_clustering'))
    # attach variable describing available labelings and which is selected
    self.add_annotation_context(context,'primary',self.datasource.primary)
    self.add_annotation_context(context,'secondary',self.datasource.secondary)
    context.addGlobal('save_url', _path_join(self.baseurl, 'save'))
    context.addGlobal('unload_url', _path_join(self.baseurl, 'unload'))
    context.addGlobal('delete_url', _path_join(self.baseurl, 'delete'))
    context.addGlobal('loaded', self.datasource.is_loaded)
    return get_template('Datasource_view', context)

  def edit(self):
    """Update settings for a data source
    """
    datasourcename = get_field('datasourcename', None)
    primary = get_field('primary', None)
    secondary = get_field('secondary', None)

    if datasourcename is not None:
      self.datasource.name = datasourcename
    if primary is not None and primary != "None":
      self.datasource.primary = primary
    if secondary is not None and secondary != "None":
      self.datasource.secondary = secondary
    return redirect(self.baseurl)
    # TODO: add the ability to change a datasource

  def save(self):
    """Save datasource to persistence cache
    """
    # FIXME: session issue
    get_session().datamanager.persist(self.datasource.id)
    
    html = [template_header(self.datamanagerui)]
    html += [self.datamanagerui.menu(self.datasource)]
    html += ["<p>saved %s to persistent store</p>" % self.datasource.getShortenedName()]
    html += [template_footer()]
    return string.join(html, os.linesep)
  
  def unload(self):
    """Try to remove dataset from memory
    """
    # FIXME: session issue
    get_session().datamanager.unload(self.datasource.id)

    html = [template_header(self.datamanagerui)]
    html += [self.datamanagerui.menu(self.datasource)]
    html += ["<p>unloaded %s from memory</p>" % self.datasource.getShortenedName()]
    html += [template_footer()]
    return string.join(html, os.linesep)

  def delete(self):
    """Delete datasource from persistence cache
    """
    # FIXME: session issue
    get_session().datamanager.delete(self.datasource.id)

    html = [template_header(self.datamanagerui)]
    html += [self.datamanagerui.menu(self.datasource)]
    html += ["<p>Removed %s from persistent store</p>" % self.datasource.getShortenedName()]
    html += [template_footer()]
    return string.join(html, os.linesep)

  def labelplot(self):
    """Show plot for a particular trajectory including its labelings
    """
    if self.datasource.primary is None:
      raise PublishError(public_msg="Please go to the dataset edit page and set a primary labeling")
    
    subset = get_field('subset', None)
    set = get_field('set', None)
  
    context = self.datamanagerui.create_context(self.datasource)
    self.add_clustering_context(context, 'set', set)
  
    # convert from string to actual labeling
    set = self.datasource.get_labeling_by_name(set)

    print "1 views: ", len(self.datasource.dataset.getViews())
    datasetplot = self.datasource.dataset_plot(subset_label=subset, subset=set)
    print "2 views: ", len(self.datasource.dataset.getViews())
  
    detail_fmt = "%s <Labeling %s: %d labels>"
    detail=[ detail_fmt %(label, labeling.getName(), len(labeling.getLabels()))
             for labeling, label in self.datasource.get_labelings_by_primary(subset)]
    
    context.addGlobal('labeling_detail', detail)
    context.addGlobal('labelplot', _path_join(self.ploturl, datasetplot.id))
    context.addGlobal('downloadploturl',
                      self.downloadploturl+"?plotid="+str(datasetplot.id))
    context.addGlobal('csimap', datasetplot.get_data_imagemap(self.baseurl, set))
    return get_template('labelplot', context)

  def trajectory_plot(self):
    """EXPERIMENTAL Plot for working toward web accessible pca viewer
    """
    #subset_label = get_field('subset_label', None)
    #color_label = get_field('color_label', None)
    #color_label = 'pc01-outlier-probes'
    set = 'pc01-outlier-probes'
  
    context = self.datamanagerui.create_context(self.datasource)
    # save combo settings
    #context.addGlobal('subset_label', subset_label)
    #self.add_clustering_context(context, 'set', set)
  
    # get labelings instead of simple strings
    set = self.datasource.get_labeling_by_name(set)
    datasetplot = self.datasource.dataset_plot()
    
    context.addGlobal('cluster_detail_plot', _path_join(self.ploturl, datasetplot.id))
    context.addGlobal('downloadploturl',
                      self.downloadploturl+"?plotid="+str(datasetplot.id))
    context.addGlobal('csimap', datasetplot.get_data_imagemap(self.baseurl, self.datasource.primary))
    return get_template('cluster_detail_plot', context)
  
  def cluster_detail_plot(self):
    subset_label = get_field('subset_label', None)
    set = get_field('set', None)
  
    context = self.datamanagerui.create_context(self.datasource)
    # save combo settings
    context.addGlobal('subset_label', subset_label)
    self.add_clustering_context(context, 'set', set)
  
    # get labelings instead of simple strings
    set = self.datasource.get_labeling_by_name(set)
  
    datasetplot = self.datasource.dataset_plot(subset_label=subset_label, subset=set)
    
    context.addGlobal('cluster_detail_plot', _path_join(self.ploturl, datasetplot.id))
    context.addGlobal('downloadploturl',
                      self.downloadploturl+"?plotid="+str(datasetplot.id))
    context.addGlobal('csimap', datasetplot.get_data_imagemap(self.baseurl, self.datasource.primary))
    return get_template('cluster_detail_plot', context)

  def cluster_trajectories(self):
    set = get_field('set', None)
    context = self.datamanagerui.create_context(self.datasource)
    self.add_clustering_context(context, 'set', set)
    
    if set is not None and set != "None":
      # we have enough information to create a plot
      set = self.datasource.get_labeling_by_name(set)
      ts_plot = self.datasource.cluster_trajectories(set)
      context.addGlobal('cluster_trajectories_plot',
                        _path_join(self.ploturl,
                                     ts_plot.id))
      context.addGlobal('downloadploturl', 
                        self.downloadploturl+"?plotid="+str(ts_plot.id))
                                                        
      context.addGlobal('csimap', ts_plot.get_axes_imagemap(self.baseurl))
    return get_template('cluster_trajectories', context)
  
  def confusion_matrix_plot(self):
    labeling1 = get_field('labeling1', None)
    labeling2 = get_field('labeling2', None)
    
    # store our parameters
    context = self.datamanagerui.create_context(self.datasource)
    self.add_clustering_context(context, 'labeling1', labeling1)
    self.add_clustering_context(context, 'labeling2', labeling2)
  
    # convert StringFields to labelings
    labeling1 = self.datasource.get_labeling_by_name(labeling1)
    labeling2 = self.datasource.get_labeling_by_name(labeling2)
      
    if labeling1 is not None and labeling2 is not None:
      # make plot
      cm_plot = self.datasource.confusion_matrix_summary(labeling1, labeling2)
      context.addGlobal('confusion_matrix_plot',
                        _path_join(self.ploturl, cm_plot.id))
      context.addGlobal('downloadploturl',
                        self.downloadploturl+"?plotid="+str(cm_plot.id))
      context.addGlobal('csimap', cm_plot.get_axes_imagemap(self.baseurl))
  
    return get_template('confusion_matrix_plot', context)
  
  def roc(self):
    """Render a roc plot.
  
    label is the label element to compare to.
    labeling is which labeling the comparison is supposed to be done with
    distance is distance metric.
    """
    labeling = get_field('labeling', None)
    # since most of the code checks for the Python None type to avoid
    # processing we should change the text representation to the python
    # type
    if labeling == "None":
      labeling = None
      
    label = get_field('label', None)
    distance = get_field('distance', None)
    
    context = self.datamanagerui.create_context(self.datasource)
    self.add_clustering_context(context, 'labeling', labeling)
  
    labelinge = self.datasource.get_labeling_by_name(labeling)

    if labeling is not None:
      # get labels from labeling
      labels = self.datasource.get_labels_for_labeling(labeling)
      context.addGlobal('labels', html_combo_selection(labels, label))
      if label is not None:
        if label not in self.datasource.get_labels_for_labeling(labeling):
          raise QueryError("unrecognized label for %s" % (labeling))
      context.addGlobal('prompt_for_label', True)
    else:
      context.addGlobal('prompt_for_label', False)
      
    if distance is None:
      distance = DistanceMetrics.EuclideanDistance
    
    if labeling is not None and label is not None:
      labeling = self.datasource.get_labeling_by_name(labeling)
      roc_plot = self.datasource.roc_plot(label, labeling, distance)
      context.addGlobal('rocplot', _path_join(self.ploturl, roc_plot.id))
      context.addGlobal('downloadploturl',
                        self.downloadploturl+"?plotid="+str(roc_plot.id))
      
    return get_template('roc', context)

  def pca_eigenvector(self):
    eigen_plot =self.datasource.pca_eigenvector()
    # store our parameters
    context = self.datamanagerui.create_context(self.datasource)

    context.addGlobal('eigenplot', _path_join(self.ploturl, eigen_plot.id))
    context.addGlobal('downloadploturl',
                      self.downloadploturl+"?plotid="+str(eigen_plot.id))
    return get_template('pca_eigen', context)
  
  ####
  # Utility functions for the following pca style plots
  def __make_pca_option_list(self, variances, pcNum):
    """Make one of the pca lists used for the html combo boxes
    that are pc compoent - variance captured.
    
    The component numbers are converted from the internal 0 base to 1 base
    for the convienence of the users.
    
    :Parameters:
      - `variances`: list of the variance captured by each eigen vector
      - `pcNum`: the 0 based principal component to look at.
    """
    label = "%s - %.2f" % (pcNum+1, variances[pcNum]*100)    
    variance_list= ["%s - %.2f"%(x+1, variances[x]*100) for x in xrange(len(variances))]
    return html_combo_selection(variance_list, label)
    
  def __get_pca_option_value(self, field_name, default_value):
    """
    Parse the form variable of our pca_option_list for our PC number
    
    The component numbers are converted from the internal 0 base to 1 base
    for the convienence of the users.
    
    :Parameters:
      - `field_name`: Which html form variable to parse
      - `default_value`: the default value to return if the field set in the form
      
    :Returns:
      Tuple containing the integer value and the label we pulled out of the form
    """
    label = get_field(field_name,None)   
    if label is None:
      value = default_value
    else:
      # since we have a value from the form convert it back to our zero base
      value = int(string.split(label)[0])-1
    return (value, label)
    
  def pca_projection(self):
    xaxis, xaxis_label = self.__get_pca_option_value('xaxis', 0)
    yaxis, yaxis_label = self.__get_pca_option_value('yaxis', 1)

    # make plot
    pca_plot =self.datasource.pca_projection(xaxis, yaxis)
    pca_iplot = pca_plot.iplot
    variances = pca_iplot.getEigenVariances()

    # make plot label
    xaxis_list = self.__make_pca_option_list(variances, xaxis)
    yaxis_list = self.__make_pca_option_list(variances, yaxis)

    # this actually forces the image to be saved
    pca_iplot.image_filename
    
    set = self.datasource.primary.name

    # store our parameters
    context = self.datamanagerui.create_context(self.datasource)
    context.addGlobal('xaxes', xaxis_list)
    context.addGlobal('yaxes', yaxis_list)

    context.addGlobal('pcaplot', _path_join(self.ploturl, pca_plot.id))
    context.addGlobal('downloadploturl',
                      self.downloadploturl+"?plotid="+str(pca_plot.id))
    context.addGlobal('csimap', pca_plot.get_scatter_imagemap(self.baseurl, set))
  
    return get_template('pca_projection', context)

  def pceg_list(self):
    """Render extreme gene list (aka PCA Ginzu getOutputForPCNOutliers / row labelings)
    """
    context = self.datamanagerui.create_context(self.datasource)
    context.addGlobal('plottitle', 'PCEG List')
   
    # make the combo box for which pca component to render
    pcnum, pcnum_label = self.__get_pca_option_value('pcNum', 0)
    # generate list of row labels that we'll be rendering
    checkboxList = []
    checkedList = []
    row_labels = [ x.name for x in self.datasource.get_labeling_sources()
                          if x.isrow ]
    row_labels.sort()
    for label in row_labels:
      fieldState = get_field(label,None)
      checkboxList.append({'id': label, 'label': label, 'checked': fieldState})
      if fieldState is not None:
        checkedList.append(label)
        
    # should we be making a report?
    if pcnum_label is not None:
      report = self.datasource.pceg_analysis.getOutputForPCNOutliers(pcnum+1, checkedList)
      context.addGlobal('report_head', report[0])
      context.addGlobal('report_body', report[1:])
      
    variances = self.datasource.pceg_analysis.getEigenVariances()
    context.addGlobal('pcNums', self.__make_pca_option_list(variances, pcnum))
    context.addGlobal('checkboxList', checkboxList)
    return get_template('pceg_list', context)
    
  def pc_vs_pc_with_extreme_genes(self):
    context = self.datamanagerui.create_context(self.datasource)
    variances = self.datasource.pceg_analysis.getEigenVariances()
    
    pcnumx, pcnumx_label = self.__get_pca_option_value('pcNumX', 0)
    pcnumy, pcnumy_label  = self.__get_pca_option_value('pcNumY', 1)
    
    if pcnumx_label is not None and pcnumy_label is not None:
      pca_plot = self.datasource.pc_vs_pc_with_extreme_genes(pcnumx,pcnumy)
      context.addGlobal('pcaplot', _path_join(self.ploturl, pca_plot.id))
      context.addGlobal('downloadploturl',
                        self.downloadploturl+"plotid="+str(pca_plot.id))
      subset_label =self.datasource.get_labeling_by_name(self.datasource.primary)
      if subset_label is not None:
        context.addGlobal('csimap', pca_plot.get_scatter_imagemap(self.baseurl, subset_label))      
      
    context.addGlobal('pcNumsX', self.__make_pca_option_list(variances, pcnumx))
    context.addGlobal('pcNumsY', self.__make_pca_option_list(variances, pcnumy))
                        
    return get_template('pceg_scatter', context)
    
  def pceg_condition_list(self):
    """Render PCEG (aka PCA Ginzu getOutputForSigGroups / col labelings)
    """
    context = self.datamanagerui.create_context(self.datasource)
   
    # make the combo box for which pca component to render
    pcnum, pcnum_label = self.__get_pca_option_value('pcNum', 0)
    # generate list of row labels that we'll be rendering
    checkboxList = []
    checkedList = []
    row_labels = [ x.name for x in self.datasource.get_labeling_sources()
                          if not x.isrow ]
    row_labels.sort()                          
    for label in row_labels:
      fieldState = get_field(label,None)
      checkboxList.append({'id': label, 'label': label, 'checked': fieldState})
      if fieldState is not None:
        checkedList.append(label)
        
    # should we be making a report?
    if pcnum_label is not None:
      report = self.datasource.pceg_analysis.getOutputForSigGroups(pcnum+1, checkedList)
      if report is None or len(report) == 0:
        context.addGlobal('report_head', ['No extreme genes found'])
        context.addGlobal('report_body', [])
      else:
        context.addGlobal('report_head', report[0])
        context.addGlobal('report_body', report[1:])
      
    variances = self.datasource.pceg_analysis.getEigenVariances()
    context.addGlobal('pcNums', self.__make_pca_option_list(variances, pcnum))
    context.addGlobal('checkboxList', checkboxList)
    return get_template('pceg_condition_list', context)
      
  def pceg_condition_scores(self):
    """Render PCEG (aka pcaGinzu scoreColumnLabelingForPCN)
    """
    def column_score_sort_by_name(s1, s2):
      s1_name = s1.labeling.getName()
      s2_name = s2.labeling.getName()
      
      if s1_name < s2_name:
        return -1
      elif s1_name == s2_name:
        return 0
      else:
        return 1
      
    def column_score_sort_by_score(s1, s2):
      s1_score = min(s1.scores)
      s2_score = min(s1.scores)
      
      if s1_score < s2_score:
        return -1
      elif s1_score == s2_score:
        return 0
      else:
        return 1
    def format_score(s):
      print s
      return ("%.5f" % (s))
    
    context = self.datamanagerui.create_context(self.datasource)
   
    # make the combo box for which pca component to render
    pcnum, pcnum_label = self.__get_pca_option_value('pcNum', 0)
        
    # should we be making a report?
    if pcnum_label is not None:
      column_scores = self.datasource.pceg_analysis.scoreColumnLabelingsForPCN(pcnum+1)
      if not (column_scores is None or len(column_scores) == 0):
        column_scores.sort(column_score_sort_by_name)
        discrete = []
        continuous = []
        for score in column_scores:
          score_dict = {'name': score.labeling.getName()}
          if score.is_discrete:
            score_dict['score'] = format_score(score.scores)
            discrete.append(score_dict)
          else:
            score_dict['best'] = format_score(min(score.scores))
            score_dict['score'] = [ format_score(x) for x in score.scores ]
            continuous.append(score_dict)
        print column_scores, discrete, continuous
                    
        context.addGlobal('discrete', discrete)
        context.addGlobal('continuous', continuous)
      
    variances = self.datasource.pceg_analysis.getEigenVariances()
    context.addGlobal('pcNums', self.__make_pca_option_list(variances, pcnum))
    return get_template('pceg_condition_scores', context)
    
  def pceg_in_native_order(self):
    context = self.datamanagerui.create_context(self.datasource)
    variances = self.datasource.pceg_analysis.getEigenVariances()
    
    pcnum, pcnum_label = self.__get_pca_option_value('pcNum', 0)
    
    if pcnum_label is not None:
      # we have a selected principal component, so make the plot
      pca_plot = self.datasource.pceg_in_native_order(pcnum)
      context.addGlobal('pcaplot', _path_join(self.ploturl, pca_plot.id))
      context.addGlobal('downloadploturl',
                        self.downloadploturl+"plotid="+str(pca_plot.id))
      subset_label =self.datasource.get_labeling_by_name(self.datasource.primary)
      if subset_label is not None:
        context.addGlobal('csimap', pca_plot.get_data_imagemap(self.baseurl, subset_label))
            
    context.addGlobal('pcNums', self.__make_pca_option_list(variances, pcnum))
                        
    return get_template('pceg_native_order', context)
  
  def pceg_in_sig_order(self):
    context = self.datamanagerui.create_context(self.datasource)
    variances = self.datasource.pceg_analysis.getEigenVariances()
    
    pcnum, pcnum_label = self.__get_pca_option_value('pcNum', 0)
    
    if pcnum_label is not None:
      pca_plot = self.datasource.pceg_in_sig_order(pcnum)
      context.addGlobal('pcaplot', _path_join(self.ploturl, pca_plot.id))
      context.addGlobal('downloadploturl',
                        self.downloadploturl+"plotid="+str(pca_plot.id))
      subset_label =self.datasource.get_labeling_by_name(self.datasource.primary)
      if subset_label is not None:
        context.addGlobal('csimap', pca_plot.get_data_imagemap(self.baseurl, subset_label))
            
    context.addGlobal('pcNums', self.__make_pca_option_list(variances, pcnum))
                        
    return get_template('pceg_sig_order', context)
    
  #####
  # Utility functions for constructing web pages
  def add_annotation_context(self, context, name, value):
    """come up with values for combo boxes containing annotations
    value indicates which value is currently selected
    """
    return self.add_label_context(context, name, value, isannotation=True)

  def add_clustering_context(self, context, name, value):
    """come up with values combo boxes containing clusterings
    """
    return self.add_label_context(context, name, value, isannotation=False)
    #return self.add_all_label_context(context, name, value, isannotation=False)

  def add_label_context(self, context, name, value, isannotation):
    """come up with values combo boxes containing any type of labeling
    """
    labels = self.datasource.get_labelingsource_names()
    labels = [ l for l in self.datasource.get_labelingsource_names() if self.datasource._labeling_sources[l].isannotation == isannotation ]
    labels.insert(0, "None")
    if name == 'primary' and value is None:
      value = self.datasource.primary
    elif name == 'secondary' and value is None:
      value = self.datasource.secondary
    context.addGlobal(name, html_combo_selection(labels, value))

  def add_all_label_context(self, context, name, value, isannotation):
    """Get all the labelings attached to a dataset
    """
    labels = self.datasource.dataset.getLabelings()
    labels.extend([ l for l in self.datasource.get_labelingsource_names() if self.datasource._labeling_sources[l].isannotation == isannotation ])
    labels.sort()
    labels.insert(0, "None")
    if name == 'primary' and value is None:
      value = self.datasource.primary
    elif name == 'secondary' and value is None:
      value = self.datasource.secondary
    context.addGlobal(name, html_combo_selection(labels, value))

  def sort_labeling_by_name(self, a_labeling, b_labeling):
    a = str(a_labeling)
    b = str(b_labeling)
    if a < b:
      return -1
    elif a > b:
      return 1
    else:
      return 0

def html_combo_selection(choices, selection):
  """Create a list of containing 'selected' in which ever position
  of the choices list which equals 'selection'
  """
  # if we pass in a labelingsource get a meaningful string name
  if isinstance(selection, LabelingSource):
    selection = selection.name
  if selection is None:
    selection = "None"
  selected_flags = []
  for c in choices:
    if c == selection:
      selected_flags.append("selected")
    else:
      selected_flags.append(None)
  return zip(choices, selected_flags)
    
      
class DatamanagerUI(Directory):
  _q_exports = ['', 'login', 'logout', 'plots', 'uploaddata', 'uploaddata_action', 'compclust_css',
                'sorttable_js']
  def __init__(self, root_url, read_only=False):
    self.root_url = root_url
    self.read_only = read_only
    self.loginurl = _path_join(self.root_url, 'login')
    
  def create_context(self, datasource=None):
    context = simpleTALES.Context()
    context.addGlobal('menu', self.menu(datasource))
    context.addGlobal('compclust_css', _path_join(self.root_url,
                                                  'compclust_css'))
    context.addGlobal('sorttable_js', _path_join(self.root_url,
                                                'sorttable_js'))
    return context

    
  def _q_access(self):
    if get_path() == self.loginurl:
      return
    elif get_session().user is None:
      raise NotLoggedInError("You must be logged in", get_path())

  def _q_exception_handler(self, exception):
    if isinstance(exception, NotLoggedInError):
      context = simpleTALES.Context()
      # save the url the user tried to connect to
      context.addGlobal('loginurl', self.loginurl)
      context.addGlobal('source_url', exception.source_url)
      return get_template('login', context)
    else:
      return AccessError(exception)
  
  compclust_css = StaticFile(os.path.join(templates, "compclust.css"), 
                             mime_type="text/css")
  sorttable_js = StaticFile(os.path.join(templates, "sorttable.js"), 
                             mime_type="text/javascript")

  def login(self):
    username = get_field('username', None)
    password = get_field('password', None)
    source_url = get_field('source_url', None)
    if get_session().initialize(username):
      if source_url is not None and len(source_url) > 0:
        return redirect(source_url)
      else:
        return redirect(self.root_url, 'compclust')
    else:
      context = simpleTALES.Context()
      context.addGlobal('source_url', source_url)
      context.addGlobal('loginurl', self.loginurl)
      return get_template('login')
    
  def logout(self):
    get_session_manager().expire_session()
    return get_template('logout')

  def menu(self, datasource=None):
    datasource_head ='<li><a href="%(rooturl)s/%(dsid)s/">DS: %(dsname)s</a></li>'
    view_head ='<li><a href="%(rooturl)s%(dsid)s/"> V: %(dsname)s</a></li>'
    analysis_detail = """\
    <li><ul>
      <li><a href="%(rooturl)s/%(dsid)s/view">Edit</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/cluster">Clustering</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/cluster_trajectories">Cluster Trajectories</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/confusion_matrix_plot">Confusion Matrix</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/roc">ROC Analysis</a></li>
      <!-- li><a href="%(rooturl)s/%(dsid)s/pca_projection">Scatter Plots</a></li -->
      <li><a href="%(rooturl)s/%(dsid)s/pca_eigenvector">PCA Eigen Vectors</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pceg_list">PC Extreme Gene Lists</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pc_vs_pc_with_extreme_genes">PCA Projection Plots</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pceg_condition_list">PC Condition Lists</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pceg_condition_scores">PC Condition Covariate Scores</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pceg_in_native_order">PCEG Trajectories in Native Order</a></li>
      <li><a href="%(rooturl)s/%(dsid)s/pceg_in_sig_order">PCEG Trajectories in Significance Order</a></li>
      <!-- possibly 2 trajectory plots for one menu item -->
    </ul></li>
"""

    analysis_list = []
    # FIXME: session issue
    for dsid, dsname, dslabelings, isloaded in get_session().datamanager.browse_list():
      if isloaded:
        params = {"rooturl": self.root_url, "dsid": dsid, "dsname": dsname}
        
        analysis_list.append(datasource_head % (params))
        if datasource is not None and datasource.id == dsid:
          analysis_list.append(analysis_detail % (params))
          # FIXME: diable views viewing until we actually figure out what to do with them
          #for view in datasource.dataset.getViews():
          #  params['dsname'] = str(view)
          #  analysis_list.append(view_head % (params))
    # if we're in read-only mode, don't include the upload url
    
    menu_parameters = {'rooturl': self.root_url,
                       'analysis': string.join(analysis_list, os.linesep),
                       'upload_path': ""}
    if not self.read_only:
      menu_parameters['upload_path']= '<li><a href="%(rooturl)s/uploaddata">Upload Dataset</a></li>' % menu_parameters

    mainmenu = """\
  <div id="compclustMenu">
  <h1>Navigation Menu</h1>
  <ul class="linkMenu">
    <li><a href="%(rooturl)s/logout">Logout</a></li>
  </ul>
  <hr/>
  <ul class="linkMenu">
    <li><a href="%(rooturl)s">Available Datasets</a></li>
    %(upload_path)s
  </ul>
  <hr/>
  <ul class="linkMenu">
    %(analysis)s
  </ul>
  <span id="compclustRevision">$Revision: 1429 $</span>
  </div>
""" % menu_parameters

    return mainmenu

  def _q_lookup(self, name):
    try:
      # FIXME: session issue
      datasource = get_session().datamanager[name]
    except KeyError, e:
      raise TraversalError("unrecognized datasource id")
    return DatasourceUI(self, datasource)
      
  def _q_index(self):
    """Show list of datasources
    """
    context = self.create_context()
    browse = [{'dsid': ds[0],
               'dsname': ds[1],
               'labelingcount': len(ds[2]),
               'edit': _path_join(self.root_url, ds[0], ''),
               'save': _path_join(self.root_url, ds[0], '/save'),
               'unload':_path_join(self.root_url, ds[0], '/unload'),
               'delete':_path_join(self.root_url, ds[0], '/delete'),
               'loaded': ds[3],
               'notloaded': not ds[3]}
               # FIXME: session issue
               for ds in get_session().datamanager.browse_list()]
    context.addGlobal('ds_browse', browse)
    context.addGlobal('read_only', self.read_only)
    return  get_template('index', context)

  def uploaddata(self):
    # No hacking in to upload data if we're in read only mode
    if self.read_only:
      return redirect(self.root_url)
    context = self.create_context()
    return get_template('uploaddata', context)
  
  def uploaddata_action(self):
    
    datasourcename = get_field('datasourcename', None)
    datasource = get_field('datasource', None)
    datasourceurl = get_field('datasourceurl', None)
    delimiter = [ x for x in get_field('delimiter', '\t') if x in string.printable ]
    
    # add new datasource
    if datasource is not None:
      # we have a datasource to add.
      ds = DataSource(datasource.fp)
    elif datasourceurl is not None:
      ds = DataSource(str(datasourceurl))
    else:
      ds = None
  
    if ds is None:
      raise PublishError("Unable to load data")
    
    if datasourcename is not None:
      ds.name = str(datasourcename)
    # FIXME: session issue
    get_session().datamanager.append(ds)

    return redirect(_path_join(self.root_url, ds.id))

