# -*- coding: utf-8 -*-
# vim: ts=4:sw=4
#    This file is part of LOME.
#
#    LOME is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    LOME is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with LOME.  If not, see <http://www.gnu.org/licenses/>.
# 

import pythoncom
import sys
__pyhook_loaded = False
__pyhook_msg = []
try:
    import pyHook
    __pyhook_loaded = True
except:
    import traceback
    exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
    msg_a = traceback.format_exception(exceptionType, exceptionValue, exceptionTraceback)
    msg = ""
    for m in msg_a:
        msg += m
    __pyhook_msg.append( msg )
try:
    import PyHook as pyHook
    __pyhook_loaded = True
except:
    import traceback
    exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
    msg_a = traceback.format_exception(exceptionType, exceptionValue, exceptionTraceback)
    msg = ""
    for m in msg_a:
        msg += m
    __pyhook_msg.append( msg )

if not __pyhook_loaded:
    print "CANNOT LOAD PYHOOK"
    for m in __pyhook_msg:
        print m
import ctypes
import win32com.client 

from pgpluginclient   import *
from szobjects        import *
  
#----------------------------------------------------------------
# Declaration of common variables to PGInput and static def
pgInputInstance          = None     #- Instance of the plugin
mouseEventClientCallback = None     #- Client call back used by pyHook for mouse events
keyPressed               = []       #- Table which carries the keys pressed

#----------------------------------------------------------------
## Call back invoked on keyboard event
# @param event            event object
def onKeyBoardEvent( event):
  # Process this event
  key = event.ScanCode
  if event.IsTransition():
    if key in keyPressed:
      keyPressed.remove( key)
      pgInputInstance.emit( SIGNAL("keyReleased"), keyPressed)
  else:
#    print '----------------------------------'
#    print 'Ascii      :', event.Ascii, chr(event.Ascii)
#    print 'Key        :', event.Key
#    print 'KeyID      :', event.KeyID
#    print 'ScanCode   :', event.ScanCode
#    print 'Extended   :', event.Extended
#    print 'Injected   :', event.Injected
#    print 'Alt        :', event.Alt
    if not ( key in keyPressed):
      keyPressed.append( key)
      pgInputInstance.addKeyAssociation( key, event.Key)
      pgInputInstance.emit( SIGNAL("keyPressed"), keyPressed)
      
  return True
#----------------------------------------------------------------
## Call back invoked on mouse event
# @param event    event object
#                 event.MessageName -> "mouse move"                             --> associated with event.Position
#                                      "mouse left up"   / "mouse left down"
#                                      "mouse middle up" / "mouse middle down" 
#                                      "mouse rigth up"  / "mouse rigth down"
#                                      "mouse wheel"
#                                      "wheel"                                  --> associated with event.Wheel
#                 event.Position    -> (x,y)
#                 event.Wheel       -> +1 / -1
def onMouseEvent( event):
  # Get instance of PGInput
  self = pgInputInstance
  
  # We do not intercept mouseMove events
  if ( event.MessageName == "mouse move") or ( not self):
    return True

  # Get mouse position
  xMouse = event.Position[0]
  yMouse = event.Position[1]

  # Check if there are some client to notify
  returnedValue = True
  plugId        = []
  for srcPludId, datas in self.requests[PGInput.MOUSE_EVENT_ID].iteritems():
    if ( xMouse >= datas[PGInput.X_INDEX]                              ) and\
       ( xMouse <= datas[PGInput.X_INDEX] + datas[PGInput.WIDTH_INDEX] ) and\
       ( yMouse >= datas[PGInput.Y_INDEX]                              ) and\
       ( yMouse <= datas[PGInput.Y_INDEX] + datas[PGInput.HEIGHT_INDEX]) :
      plugId.append( srcPludId)
      
      # Do not forward the event
      returnedValue = False
  
  # Send data
  if not returnedValue:
    # Build object and send it
    szObject = SzMouseEvent( from_id  = self.get_name()
                           , data_id  = self.build_full_itemId( PGInput.MOUSE_EVENT_ID)
                           , event    = event.MessageName
                           , position = event.Position
                           , wheel    = event.Wheel
                           )
    self.send_object( plugId, PGPluginClient.OPCODE_DATA_VALUE, szObject) 
  
  return returnedValue
 

  
#================================================================
## This class subclasses the QStandardItem to provide item for tableView
class CmdxxxItem( QStandardItem):
  def __init__( self, name):
    QStandardItem.__init__( self, name)
    
#================================================================
## This class is used to gather data associated to a command
class CmdItem():
  def __init__( self, cmdId):
    self.__cmdId__  = cmdId
    self.__name__   = cst_get_last_part_of_address( cmdId)
    self.clearCombo()
    
  def getId( self):
    return self.__cmdId__
    
  def getNameItem( self):
    name = CmdxxxItem( self.__name__)
    name.setEditable( False)
    return name
  def getComboItem( self):
    keys = CmdxxxItem( self.__comboText__)
    keys.setEditable( False)
    return keys
    
  def clearCombo( self):
    self.__combo__       = []
    self.__comboLength__ = 0
    self.__comboText__   = ""
  def setCombo( self, combo):
    self.__combo__       = combo
    self.__comboLength__ = len( combo)
    self.__comboText__   = ""
    for key in combo:
      if pgInputInstance.keyAssociation.has_key( key):
        self.__comboText__ += pgInputInstance.keyAssociation[key] + "  "
      else:
        self.clearCombo()
        return
  def addKeyToCombo( self, key):
    if pgInputInstance.keyAssociation.has_key( key) and not ( key in self.__combo__):
      self.__combo__.append( key)
      self.__comboLength__ += 1
      self.__comboText__   += pgInputInstance.keyAssociation[key] + "  "
      return True
    return False
  def getCombo( self):
    return self.__combo__
  def hasCombo( self, combo):
    if len(combo) == self.__comboLength__:
      for key in combo:
        if not ( key in self.__combo__):
          return False
      return True
    return False
 
 
#================================================================
## Main class in python to manage connection with server
class PGInput(PGPluginClient):

  MOUSE_EVENT_ID = CST_LOPLUG_REQUEST + "MouseEventId"
  X_INDEX        = 0
  Y_INDEX        = 1
  WIDTH_INDEX    = 2
  HEIGHT_INDEX   = 3
  
  # Index used in TableView
  CMD_NAME_INDEX = 0
  COMBO_INDEX    = 1

  # Working mode of the module
  WORKING_MODE_NOMINAL = 0
  WORKING_MODE_EDITION = 1

  infos = { "description": "Input plugin"
          , "version"    : 0.1
          , "icon"       : "input.jpg"
          }
#----------------------------------------------------------------
  ## Constructor
  # @param self              instance class
  # @param config            config
  def __init__( self, config, pathname):
    PGPluginClient.__init__( self, config, pathname)
    
    # Here config has been read, and config variables are set
      
#================================================================
# OVERRIDDEN METHODS
#================================================================

#----------------------------------------------------------------
  # Create all plug variables.
  # Method invoked from __init__() method of the base class
  # @param self        instance class
  def _create_plug_context( self):
    self.capabilities = { "provider":[
                                     ],   #- List of datas that the plugin can provide [name, id, description, type, default format]
                          "renderer":[ CST_LOPLUG_CAPA_CMD
                                     ]    #- List of capabilities for plugins (see constants.py for a list)
                        }

    # Init variable which are part or issued of the config.
    global pgInputInstance
    pgInputInstance     = self                           #- Instance of PGInput, used by pyHook callback methodes
    self.clientId       = None                           #- ClientId for mouseEvent notification
    self.requests       = {}                             #- Discard all the requests from every plug
    self.keyAssociation = {}                             #- Association KeyValue/KeyName for the used key
    self.row            = 0                              #- Identify the current selected row
    self.cmdList        = []                             #- list of command items
    self.workingMode    = PGInput.WORKING_MODE_NOMINAL
    
    # Do Hook stuff
    self.hookManager = pyHook.HookManager()
    self.connect( self, SIGNAL("keyPressed"), self.__onKeyPressed__ )
    
    # Subscribed to keyboard events
    self.hookManager.SubscribeKeyAll( onKeyBoardEvent)
    self.hookManager.HookKeyboard()
    
    # Set mouseEvent call back. Service activation is done by the client
    self.mouseSubscribed = False
    self.connect( self, SIGNAL("EnableMouseHook") , self.__enableMouseHook__)
    self.connect( self, SIGNAL("DisableMouseHook"), self.__disableMouseHook__)
#----------------------------------------------------------------
  # To load the settings
  # Method invoked from __init__() method of the base class
  # @param self        instance class
  def _load_config( self ):
    # Read key association
    self.keyAssociation = {}
    value = self.get_config_value( "keyAssociation")
    if value:
      pairs = value.split("|")
      for pair in pairs:
        keyValue, keyName = pair.split(":")
        self.keyAssociation[int( keyValue)] = keyName
      
    # Read CmdConfig  
    self.cmdList = []
    value = self.get_config_value( "CmdConfig")
    if value:
      cmdConfigs = value.split("##")
      for cmdConfig in cmdConfigs:
        keyId, keyCombo = cmdConfig.split(":")
        # Create cmdItem
        cmdItem = CmdItem( keyId)
        # Process combo for this cmdItem
        keyComboStr = keyCombo.split("|")
        keyCombo = []
        for key in keyComboStr:
          if key != "":
            keyCombo.append( int( key))
        cmdItem.setCombo( keyCombo)
        self.cmdList.append( cmdItem)

#----------------------------------------------------------------
  # To save the settings
  # @param self        instance class
  def _save_config( self ):
    # Save key association
    keyAssociation = ""
    for keyValue, keyName in self.keyAssociation.iteritems():
      keyAssociation += "%s:%s|" % ( keyValue, keyName)
    keyAssociation = keyAssociation[:-1]
    self.set_config_value( "keyAssociation", keyAssociation)
    
    # Save Combo
    keyCombo = ""
    for cmdItem in self.cmdList:
      cmdId    = cmdItem.getId()
      cmdKeys  = cmdItem.getCombo()
      keyCombo += "%s:" % cmdId
      if len( cmdKeys):
        for cmdKey in cmdKeys:
          keyCombo += "%s|" % cmdKey
        keyCombo = keyCombo[:-1]
      else:
        keyCombo += "|"
      keyCombo += "##"
    keyCombo = keyCombo[:-2]
    self.set_config_value( "CmdConfig", keyCombo)
    
    self.workingMode    = PGInput.WORKING_MODE_NOMINAL
#----------------------------------------------------------------
  # To get configuration widget.
  # It is used to get the teamspeak executable and DLL path
  # @param self        instance class
  # @return widget
  def _get_config_widget( self, data):
    self.selectedIndex = None
    self.workingMode   = PGInput.WORKING_MODE_NOMINAL

    #- List of all commands managed by the module
    self._updateCmdList__( data)
    
    # Build 
    self.main = QWidget()
    hBox = QHBoxLayout()
    self.main.setLayout( hBox)
    
    self.cmdView  = QTableView( self.main)
    self.cmdView.horizontalHeader().setStretchLastSection( True)
    self.cmdView.horizontalHeader().setResizeMode( PGInput.COMBO_INDEX, QHeaderView.Stretch)
    self.cmdModel = QStandardItemModel( 0, 2, self.main)
    self.cmdView.setModel( self.cmdModel)
    
    hBox.addWidget( self.cmdView)
    
    vBox = QVBoxLayout()
    lblExplain = QLabel( self.tr(
    """Way of working:
    1) Enter 'EDIT' mode
    2) Select the combo to change"
    3) Change the combo, enter key by key
    4) Repeat operations 2) and 3)
    5) Exit 'EDIT' mode
    
    COMBO:
      All the keys pressed at the same time
      This is why you can not have the same key two times
    """) ) 
    lblExplain.setWordWrap( True )
    vBox.addWidget( lblExplain)
    vBox.addStretch(0)    
    self.btnEditMode = QPushButton()
    vBox.addWidget( self.btnEditMode)
    vBox.addStretch(0)

    hBox.addLayout( vBox)
    
    self.connect( self.cmdView    , SIGNAL( "clicked(const QModelIndex&)"), self.__onCmdItemSelected__ )
    self.connect( self.btnEditMode, SIGNAL( "clicked(bool)"              ), self.__onChangeEditMode__  )
    
    self.__updateUi__()

    
    return self.main
#----------------------------------------------------------------
  # Transfert data from 'model' to 'UI'
  # @param self instance class
  def _init_ui( self, options=None ):
    pass

#----------------------------------------------------------------
  # Transfert data from 'UI' to 'model'
  # @param self instance class
  def _deinit_ui( self ):
    pass

#----------------------------------------------------------------
  # Method invoked by the PGPluginClient parent on start()
  # When this method is invoke the config has been loaded
  # So, here execute start specific part.
  # @param self                      instance class
  def _start( self ):    # Get instance of hook manager
    pass
#----------------------------------------------------------------
  # Method invoked by the PGPluginClient parent on restart()
  # When this method is invoke the config has been loaded
  # So, here execute restart specific part.
  # @param self                      instance class
  def _config_has_changed( self ):
    pass
#----------------------------------------------------------------
  # Provider datas has been updated -> Change the UI
  # @param self instance class
  def _update_provider_data( self, data ):
    self._updateCmdList__( data)
    self.__updateUi__()
#----------------------------------------------------------------
  # A client request data
  # So, here execute restart specific part.
  # @param self                      instance class
  # @param szDataRequest             Data for this request
  def _data_requested( self, szDataRequest):  
    # Create entry for this data if not yet done
    dataId = szDataRequest.data_id
    if not self.requests.has_key( dataId):
      self.requests[dataId] = {}
      
      if dataId == PGInput.MOUSE_EVENT_ID:
        # Enable hook mouse process
        self.debug( "Enable mouse hook -----------------------------------------------------------" )
        self.emit( SIGNAL("EnableMouseHook"))

    srcPlugId = szDataRequest.from_id
    if not ( srcPlugId in self.requests[dataId].keys()):
      self.requests[dataId][srcPlugId] = []

    if dataId == PGInput.MOUSE_EVENT_ID:
      self.requests[dataId][srcPlugId] = [ szDataRequest.x, szDataRequest.y, szDataRequest.width, szDataRequest.height]
#       print "Mouse event request, x[%s], y[%s], w[%s], h[%s]" % (szDataRequest.x, szDataRequest.y, szDataRequest.width, szDataRequest.height)


#----------------------------------------------------------------
  # To receive data discard commands
  # @param self                      instance class
  # @param srcPlugId                 Id of the requester plug (renderer)
  # @param datas                     datas
  def _data_discarded( self, szDataDiscard):
    dataId = szDataDiscard.data_id
    if self.requests.has_key( dataId):
      srcPlugId = szDataDiscard.from_id
      if srcPlugId in self.requests[dataId].keys():
        del self.requests[dataId][srcPlugId]
        if not self.requests[dataId]:
          del self.requests[dataId]
          if dataId == PGInput.MOUSE_EVENT_ID:
            # Disable hook mouse process
            self.emit( SIGNAL("DisableMouseHook"))

  #----------------------------------------------------------------
  # A selection occurs on the channel list -> switch
  # @param self                      instance class
  # @param srcPlugId                 Id of the plug sending the data
  # @param list_id                   list Id
  # @param datas                     datas
  def _selection( self, srcPlugId, list_id, datas):
    pass    
#----------------------------------------------------------------
  # To shutdown the instance
  # To be overwritten by the daughter class if needed
  # @param self                      instance class
  def _shutDown( self):
    pass
#----------------------------------------------------------------
  # Wait for shutdown complete
  # @param self                      instance class
  def _waitForShutDownComplete(self):
    pass
    
#================================================================
# INTERFACE METHODS
#================================================================
  def addKeyAssociation( self, keyId, keyName):
    if not self.keyAssociation.has_key( keyId):
      self.keyAssociation[keyId] = keyName
      
#================================================================
# LOCAL METHODS
#================================================================
#----------------------------------------------------------------
  # Invoked on receiving signal "EnableMouseHook"
  # @param self        instance class
  def __enableMouseHook__( self):
    # To perform mouse subscription only onece
    if self.mouseSubscribed == False:
      self.mouseSubscribed = True
      self.hookManager.SubscribeMouseAll( onMouseEvent)   
    self.hookManager.HookMouse()
#----------------------------------------------------------------
  # Invoked on receiving signal "DisableMouseHook"
  # @param self        instance class    
  def __disableMouseHook__( self):
    self.hookManager.UnhookMouse()
#----------------------------------------------------------------
  # Call back method invoked when a key is pressed
  # @param self                      instance class
  # @param keyTable                  table of keyCodes for all the key pressed
  def __onKeyPressed__( self, keyTable):
    if self.workingMode == PGInput.WORKING_MODE_EDITION:
      if self.cmdItem:
        for key in keyTable:
          newKey = self.cmdItem.addKeyToCombo( key)
          if newKey:
            self.__updateUi__()
      
    else:
      for cmdItem in self.cmdList:
        if cmdItem.hasCombo( keyTable):
          cmdId = cmdItem.getId()
          opCode = PGPluginClient.OPCODE_DATA_VALUE
          szObj = SzText( from_id = self.get_name()
                        , data_id = cmdId
                        , text    = ""
                        )
          self.send_object([cmdId], opCode, szObj )
          # We stop at the first item found.
          # But it is possible to continue if we want many commands associated to a unic combo
          break
            
#----------------------------------------------------------------
  # This method updates the list of cmdItem containing all the command to be processed
  # @param self                      instance class
  # @param data                      data related to all_providers
  def  _updateCmdList__( self, data):
    newList = []
    rendered_datas = self.get_list_data_to_render_for()
    for providerName, providerInfoList in rendered_datas.iteritems():
      for thisProviderInfo in providerInfoList:
        cmdItem = None
        for providerInfo in data["all_providers_data"]:
          if ( thisProviderInfo.id == providerInfo.id) and providerInfo.visible:
            cmdItem = self.__searchForCmdItem__( thisProviderInfo.id)
            if cmdItem:
              break
        if not cmdItem    :
          cmdItem = CmdItem( thisProviderInfo.id)
          
        newList.append( cmdItem)
        
    self.cmdList = newList
#----------------------------------------------------------------
  # This method searchs in teh cmdList if the cmdId exits
  # @param self                      instance class
  # @param cmdId                     id of the command to search
  def __searchForCmdItem__( self, cmdId):
    for cmdItem in self.cmdList:
      if cmdItem.getId() == cmdId:
        return cmdItem
    return None
#----------------------------------------------------------------
  # This method updates the UI according to the cmdList.
  # @param self                      instance class
  def __updateUi__( self):
    self.cmdModel.clear()
    self.cmdModel.setHorizontalHeaderLabels( [ self.tr("Command name"), self.tr("Combo")])
    row = 0
    for cmdItem in self.cmdList:
      self.cmdModel.appendRow( [ cmdItem.getNameItem(), cmdItem.getComboItem()])
      self.cmdModel.setVerticalHeaderItem( row, QStandardItem())
      row += 1
    self.cmdView.resizeColumnsToContents()
    if self.workingMode == PGInput.WORKING_MODE_NOMINAL:
      self.btnEditMode.setText( self.tr( "Enter EDIT mode"))
      self.cmdView.setStyleSheet( "background-color: white;"
                                  "selection-background-color: pink;")
    else:
      self.btnEditMode.setText( self.tr( "Exit EDIT nmode"))
      self.cmdView.setStyleSheet( "background-color: white;"
                                  "selection-background-color: cyan;")


    if self.selectedIndex:
      index = self.cmdModel.index( self.selectedIndex[0], PGInput.CMD_NAME_INDEX)
      self.cmdView.setCurrentIndex( index)
#----------------------------------------------------------------
  # Call back method invoked when 'ChangeCombo' key is pressed.
  # @param self                      instance class
  # @param clicked                   unused
  def __onChangeEditMode__( self, clicked):
    if self.workingMode == PGInput.WORKING_MODE_NOMINAL:
      self.workingMode = PGInput.WORKING_MODE_EDITION
    else:
      self.workingMode = PGInput.WORKING_MODE_NOMINAL
      
    self.cmdItem = None
    self.selectedIndex = None
    self.__updateUi__()
#----------------------------------------------------------------
  # Call back method invoked when a cell is selected
  # @param self                      instance class
  # @param index                     provides info on the selected cell
  def __onCmdItemSelected__( self, index):
    self.cmdItem = self.cmdList[index.row()]
    if self.workingMode == PGInput.WORKING_MODE_EDITION:
      self.cmdItem.clearCombo()
      self.selectedIndex = [index.row(), index.column()]
      self.__updateUi__()


