#!/usr/bin/env python
import paraview.servermanager
import zipfile
import os
import cPickle as pickle
class Presentation(object):
  '''Allows the user to save states, which is all the display properties for each Pipeline object and camera. After saving
  states you can than load them back up, reverting all the object and camera properties to what they where
  on saving. The set of views can also be saved to a file by using pickle so that you can build up multiple viewStates
  and than display them at a latter time

  Currently Supports:
    saving a state by calling
      addView(statename) statename can be numeric / string

    Loading a View:
      loadView(statename) will load the respective state. If nothing changes
      click on the render window of paraview, that should fix the render display.

    Extra Commands:
      deleteView(name), will delete the view
      deleteAllViews, will clear the view list
      renameView(oldName, newName), renames the view currently called oldName, to newName
      containsView(name), Will tell you if the name is already being used by a view
      listViews(), Will list all the names of the views currently made
      debugView(name), Will dump the properties of the view, for testing

    Pickle Saving all Presentation:
      savePresentation(filepath+filename) will pickle all the Presentation so that you can load them up at a different time

      loadPresentation(filepath+filename) will unpickle all the states so that you can run previously generated states

    Bug:
      Currently items added after a view is stored are not turned off/on when loading view that do not effect
      that item. This may or may not be helpfull to you


  '''
  def __init__(self,StereoMode=1):
    #storage for a list of view objects    
    self.StereoMode = StereoMode
    self.view = list()
    self.proxies = {1:'representations',2:'sources',3:'views'}
    
    self.OriginalPipelineState = self.__buildSourceNames()

  def loadPresentation(self,file):
    '''loads a pickle view file, the passed in value needs
    to be a string that is the path to the pickled view'''
    
    try:
      package = zipfile.ZipFile(file,'r')   
    except:
      f = open(file, 'rb')
      self.view = pickle.load(f)
    else:
      try:
        view =  package.read('pickle')            
        self.view = pickle.loads(view)
        self.OriginalPipelineState = self.__buildSourceNames()
      except IOError:
        print "file not found"
  
  def savePresentation(self,file):
    '''save the current view collection as a ascii pickle'''
    #use zip file to make a nice zip of the state and pickle      
    try:    
      #make the zip package      
      package = zipfile.ZipFile(file,'w')            
      package.writestr('pickle', pickle.dumps(self.view))    
      package.close()
            
    except IOError:
      print "file path does not exists, please check spelling"
      
                    

  def deleteView(self,name):
    '''deletes the first occurance of a name from the view list'''
    if self.containsView(name):      
      for item in self.view:
        if (item.name == name):
          self.view.remove(item)
      self.deleteView(name)
      return True
    else:
      return False

  def deleteAllViews(self):
    '''deletes all views from the list'''
    del self.view
    self.view = list()

  def renameView(self,oldName, newName):
    if (self.containsView(newName)):
      print 'Cannot rename a view to one that already exists.'
      return
    elif(self.containsView(oldName) ):
      '''rename a view with a new name'''
      for item in self.view:
        if (item.name == oldName):
          item.name = newName
      return True
    else:
      print oldName, ' view does not exist.'
      return None

  def containsView(self,name):
    '''checks to see if a view with the name already exists'''
    for view in self.view:
      if (view.name == name):
        return True
    return False    


  def listViews(self):
    '''lists all the views sorted by name'''
    viewNames = list()
    for item in self.view:
      if ( (item.name in viewNames)==False):
        viewNames.append(item.name)
    #collect all the views and than sort them
    viewNames.sort()
    #display all the names
    for name in viewNames:
      print name      

  
  def loadView(self,name):
    '''loads a select view that has already be saved.
       ex: state.loadView(1)'''
    for v in self.view:
      if (v.name == name):
        self.__load(v)

  def addView(self,name):
    '''adds the current state of all items in paraview + cameras as a view.
    loading this view will restore all the saved items to the current state
    ex: state.addView(1)'''
   
    self.deleteView(name) #delete the view if it already exists
    newView = view(name, self.__constructProxyGroups( ) )
    self.view.append(newView)


  def __load(self, v):
    #load the current view object
    print self.proxies[1]
    print v.proxyGroup
    reps = v.GetProxyGroup( self.proxies[1] )
    sources = v.GetProxyGroup( self.proxies[2] )
    cameras = v.GetProxyGroup( self.proxies[3] )
    
    #need to get all the names back
    iter = self.__getProxiesInGroup( self.proxies[1])     
    for proxy in iter:
      iter2 = self.__getProxiesInGroup( self.proxies[2] )      
      for proxy2 in iter2:
        
        if (proxy.Input.GetData().Proxy == proxy2):
          name = iter2.GetKey()          
          if (name in reps):          
            self.__reconstruct( proxy, reps.GetData( name )  )
          
          if (name in sources):
            self.__reconstruct( proxy2, sources.GetData( name )  )                                          
          break
        
    #now we have to worry about camera controls
    iter = self.__getProxiesInGroup( self.proxies[3] )
    for cam in iter:
      name = iter.GetKey()
      if ( name in cameras ):
        self.__reconstruct( cam, cameras.GetData( name ) )
        try:
          cam.StillRender()
        except:
          pass
      
    
  def __reconstruct(self,proxy, propertyList):
    #sets all the properties back to their stored values
    for items in propertyList:
      property = items[0]
      value = items[1]
      if "StereoRender" == property:
        #need to force the stereo to the user defined
        proxy.SetPropertyWithName(property, self.StereoMode)
      elif "ViewSize" == property:
        #need to allways maximize the view size, so ignore this property
        pass
      else:
        proxy.SetPropertyWithName(property, value)
    proxy.UpdateVTKObjects()

  def __constructProxyGroups(self):
    #get all the data needed in a view here
    proxyGroup = {}
    for proxyKey in self.proxies:
      object = DataStore()
      proxy = self.__getProxiesInGroup( self.proxies[proxyKey] )            
      if (proxyKey == 1):                
        for item in proxy:
          iter2 = self.__getProxiesInGroup( self.proxies[2] )      
          for proxy2 in iter2:        
            if (item.Input.GetData().Proxy == proxy2):
              name = iter2.GetKey()          
              object.SetData(name ,self.__getPropertyValues( item )) 
      else:
        for item in proxy:
          object.SetData(proxy.GetKey() ,self.__getPropertyValues(item))
      
      proxyGroup[ self.proxies[proxyKey] ]=object
    return proxyGroup

  def __getPropertyValues(self,proxy):
    properties = list()
    for property in proxy:
      name = property.Proxy.GetPropertyName(property.SMProperty)
      try:
        value = property.GetData()
        float(value)
      except (IndexError,TypeError,ValueError,AttributeError,SyntaxError):
        #we can only pickle floats,and ints. So we need a way
        #to make sure we dont save any paraview reference. This would change
        #if the servermanager became pickleable
        try:
          float(value[0])
        except  (IndexError,TypeError,ValueError,AttributeError,SyntaxError):
          pass
        else:
          properties.append( (name,value) )
      else:
        properties.append( (name,value) )

    return properties
  
  def __buildSourceNames(self):
    #builds all the source names for each display, by using the
    #dump tool procyLabel method
    sourceNames = list()
    iter = self.__getProxiesInGroup(self.proxies[2])
    
    for proxy in iter:
      sourceNames.append(iter.GetKey())          
    return sourceNames

  def __getProxiesInGroup(self,name):
    pxm = paraview.servermanager.ProxyManager()
    iter = pxm.NewGroupIterator(name)
    return iter


class view(object):
  #merely a storage container of view properties
  def __init__(self, name, pg):
    self.name = name
    self.proxyGroup = pg
    
  def GetProxyGroup( self, name ):
    return self.proxyGroup[ name ]
    
  def __getstate__(self):
    #custom pickle data, so that all the items are stored on pickling
    pickleDict = dict()
    pickleDict['proxyGroup']=self.proxyGroup
    pickleDict['name']=self.name
    return pickleDict

  def __setstate__(self,dict):
    #restore the items and view name when unpickling
    self.proxyGroup = dict['proxyGroup']
    self.name = dict['name']

class DataStore(object):
  def __init__(self):
    self.__data = dict()
    
  def __contains__(self, name):
    return ( name in self.__data )
  
  def GetData(self, name ):
    return self.__data[name]
  
  def SetData(self, name, data):
    self.__data[name]=data
    

pv = Presentation()
pv.addView(1)
pv.loadView(1)
