'''
Test code for refactoring settings.

Issues

- cyclic reference to state, probably need weakref
- strong references to observers, prevents garbage collection without an 
  explicit deletion
- how do we initialize/override base class settings (e.g. IBMSpeech -> AudioStyleDefault)

@author: Peter Parente
@organization: IBM Corporation
@copyright: Copyright (c) 2005, 2006 IBM Corporation
@license: The BSD License

All rights reserved. This program and the accompanying materials are made
available under the terms of the BSD license which accompanies
this distribution, and is available at
U{http://www.opensource.org/licenses/bsd-license.php}
'''

import weakref

class Setting(object):
  def __init__(self, state, name, default, label, description, persist):
    self.state = state
    # problem: cyclic reference with strong ref, probably need weak
    self.name = name
    self.default = default
    self.label = label
    self.description = description
    self.persist = persist
    self._value = default
    self._cached = None
    self._observers = []

  def _getValue(self):
    return self._value
  
  def _setValue(self, val):
    self._value = val
    self._notify()
    
  value = property(_getValue, _setValue)

  def _notify(self):
    for ob in self._observers:
      ob(self.state, self)
    
  def addObserver(self, ob):
    # problem: strong reference to observer, prevent garbage collection without
    # an explicity deletion
    self._observers.append(ob)
    
  def removeObserver(self, ob):
    self._observers.remove(ob)
    
  def clearObservers(self):
    self._observers = []

  def save(self):
    self._cached = self._value
  
  def restore(self):
    if self._cached is not None:
      if self._value != self._cached:
        # this will send notification only if we're not restoring the same
        # value as the current value
        self.value = self._cached
      self.cached = None

class AEState(object):
  def __init__(self):
    # special init to avoid call to __setattr__
    self.__dict__['settings'] = {}
  
  def init(self):
    # problem: how do we initialize/override base class settings
    pass
  
  def newBool(self, name, default, label, description, persist=True):
    self.settings[name] = Setting(self, name, default, label, description, 
                                  persist)
  
  def __setattr__(self, name, val):
    try:
      self.__dict__['settings'][name].value = val
    except KeyError:
      self.__dict__[name] = val
  
  def __getattr__(self, name):
    try:
      return self.__dict__['settings'][name].value
    except KeyError:
      pass
    try:
      return self.__dict__[name]
    except KeyError:
      raise AttributeError("'%s' object has no attribute '%s'" % 
                           (self.__class__.__name__, name))

  def save(self):
    for sett in self.settings.values():
      sett.save()
      
  def restore(self):
    for sett in self.settings.values():
      sett.restore()

  def getGroups(self):
    pass
  
  def getSettingObj(self, name):
    return self.settings[name]
  
  def getSettingVal(self, name):
    return self.settings[name].value
   
  def serialize(self):
    return dict([(name, sett.value) for name, sett in self.settings.items()
                 if sett.persist])
  
  def unserialize(self, data):
    for name, value in data.items():
      try:
        self.settings[name].value = value
      except KeyError:
        # nice! filters out old settings that are no longer defined
        pass

class MyState(AEState):
  def init(self):
    self.newBool('WordEcho', True, 'Echo words?', 'Echo words on output')
    self.newBool('CharEcho', True, 'Echo charss?', 'Echo character on output')
    #self.newString('Blank', 'blank', 'Blank string', 'String to speak in place of blanks')
    #self.newPercent('Volume', 90, 'Volume', 0, 100, 0, 'Speech volume as a percentage')

def myCB(state, sett):
  print sett.name, 'changed to', sett.value

if __name__ == '__main__':
  a = MyState()
  # problem: how do we initialize settings defined in base classes?
  a.init()
  print 'echo default', a.WordEcho
  # add an observer
  sett = a.getSettingObj('WordEcho') 
  sett.addObserver(myCB)
  # save all current values
  a.save()
  # modify a value
  a.WordEcho = False
  print 'after set on echo', a.WordEcho
  # restore all values, only get notification for those that changed
  a.restore()
  print 'after restore to echo', a.WordEcho
  print a.serialize()