#!/usr/bin/env python
# Copyright (c) 2016 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.

import optparse
import readline
import socket
import sys
import os

import CliCommon
from EapiClientLib import EapiClient
from EapiClientLib import EapiException
import PyClient
import Tac

AUTO_COMPLETE_CACHE = {}
PROMPT = None
pc = None
xmppResponseStatus = None
eapiClient = EapiClient( disableAaa=True )

# pylint: disable-msg=E1103

def waitStatus( func, to, id_, targetUsers=None ):
   ''' takes in a function and returns True if the function evaluated
   on the recipient(s) return a True value '''
   global xmppResponseStatus
   xmppResponseStatus = pc.eval( "Xmpp.getXmppMessageStatus( '%s', '%s' )"
                                 % ( to, id_ ) )
   messageValues = [ status for user, status in xmppResponseStatus.items()
                     if not targetUsers or user in targetUsers ]
   return all( map( func, messageValues ) )

def sendXmppCommand( to, command ):
   # Ensure we send only 'str' type to PyClient (which is unicode incompatible)
   to = to.encode( "utf8" )
   command = command.encode( "utf8" )

   result = pc.eval( "Xmpp.checkXmppUserOrGroup( %s )" % repr( to ) )
   if result:
      CliCommon.printWarningMessage( result )
      return

   group = '@conference' in to
   id_ = pc.eval( "Xmpp.sendXmppMessage( %s, %s, %s )" %
                  ( repr( to ), repr( command ), group ) )
   assert isinstance( id_, str )
   try:
      # wait for status to become 'composing' or 'active' signaling acknowledgment
      Tac.waitFor( lambda : waitStatus( ''.__ne__ , to, id_ ),
                   timeout=15, warnAfter=False,
                   sleep=True, description="command acknowledgement" )
   except Tac.Timeout as e:
      responsiveUsers = set( user for user, status in xmppResponseStatus.items()
                             if status )
      unresponsiveUsers = ( user for user in xmppResponseStatus
                            if user not in responsiveUsers )
      CliCommon.printErrorMessage( e )
      CliCommon.printErrorMessage( "Did not receive command acknowledgement "
                        "from the following switch(es):" )
      for user in sorted( unresponsiveUsers ):
         CliCommon.printErrorMessage( user )
      if not responsiveUsers:
         CliCommon.printErrorMessage( "No one acknowledged your command. "
                           "No longer waiting for a response" )
         pc.eval( "Xmpp.stopAwaitingXmppReply( '%s', '%s' )" % ( to, id_ ) )
         return

   # some switches sent a compose, we will wait for them
   composedUsers = set( user for user, status in xmppResponseStatus.items()
                        if status )

   try:
      # wait for status to become 'active' signaling completion.
      Tac.waitFor( lambda :
                   waitStatus( 'active'.__eq__, to, id_, composedUsers ),
                   timeout=600, warnAfter=60, maxDelay=3, sleep=True,
                   description="command response" )
   except Tac.Timeout as e:
      responsiveUsers = set( user for user, status in xmppResponseStatus.items()
                             if status=='active' )
      unresponsiveUsers = ( user for user in xmppResponseStatus
                            if user not in responsiveUsers )
      CliCommon.printErrorMessage( e )
      CliCommon.printErrorMessage( "Did not receive command response "
                                   "from the following switch(es):" )
      for user in sorted( unresponsiveUsers ):
         CliCommon.printErrorMessage( user )
      if not responsiveUsers:
         CliCommon.printErrorMessage( "No one responded to your command. "
                                      "No longer waiting for a response." )
         pc.eval( "Xmpp.stopAwaitingXmppReply( '%s', '%s' )" % ( to, id_ ) )
         return

   # some switches finished responding, we will print them out
   activeUsers = set( user for user, status in xmppResponseStatus.items()
                      if status=='active' )
   reply = pc.eval( "Xmpp.getXmppReply( '%s', '%s' )" % ( to, id_ ) )
   for user in sorted( reply ):
      if user in activeUsers:
         if reply[ user ].strip():
            print 'response from: %s\n%s\n%s' % ( user, '-' * 50, reply[ user ] )

def complete( text, state ):
   result = autoComplete( text )
   sortResultList = sorted( result.keys() )
   if state > len( result ):
      return None
   if readline.get_completion_type() == ord( '?' ):
      return '{:<30} {:}'.format( sortResultList[ state ],
                                result[ sortResultList[ state ] ] )
   else:
      return ( ' '.join( text.split( ' ' )[ : -1 ] + [ sortResultList[ state ] ] ) +
               ' ' )

def showOnlineHelp( subst, matches, longest_match ):
   print
   for match in matches:
      print match
   print '%s%s' % ( PROMPT, readline.get_line_buffer() ),

def autoComplete( command ):
   # This function is mainly called from the complete function. This complete
   # function will try to find out completions one by one, whereas we
   # get all of the completions in 1 shot. So we cache the auto-complete 
   # results so that we aren't going to eAPI an excessive amount of times
   if command in AUTO_COMPLETE_CACHE:
      return AUTO_COMPLETE_CACHE[ command ]
   try:

      result = eapiClient.getCommandHelp( command )[ 'result' ]
      AUTO_COMPLETE_CACHE[ command ] = result
      return result
   except EapiException:
      return {}
   except KeyboardInterrupt:
      return {}

def main( xmppUserOrGroup ):
   while True:
      # print our fake prompt
      try:
         command = raw_input( '%s' % PROMPT ).rstrip()
      except KeyboardInterrupt:
         print
      except EOFError:
         print
         return

      if not command:
         # They entered a new line or just whitespace: no-op
         continue
   
      # Make sure we're not trying to leave
      if command == "exit" or command == "exi" or command == "ex":
         sys.exit( 0 )

      # Now let's send the command!
      # BUG28151 - only enable mode is supported
      sendXmppCommand( xmppUserOrGroup, command )

if __name__ == '__main__':
   Tac.setproctitle( " ".join( [ "XmppCli [interactive]" ] + sys.argv[ 1: ] ) )
   parser = optparse.OptionParser()
   parser.add_option( "-s", "--sysname", action="store",
                      default=os.environ.get( "SYSNAME","ar" ),
                      help="system name (default: %default)" )
   options, args = parser.parse_args()
   if not args:
      parser.error( "XMPP user or group name must be specified" )
   if len( args ) != 1:
      parser.error( "Only 1 XMPP user or group name is supported" )
   PROMPT = "xmpp-%s-%s#" % ( args[ 0 ].split( "@" )[ 0 ], 
                              socket.gethostname() ) 
   pc = PyClient.PyClient( options.sysname, "Xmpp" )
   pc.execute( "import Xmpp" )

   readline.set_completer_delims( '\n' )
   readline.parse_and_bind( 'tab: complete' )
   readline.parse_and_bind( '?: possible-completions' ) # same as tab-tab
   readline.set_completer( complete )
   readline.set_completion_display_matches_hook( showOnlineHelp )

   main( args[ 0 ] )
