########################################
# The contents of this file are subject to the MLX PUBLIC LICENSE version
# 1.0 (the "License"); you may not use this file except in
# compliance with the License.
# 
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
# the License for the specific language governing rights and limitations
# under the License.
# 
# The Original Source Code is "compClust", released 2003 September 03.
# 
# The Original Source Code was developed by the California Institute of
# Technology (Caltech).  Portions created by Caltech are Copyright (C)
# 2002-2003 California Institute of Technology. All Rights Reserved.
########################################

"""
Provides a base from which all other IML_Algorithm classes can be derived.
"""

from compClust.mlx.datasets import Dataset

import sys
import warnings
import types

from compClust.mlx.interfaces import IML_Algorithm
from compClust.util.WrapperParameters import WrapperParameters

def deprecated(message):
  warnings.warn(message, DeprecationWarning, 2)


class ML_Algorithm(IML_Algorithm):

  """
  Provides a skeletal implementation of the IML_ALgorithm interface.

  Most classes derived from ML_Algorithm will probably override most of
  these methods.  This class primarily here to ensure that all the
  methods of the interface class are implmented.
  """

  def __init__(self, dataset=None, parameters=None):
    self.dataset        = Dataset(dataset)
    self.parameters     = parameters
    self.model          = None
    self.labeling       = None
    self.MESSAGE_STREAM = sys.stdout

# I don't think this does anything useful. 2005-may-26
# it was used by the WrapperParametersDescription stuff but 
#   def cleanParameters(self, parameters):
#     if parameters is None:
#       return parameters
#     elif type(parameters) == types.DictType:
# 
#       keyList = parameters.keys()
# 
#       if len(keyList) == 0:
#         return parameters
# 
#       newParams = {}
#       for key in keyList:
#         newParams[key] = parameters[key].value
# 
#       return newParams
              
  
  def clear(self):
    if self.dataset is not None:
      self.dataset.resetVars()
      self.dataset = None

    if isinstance(self.parameters, WrapperParameters):
      self.parameters.resetParameters()
    else:
      self.parameters = None
    self.labeling   = None
    self.model      = None
    
  def run(self, stream=sys.stdout):
    """
    Runs (executes) the algorithm on the current dataset with the parameters
    passed to it. validate() should always be called prior to running an
    algorithm.
    """

    raise NotImplementedError()


  def validate(self, stream=sys.stdout):
    """
    Verifies that files have correct permissions and that all the needed
    parameters are set and have valid values.  True will be retuned if
    these conditions are satisfied, false if not.
    """
    
    return 1


  def getDataset(self):
    """
    Returns a reference to the dataset object on which the algorithm will
    operate.
    """
    return self.dataset


  def getTransformedDataset(self, dataset):
    """
    Takes the data in dataset and does a "dry-run" of the algorithm to
    produce a normalized dataset.  If the algorithms does not need to
    normalize the data it may return None.

    This function is usefule for algorithms which performs some sort of
    normalization on the dataset prior to running.  We need a copy of this
    transformed data so that we can properly score training results.
    """

    return None


  def setDataset(self, dataset):
    """
    Set the dataset for the algorithm.  dataset must be a valid instance of an
    IDataset object.
    """
    
    self.dataset = dataset

  def getLabeling(self):
    """
    Returns the instance of a labeling object which contains one label per
    datum.  These labels define a hard partitioning of the dataset into
    distinct classes.
    """
    return self.labeling

  def hasLabeling(self):
    """
    Return true if we have a labeling attached
    """
        
    if not hasattr(self, 'labeling') or self.labeling is None:
      return False
    else:
      return True
      
  def getModel(self):
    """
    Returns an instance of a Model created by the algorithm to describe
    its Labeling and Dataset.  This model can then be used to evaluate the
    fitness of the algorithm run.
    """
    
    return self.model


  def setModel(self, model):
    """
    Forces the algorithm to have a specific model instead of a generated one.
    Most useful for resetting the model and forcing it to be recomputed.
    """

    self.model = model

  def getName(self):
    """Returns the name of the algorithm
    """
    return self.__class__.__name__
  name = property(getName,doc=getName.__doc__)
  
  def getParameters(self):
    """
    Returns a reference to the parameters dictionary.
    """
    
    return self.parameters


  def setParameters(self, parameters):
    """
    If given a dictionary, set it as the new parameters for the algorithm.
    """
    
    if parameters is None:
      self.parameters.resetParameters()
    else:
      self.parameters.setParametersDictionary(parameters)
  
  
  def setMessageStream(self, stream):
    """
    Redirect output information from the algorithm.  Used for logging
    operations.
    """
    
    self.MESSAGE_STREAM = stream


  def getMessageStream(self):
    """
    Return the stream to which the algorithm will output status information
    """
    
    return self.MESSAGE_STREAM

class ML_Composable_Algorithm(ML_Algorithm):
  """Base class indicating that an algorithm operates on sub algorithms
  """
  def __init__(self, dataset=None, parameters=None):
    super(ML_Composable_Algorithm, self).__init__(dataset, parameters)
    self.__sub_parameters = {}
    
  def _setSubParameter(self, name, value):
    """Set sub parameter in the sub parameter dictionary
    
    sub parameters are a way for a higher ML_Composable_Algorithm to set parameters in
    the algorithm which will be run, through all of the various other Composable_Algorithms that 
    are also wrapping it
    """
    self._sub_parameters[name] = value
    
  def _getSubParameter(self, name):
    """Return a particular sub parameter from the sub parameter dictionary
    
    sub parameters are a way for a higher ML_Composable_Algorithm to set parameters in
    the algorithm which will be run, through all of the various other Composable_Algorithms that 
    are also wrapping it
    """
    return self._sub_parameters[name]
    
  def _delSubParameter(self, name):
    """delete a particular sub parameter from the sub parameter dictionary
    
    sub parameters are a way for a higher ML_Composable_Algorithm to set parameters in
    the algorithm which will be run, through all of the various other Composable_Algorithms that 
    are also wrapping it    
    """
    del self._sub_parameters[name]

  def hasLabeling(self):
    """
    Return true if a labeling has been generated by this wrapper
    """
    if self.algorithm is None:
      return False
    else:
      return self.algorithm.hasLabeling()