########################################
# 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.
########################################
#
#       Authors: Lucas Scharenbroich
#                Christopher Hart
# Last Modified: Dec 13 23:41:29 PST 2001
#

from compClust.mlx.views import BaseView

#############################################################################
#
# Superset classes
#
############################################################################

class SupersetView(BaseView):
  """
  Allows for concatenation of two datasets into a single, large dataset.

  This is a skeleton class which provides generic superset services in terms
  of methods which must be implemented by subclasses.  A Superset view allows
  two datasets to be virtually merged along their columns or rows.  The
  dimension of the non-merged dimension must be equal. i.e. If creating
  a superset of rows, the datasets must have the same number of columns.

  A subclass of SupersetView must implement the methods mapKeyToParent(key)
  and mapKeyFromParent(parent, key).
  """
  
  def __init__(self, dataset1, dataset2, name=None):

    #
    # We are complicated enough that we can't call the BaseView.__init__()
    # function, so we have to do a lot of setup ourselves
    #
    # Note that there is no self.dataset variable.  That's because this view
    # doesn't have one.
    
    self.resetVars()
    self.dirty = 0
    
    #
    # Keep pointers to the two parents
    
    self.ds1 = dataset1
    self.ds2 = dataset2

    self.ds1Rows = dataset1.getNumRows()
    self.ds2Rows = dataset2.getNumRows()

    self.ds1Cols = dataset1.getNumCols()
    self.ds2Cols = dataset2.getNumCols()

    #
    # Add ourselves to the parents' view lists

    dataset1.addView(self)
    dataset2.addView(self)

    #
    # A superset needs more information than a subset
    #
    #   uid2uid:    Maps UIDs from self to the datasets
    #   mapFromDS1: Maps UIDs from the first dataset to self
    #   mapFromDS2: Maps UIDs from the second dataset to self
    #

    self.uid2uid    = {}
    self.mapFromDS1 = {}
    self.mapFromDS2 = {}

    self.counter = 0

    self.name = name

  def detatch(self):
    self.ds1 = None
    self.ds2 = None
    self.uid2uid.clear()
    self.mapFromDS1.clear()
    self.mapFromDS2.clear()


  def getData(self, key=None):
    raise NotImplementedError()


  def getLineage(self):

    #
    # This causes a branch in the lineage.  Need to copy the lineage returned
    # and pre-pend self to all of the branches
    #

    lineage = self.ds1.getLineage()
    for branch in lineage:
      branch.insert(0, self)

    lineage2 = self.ds2.getLineage()
    for branch in lineage2:
      branch.insert(0, self)
      lineage.append(branch)

    return lineage

  def _mapKeysToParent(self, keys, parent):
    raise NotImplementedError()


  def _mapKeysFromParent(self, keyList, parent):
    return map(self._mapKeyFromParent, keyList, [parent] * len(keyList))
  
  def _mapKeyToParent(self, key):
    raise NotImplementedError()


  def _mapKeyFromParent(self, key, parent):
    raise NotImplementedError()


  def _mapUIDToParent(self, uid, parent):

    if parent is self.ds1:
      return self.uid2uid[uid][0]
    else:
      return self.uid2uid[uid][1]


  def _mapUIDFromParent(self, uid, parent):

    if parent is self.ds1:
      return self.mapFromDS1.get(uid,[])
    else:
      return self.mapFromDS2.get(uid,[])      


  def _getKeysByUID(self, uid):

    #
    # get the keys from the parent datasets
    #

    keys1 = self.ds1._getKeysByUID(self._mapUIDToParent(uid, self.ds1))
    keys2 = self.ds2._getKeysByUID(self._mapUIDToParent(uid, self.ds2))

    #
    # scan through and map the keys from the parent to self
    #

    #keys1 = map(lambda x : self._mapKeyFromParent(x, self.ds1), keys1)
    #keys2 = map(lambda x : self._mapKeyFromParent(x, self.ds2), keys2) 
    keys1 = map(self._mapKeyFromParent, keys1, [self.ds1] * len(keys1))
    keys2 = map(self._mapKeyFromParent, keys2, [self.ds2] * len(keys2))
    
    return keys1 + keys2

  
  def _getUIDsByKey(self, key=None):

    if key is None:
      return (self.ds1._getUIDsByKey() + self.ds2._getUIDsByKey())
    else:
      (ds, newKey) = self._mapKeyToParent(key)
      uid = ds._getUIDsByKey(newKey)
      return map(self._mapUIDFromParent, uid, [ds] * len(uid))


  def _removeUID(self, uid, key):

    (ds, newKey) = self._mapKeyToParent(key)
    newUID = self._mapUIDToParent(uid, ds)
    ds._removeUID(newUID, newKey)

      
  def _addUID(self, uid, key):

    (ds, newKey) = self._mapKeyToParent(key)
    newUID = self._mapUIDToParent(uid, ds)
    ds._addUID(newUID, newKey)

    
  def _getUID(self):

    uid1 = self.ds1._getUID()
    uid2 = self.ds2._getUID()
    self.counter += 1

    self.uid2uid[self.counter] = (uid1, uid2)
    self.mapFromDS1[uid1] = self.counter
    self.mapFromDS2[uid2] = self.counter
    
    return self.counter
