'''
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
import brlapi

# 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
  
  @cvar LeftOverlap: Number of cells left unchanged on left during scrolling
  @type ReadBackground: int
  @cvar RightOverlap: Number of cells left unchanged on right during scrolling
  @type ReadBackground: int
  '''
  # user defined settings
  LeftOverlap = 0
  RightOverlap = 0
  
  def getSettings(self):

    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)
    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
  CurrentText = ""
  PrevText = ""
  
  def init(self):
    '''
    Registers L{Task}s to handle focus, caret, selector, state, and 
    property change events. 
    '''
    self.left_slice = 0
    self._scroll_offset = 0
    self.caretpos = -1
    
    # 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('output text'))
    
    # register braille named tasks for input events
    self.registerTask(ScrollForward('scroll forward'))
    self.registerTask(ScrollBackward('scroll backward'))
    
    # 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, 
    #                     [brlapi.KEY_CMD_ROUTE])
                                                     
  def getDescription(self):
    return _('Applies to all applications by default.')
    
  def getLeftSlice(self, style):
    '''
    Get the leftmost slice indice for the current output text
    
    @return: Left slice index
    @rtype: int
    '''  
    #TODO Is this the best way to calculate this value?
    if self.task_por.char_offset>style.TotalCells-1:
      retval = self.task_por.char_offset-style.TotalCells+1
    else:
      retval = 0
    
    if self.scroll_offset > 0:
      retval += self.scroll_offset
    elif self.scroll_offset < 0:
      retval = max(0, retval + self.scroll_offset)
    
    return retval
    
  def setScrollOffset(self, val):
    '''
    Set scroll offset to val.
    
    @param val: Value of scroll offset
    @type val: int
    ''' 
    self.scroll_offset = val
    
  def getScrollOffset(self):
    '''
    Get the scroll offset
    
    @return: scroll offset
    @rtype: int
    '''  
    return self.scroll_offset
  
  def setCaretPosition(self, pos):
    '''
    Set the caret position in CurrentText
    
    @param pos: position of caret in CurrentText
    @type pos: int
    ''' 
    self.caretpos = pos
  
  def getCaretPosition(self):
    '''
    Get the caret position in CurrentText
    
    @return: caret position
    @rtype: int
    '''  
    return self.caretpos
  
  
class ScrollForward(Task.InputTask):
  '''
  Scrolls Braille viewport to right
  '''
  def execute(self, **kwargs):
    style = self.getStyle()
    if self.perk.getLeftSlice(style)+style.TotalCells< len(self.perk.CurrentText):
      newoffset = self.perk.getScrollOffset()+1
      self.perk.setScrollOffset(newoffset)
      self.perk.setCaretPosition(self.perk.getCaretPosition()-1)
      self.doTask("output text")
    
class ScrollBackward(Task.InputTask):
  '''
  Scrolls Braille viewport to left
  '''
  def execute(self, **kwargs):
    style = self.getStyle()
    if self.perk.getLeftSlice(style) > 0:
      newoffset = self.perk.getScrollOffset()-1
      self.perk.setCaretPosition(self.perk.getCaretPosition()+1)
      self.perk.setScrollOffset(newoffset)
      self.doTask("output text")
    
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.setScrollOffset(0)
    self.doTask("output text")
    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.CurrentText = self.getItemText()
    self.perk.setScrollOffset(0)
    # Don't show cursor
    self.perk.setCaretPosition(-1)
    # send out line of text
    self.doTask("output text")
      
  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 execute(self, por, text, text_offset, added, **kwargs):
    '''
    Stores the point of regard to the caret event location. Tries to stop
    current output.
    
    @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 
      (the default) when event is for caret movement only
    @type added: boolean
    '''
    # let the base class call the appropriate method for inset, move, delete
    rv = Task.CaretTask.execute(self, por, text, text_offset, added, **kwargs)
    return rv
    
  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
    self.perk.CurrentText = self.getItemText()
    self.perk.setScrollOffset(0)
    self.perk.setCaretPosition(por.char_offset-self.perk.getLeftSlice(self.getStyle()))
    # send out line of text
    self.doTask("output text") 
    
  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
    self.perk.CurrentText = self.getItemText()
    self.perk.setScrollOffset(0)
    self.perk.setCaretPosition(por.char_offset-self.perk.getLeftSlice(self.getStyle()))
    # send out line of text
    self.doTask("output text")
    
  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
    self.perk.CurrentText = self.getItemText()
    self.perk.setScrollOffset(0)
    self.perk.setCaretPosition(por.char_offset-self.perk.getLeftSlice(self.getStyle()))
    # send out line of text
    self.doTask("output text")

  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 current text
    self.perk.CurrentText = self.getItemText()
    self.perk.setScrollOffset(0)
    self.perk.setCaretPosition(por.char_offset-self.perk.getLeftSlice(self.getStyle()))
    # send out line of text
    self.doTask("output text")
    
 
class OutputText(Task.Task):
  '''
  Output text to braille device
  '''
  def execute(self, **kwargs):
    style = self.getStyle()
    
    leftslice = self.perk.getLeftSlice(style)
    #Note: rightslice >= len(CurrentText) does not throw exception
    rightslice = leftslice+style.TotalCells
    
    # set caret position
    caretpos = self.perk.getCaretPosition()
    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.CurrentText)), talk=False) 
        
    # send text to device
    self.sayItem(text=self.perk.CurrentText[leftslice:rightslice])
    