import os
import string
import thread
import traceback

from quixote import get_field, get_response, redirect
from quixote.directory import Directory
from quixote.errors import QueryError

from compClust.mlx.ML_Algorithm import ML_Composable_Algorithm
from compClust.util.WrapperBuilder import WrapperBuilder
from compClust.util.WrapperPlugins import WrapperPlugins
from compClust.util import WrapperParameters
from compClust.util.WrapperParameters import HTMLFormatParameter

from compClust.gui.LabelingSource import LabelingSource
from compClust.gui.TemplateUtils import template_header, template_footer,  \
                                        get_template, _path_join

CLUSTERING_MISSING = 0
CLUSTERING_RUNNING = 1
CLUSTERING_FINISHED = 2
CLUSTERING_FAILED = 3

clustering_status_lock = thread.allocate()
clustering_status = {}

def clustering_thread(name, wrapper_builder, datasource):
  # FIXME: perhaps we should track which jobs a created and delete them from
  # a queue when done
  threadid = str(thread.get_ident())
  clustering_status_lock.acquire()
  clustering_status[threadid] = CLUSTERING_RUNNING
  clustering_status_lock.release()
  print "starting run", name
  try:
    wrapper_builder.run()
    labeling = wrapper_builder.getLabeling()
    if labeling is None:
      print "error running clustering"
      clustering_status_lock.acquire()
      clustering_status[threadid] = CLUSTERING_FAILED
      clustering_status_lock.release()
    else:
      labeling.setName(name)
      print "finished run", name
      source = LabelingSource(name, isrow=True, isannotation=False)
      clustering_status_lock.acquire()
      datasource._labeling_sources[name] = source
      clustering_status[threadid] = CLUSTERING_FINISHED
      clustering_status_lock.release()
  except Exception, e:
    clustering_status_lock.acquire()
    clustering_status[threadid] = CLUSTERING_FAILED
    clustering_status_lock.release()
    print traceback.print_exc()

wrapper_plugins = WrapperPlugins()
  
    
class ClusteringUI(Directory):
  """UI for a particular clustering algorithm
  """
  _q_exports = ['', 'action', 'status', 'subalgorithm']
  def __init__(self, datamanagerui, datasource, baseurl, wrapper_builder):
    self.datamanagerui = datamanagerui
    self.datasource = datasource
    self.wrapper_builder = wrapper_builder
    self.baseurl = os.path.join(baseurl, self.wrapper_builder[-1].name)
    
  def nameByClusteringParameters(self, parameters):
    """Create a name based on clustering parameters
    """
    labeling_parameters = ''
    for prop in parameters.getProperties().values():
      if prop.priority == WrapperParameters.Priority.REQUIRED:
        labeling_parameters+= ' %s=%s' % (prop.name[:3], parameters[prop.name])
    return labeling_parameters

  def action(self):
    """Execute results of Clustering Form submission
    """
    #wrapper_builder = self.__subalgorithm_list_parse()
    # get & validate variables
    for i in xrange(len(self.wrapper_builder)):
      algorithm = self.wrapper_builder[i]
      for name, prop in algorithm.parameters.getProperties().items():
        variable_name = self.__format_name(name, i)
        value = get_field(variable_name, algorithm.parameters[name])
        # Convert human lists into python lists for ListProperties
        if isinstance(prop, WrapperParameters.ListProperty):
          value = value.split()
        try:
          algorithm.parameters[name] = value
        except ValueError, e:
          raise QueryError(str(e))
      
    # come up with our name
    clustering_name = get_field('clusteringname',
                                self.wrapper_builder[0].name)
    append_parameters = get_field('appendparameters', False)
    if append_parameters is not False:
      clustering_name += self.nameByClusteringParameters(self.wrapper_builder[0].parameters)
    # is there a labeling with that name?
    if not self.datasource.has_labeling_by_name(clustering_name):
      raise QueryError("Clusterings must be unique")
    
    # make dictionary for clustering algorithm
    if not self.wrapper_builder.validate():
      raise QueryError("Parameters failed validation")

    # run algorithm
    threadid = thread.start_new_thread(clustering_thread, (clustering_name, self.wrapper_builder, self.datasource))
    return redirect(os.path.join(self.baseurl, 'status?jobid=%s' % (threadid)))

  def status(self):
    threadid = get_field('jobid', None)
    
    # tell the user something happened
    html = [template_header(self.datamanagerui)]
    html += [self.datamanagerui.menu(self.datasource)]
    html += ['<div id="compclustBody">']

    try:
      clustering_status_lock.acquire()
      if threadid is None or not clustering_status.has_key(threadid):
        html += ["<p>bad job identifier</p>"]
      elif clustering_status[threadid] == CLUSTERING_RUNNING:
        html += ["<p>running job %s</p>" % (threadid)]
        html += ["<p>This page will automatically reload every 15 seconds to check to see if the job is done.</p>"]
        get_response().set_header('refresh', '15')
      elif clustering_status[threadid] == CLUSTERING_FINISHED:
        html += ["<p>job %s finished successfully</p>" % (threadid)]
      elif clustering_status[threadid] == CLUSTERING_FAILED:
        html += ["<p>job %s finished successfully</p>" % (threadid)]
    finally:
      clustering_status_lock.release()
    
    html += ['</div>']
    html += [template_footer()]
    
    return string.join(html, os.linesep)

  def __format_name(self, name, id=None):
    """Format a parameter name in a way that seperates all the various
    parameters for each algorithm in a wrapper_builder chain.
    """
    if id is None:
      return name
    else:
      return name+"."+str(id)
  
  def __format_parameters(self):
    """
    Construct all the gory xHTML necessary to prompt for the required
    and optional clustering algorithm paramters for each algorithm
    that was added to the wrapper builder.
    """
    algorithms_html = []
    for i in xrange(len(self.wrapper_builder)):
      algorithm = self.wrapper_builder[i]
      required = []
      optional = []
      for name, prop in algorithm.parameters.getProperties().items():
        munged_name = self.__format_name(name, i)
        value = algorithm.parameters[name]
        if prop.priority == WrapperParameters.Priority.REQUIRED:
          required += HTMLFormatParameter(name, prop, value, munged_name)
        elif prop.priority == WrapperParameters.Priority.OPTIONAL:
          optional += HTMLFormatParameter(name, prop, value, munged_name)
      algorithms_html.append({'clusteringname': algorithm.name,
                              'required': required,
                              'optional': optional})
    return algorithms_html

  def _q_lookup(self, name):
    self.wrapper_builder.append(wrapper_plugins[name])
    return ClusteringUI(self.datamanagerui, self.datasource, self.baseurl, wrapper_plugins, self.wrapper_builder)
  
  def _q_index(self):
    context = self.datamanagerui.create_context(self.datasource)
    # prompt for parameters
    context.addGlobal("algorithms_parameters", self.__format_parameters())
    # submit to the action function which runs the algorithm
    context.addGlobal('clusteringname', self.wrapper_builder[0].name)
    return get_template('Clustering', context)

class ClusteringManagerUI(Directory):
  """Allow browsing clustering algorithms that are installed.
  """
  _q_exports = ['']
  def __init__(self, datamanagerui, datasource, baseurl, wrapper_builder=None):
    self.datamanagerui = datamanagerui
    self.datasource = datasource
    if wrapper_builder is None:
      self.baseurl = os.path.join(baseurl, 'cluster')
      self.wrapper_builder = WrapperBuilder(self.datasource.dataset)
    else:
      self.baseurl = os.path.join(baseurl, wrapper_builder[-1].name)
      self.wrapper_builder = wrapper_builder

  def _q_lookup(self, name):
    self.wrapper_builder.append(wrapper_plugins[name])
    print self.wrapper_builder.algorithm_names()
    if self.wrapper_builder.isFinalized():
      return ClusteringUI(self.datamanagerui, self.datasource, self.baseurl, self.wrapper_builder)
    else:
      return ClusteringManagerUI(self.datamanagerui, self.datasource, self.baseurl, self.wrapper_builder)
  
  def _q_index(self):
    context = self.datamanagerui.create_context(self.datasource)
    subalgorithm_names = []
    subalgorithm_names.append(string.join(self.wrapper_builder.algorithm_names,
                                          " : "))
    subalgorithms = []
    for alg_name in wrapper_plugins.keys():
      subalgorithms.append((os.path.join(self.baseurl, alg_name), alg_name))
    context.addGlobal('subalgorithms', subalgorithms)
    # submit back to the index page
    context.addGlobal('action', '')
    context.addGlobal('clusteringname', self.wrapper_builder[0].name)
    return get_template('ClusteringBuilder', context)

  def _q_index(self):
    context = self.datamanagerui.create_context(self.datasource)
    # build list of names of algorithms in the wrapper builder
    if len(self.wrapper_builder) > 0:
      context.addGlobal('subalgorithm_names',
                     string.join(self.wrapper_builder.algorithm_names()," : "))
    # build list of algorithms that can be added to the chain
    simple_algorithms = []
    composable_algorithms = []
    for name in wrapper_plugins.keys():
      if issubclass(wrapper_plugins[name], ML_Composable_Algorithm):
        composable_algorithms.append({'name': name,
                                      'href': os.path.join(self.baseurl, name),
                                      'composable':  "Yes"})
      else:
        simple_algorithms.append({'name': name,
                                  'href': os.path.join(self.baseurl, name),
                                  'composable':  "No"})
    context.addGlobal("composable_algorithms", composable_algorithms)
    context.addGlobal("simple_algorithms", simple_algorithms)      
    return get_template('ClusteringManager', context)
  
