'''
This perk provides the basic braille user interface for LSR

@author: <author's name>
@organization: <author's organization (or None)>
@copyright: Copyright (c) <year and copyright holder>
@license: <license name>

All rights reserved. This program and the accompanying materials are made 
available under the terms of the <license name> which is available at
U{<license URL>}
'''
# import useful modules for Perks
import Perk, Task.Tools
from POR import POR
from i18n import bind, _
import AEOutput.Constants as AEOConstants

# metadata describing this Perk
__uie__ = dict(kind='perk', tier=None, all_tiers=True)

# to support translation of strings in this Perk, uncomment the following line
# and provide the proper domain and path to your translation file
# _ = bind(domain, locale_dir)

class BasicBraillePerkState(Perk.PerkState):
  '''
  Settings for BasicBraillePerk
  #TODO: new desc for padding
  @cvar LeftOverlap: Number of cells left unchanged on left during scrolling
  @type LeftOverlap: int
  @cvar RightOverlap: Number of cells left unchanged on right during scrolling
  @type RightOverlap: int
  @cvar LeftPadding: remaining cells on left side during scrolling
  @type LeftPadding: int
  @cvar RightPadding: remaining cells on right side during scrolling
  @type RightPadding: int
  '''
  # user defined settings
  LeftOverlap = 0
  RightOverlap = 0
  LeftPadding = 0
  RightPadding = 0
  SnapOnUpdate = False
  
  def getSettings(self):
   
    #TODO: validate padding and overlap values
    g = self._newGroup()
    e = g.newGroup(_('Overlapping Cells'))
    e.newNumeric('LeftOverlap', _('Overlapping cells on left side'), 0, 5, 0)
    e.newNumeric('RightOverlap', _('Overlapping cells on right side'), 0, 5, 0)
 
    e = g.newGroup(_('LeftPadding'))
    e.newNumeric('LeftPadding', _('Number of remaining cells on left side during scrolling'), 0, 10, 0)
    e.newNumeric('RightPadding', _('Number of remaining cells on right side during scrolling'), 0, 10, 0)
    
    e = g.newGroup(_('Update'))
    e.newBool('SnapOnUpdate', _('Snap on update?'), 
              _('When set, display snaps to original position upon an update after display scrolling.'))
    
    return g
  
class BasicBraillePerk(Perk.Perk):
  '''
  Defines a default braille user interface.
  
  @todo: fill in variable and types
  '''
  # defines the class to use for state information in this Perk
  STATE = BasicBraillePerkState
  
  def init(self):
    '''
    Registers L{Task}s to handle focus, caret, selector, state, and 
    property change events. 
    '''
    self.left_slice = 0
    self.prevleftslice = 0
    self.scroll_offset = 0
    
    self.abscaretpos = -1
    
    self.current_text = ""
    
    
    # set the output device                       
    self.setPerkIdealOutput('braille')
    
    # register braille events handlers
    self.registerTask(HandleSelectorChange('default selector'))
    self.registerTask(HandleCaretChange('default caret'))
    self.registerTask(HandleFocusChange('default focus'))
    
    # register named tasks
    self.registerTask(OutputText('braille output'))
    
    # register braille named tasks for input events
    self.registerTask(ScrollForward('scroll forward'))
    self.registerTask(ScrollBackward('scroll backward'))
    
    self.registerTask(Bogus('bogus method'))
    
    # register keyboard command listeners
    kbd = self.getInputDevice('Keyboard')
    self.addInputModifiers(kbd, kbd.AEK_CAPS_LOCK)
    self.registerCommand(kbd, 'scroll forward', True, 
                         [kbd.AEK_CAPS_LOCK, kbd.AEK_SHIFT_R])
    self.registerCommand(kbd, 'scroll backward', True, 
                         [kbd.AEK_CAPS_LOCK, kbd.AEK_SHIFT_L])
                         
    #register braille device commands
    brlindev = self.getInputDevice('BrlAPIDevice')
    self.registerCommand(brlindev, 'bogus method', True, 
                         [brlindev.KEY_CMD_ROUTE])
                                                     
  def getDescription(self):
    return _('Basic Braille Perk manages basic braille output')
  
  def setAbsCaretPos(self, pos):
    if pos >= -1 or pos < len(self.current_text):
      self.abscaretpos = pos
      
  def getRelCaretPos(self):
    style = self.getStyle()
    if style is None:
      return
    # -1 (0 on device) is special case where we don't show cursor
    if self.abscaretpos == -1:
      return -1
    else:
      return max(-1, self.abscaretpos - self.prevleftslice - self.scroll_offset) 
      
  def getLeftSlice(self, totalcells):
    '''
    Get the leftmost slice indice for the current braille output
    
    @return: Left slice index
    @rtype: int
    '''  
    state = self.getState()
    if self.task_por.char_offset-self.prevleftslice < state.LeftPadding:
      retval = max(0,self.task_por.char_offset-state.LeftPadding)
    elif self.task_por.char_offset-self.prevleftslice >= totalcells-state.RightPadding:
      retval = self.task_por.char_offset-totalcells+state.RightPadding+1
    else:
      retval = self.prevleftslice
    
    # set before applying scroll_offset
    self.prevleftslice = retval
    
    # apply scroll offset
    if self.scroll_offset > 0:
      retval += self.scroll_offset
    elif self.scroll_offset < 0:
      retval = max(0, retval + self.scroll_offset)
      
    return retval
    
  
class Bogus(Task.InputTask):
  '''
  Scrolls Braille viewport to right
  '''
  def execute(self, **kwargs):
    print "got bogus"
  
class ScrollForward(Task.InputTask):
  '''
  Scrolls Braille viewport to right
  '''
  def execute(self, **kwargs):
    style=self.getStyle()
    if style is None:
      return
    state=self.perk.getState()
    if self.perk.prevleftslice + self.perk.scroll_offset < \
          len(self.perk.current_text)-style.TotalCells+state.RightOverlap:
      self.perk.scroll_offset += 1
    self.doTask("braille output")
    
class ScrollBackward(Task.InputTask):
  '''
  Scrolls Braille viewport to left
  '''
  def execute(self, **kwargs):
    if self.perk.prevleftslice + self.perk.scroll_offset > 0:
      self.perk.scroll_offset -= 1
    self.doTask("braille output")
    
class HandleFocusChange(Task.FocusTask):
  '''  
  Task that handles a L{AEEvent.FocusChange}.
  '''  
  def executeGained(self, por, **kwargs):
    '''
    TODO: description
    
    @param por: Point of regard for the new focus
    @type por: L{POR}
    '''
    self.perk.scroll_offset = 0
    self.doTask("braille output")
    return True

class HandleSelectorChange(Task.SelectorTask):
  ''' 
  Task that handles a L{AEEvent.SelectorChange}.
  '''
  def executeActive(self, por, text, **kwargs):
    '''
    Announces the text of the active item.
    
    @param por: Point of regard where the selection event occurred
    @type por: L{POR}
    @param text: The text of the active item
    @type text: string
    '''  
    # update current text
    self.perk.current_text = self.getItemText()
    self.perk.scroll_offset = 0
    # Don't show cursor
    self.perk.setAbsCaretPos(-1)
    # send out line of text
    self.doTask("braille output")
      
  def executeText(self, por, text, **kwargs):
    '''
    Announces the localized word selected or unselected followed by the total 
    number of characters in the selection.
    
    @param por: Point of regard where the selection event occurred
    @type por: L{POR}
    @param text: The currently selected text
    @type text: string
    '''
    pass  
    
class HandleCaretChange(Task.CaretTask):
  '''  
  Task that handles a L{AEEvent.CaretChange}. The L{execute} method performs
  actions that should be done regardless of whether text was inserted or
  deleted or the caret moved. The other three methods handle these more
  specific cases.
  
  @ivar changed: Tracks whether the last caret move event was a caused by a
    change (insert/delete) or just navigation
  @type changed: boolean
  '''
  def init(self):
    pass
    
  def executeInserted(self, por, text, text_offset, **kwargs):
    '''
    Announces the inserted text.
    
    @param por: Point of regard where the caret event occurred
    @type por: L{POR}
    @param text: The text inserted
    @type text: string
    @param text_offset: The offset of the inserted text
    @type text_offset: integer
    '''
    # update current text
    style = self.getStyle()
    if style is None:
      return
    self.perk.current_text = self.getItemText()
    self.perk.scroll_offset = 0
    self.perk.setAbsCaretPos(por.char_offset)
    # send out line of text
    self.doTask("braille output") 
    
  def executeMoved(self, por, text, text_offset, **kwargs):
    '''
    Announces the text passed in the caret movement.
    
    @param por: Point of regard where the caret event occurred
    @type por: L{POR}
    @param text: The text passed during the move
    @type text: string
    @param text_offset: The offset of the new caret position
    @type text_offset: integer
    '''
    # update current text
    style = self.getStyle()
    if style is None:
      return
    self.perk.current_text = self.getItemText()
    self.perk.scroll_offset = 0
    self.perk.setAbsCaretPos(por.char_offset)
    # send out line of text
    self.doTask("braille output")
    
  def executeDeleted(self, por, text, text_offset, **kwargs):
    '''
    Announces the deleted text. Prepends delete and backspace text to indicate
    which way the caret moved.
    
    @param por: Point of regard where the caret event occurred
    @type por: L{POR}
    @param text: The text delete
    @type text: string
    @param text_offset: The offset of the delete text
    @type text_offset: integer
    '''
    # update current text
    style = self.getStyle()
    if style is None:
      return
    self.perk.current_text = self.getItemText()
    self.perk.scroll_offset = 0
    self.perk.setAbsCaretPos(por.char_offset)
    # send out line of text
    self.doTask("braille output")

  def update(self, por, text, text_offset, added, **kwargs):
    '''
    Updates the L{DefaultPerk}.last_caret with the current L{por}.
    
    @param por: Point of regard where the caret event occurred
    @type por: L{POR}
    @param text: The text inserted, deleted or the line of the caret
    @type text: string
    @param text_offset: The offset of the inserted/deleted text or the line 
      offset when movement only
    @type text_offset: integer
    @param added: True when text added, False when text deleted, and None 
      when event is for caret movement only
    @type added: boolean
    '''
    # update all state variables 
    style.getStyle()
    if style is None:
      return
    self.perk.current_text = self.getItemText()
    self.perk.scroll_offset = 0
    self.perk.setAbsCaretPos(por.char_offset)
   
 
class OutputText(Task.Task):
  '''
  Output text to braille device
  '''
  def execute(self, **kwargs):
    style = self.getStyle()
    if style is None:
      return
    
    leftslice = self.perk.getLeftSlice(style.TotalCells)
    #Note: rightslice >= len(current_text) does not throw exception
    rightslice = leftslice+style.TotalCells
  
    # set caret position. 
    caretpos = self.perk.getRelCaretPos()
    if caretpos < 0 or caretpos >= style.TotalCells:
      self.send(AEOConstants.CMD_CARET, -1, talk=False)
    else:
      self.send(AEOConstants.CMD_CARET, caretpos, talk=False)
      
    # truncation indicators for ellipsis
    self.send(AEOConstants.CMD_TRUNCATE, \
        (leftslice>0, rightslice<len(self.perk.current_text)), talk=False) 
        
    # send text to device
    self.sayItem(text=self.perk.current_text[leftslice:rightslice])
    