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

import argparse
import re
import sys
import SpiUtil

usage = '''Performs a SPI request on the specified accelerator.

<accel> has the following format:
   /scd/<offset>[/<stride>]
Offset is the offset of the SPI accelerator command register.
Stride is the difference in addresses between each register.
The default stride is 0x10.

A request consists of a list of commands which forms a single
transaction. Commands are space-delimited and each command
is in the following format:
   bit 9: 1 if this command ends a transaction
   bit 8: 1 if a response is expected
   bits 7:0: The command itself.
If no command has bit 9 set, the final command will include it automatically.

For example, the following command expects 4 response bytes:
   0x9f 0x100 0x100 0x100 0x100

As a shortcut, the wildcard character * followed by a number can be used to
specify multiple identical bytes. For example, the following expects
9 response bytes:
   0x9f 0x100*9
'''

def doRequest( pciAddr, accel, stride, commands ):
   try:
      spiSm = SpiUtil.SpiSm( pciAddress=pciAddr, accelOffset=accel,
                             accelStride=stride )
      response = spiSm.do( commands )
   except SpiUtil.SpiError as e:
      # pylint: disable-next=consider-using-f-string
      print( 'Request failed. Error: %s' % e, file=sys.stderr )
      sys.exit( 1 )

   # pylint: disable-next=consider-using-f-string
   print( 'Request succeeded. Response bytes( %d ):' % len( response ) )
   # pylint: disable-next=consider-using-f-string
   print( '    %s' % ' '.join( [ hex( x ) for x in response ] ) )

numberRe = r'(0x)?[0-9a-f]+'
def parseAccel( accelSpec ):
   m = re.match( r'/scd/(' + numberRe + r')(/(' + numberRe + r'))?', accelSpec )
   if not m:
      # pylint: disable-next=consider-using-f-string
      print( 'Failed to parse accelerator specification: %s' % accelSpec,
             file=sys.stderr )
      sys.exit( 1 )
   accel = int( m.group( 1 ), 0 )
   stride = 0x10
   if m.group( 4 ):
      stride = int( m.group( 4 ), 0 )
   return accel, stride

def parseCommands( commandSpec ):
   commands = []
   for commandParm in commandSpec:
      m = re.match( r'(' + numberRe + r')\*(' + numberRe + ')', commandParm )
      if m:
         command = int( m.group( 1 ), 0 )
         n = int( m.group( 3 ), 0 )
         commands += [ command ] * n
         continue

      try:
         commands += [ int( commandParm, 0 ) ]
      except ValueError:
         # pylint: disable-next=consider-using-f-string
         print( 'Failed to parse commands. Invalid command: %s' % commandParm,
                file=sys.stderr )
         sys.exit( 1 )

   return commands

def main():
   parser = argparse.ArgumentParser(
      description=usage, formatter_class=argparse.RawDescriptionHelpFormatter )
   parser.add_argument( '--pciAddr', help='PCI address of the device' )
   parser.add_argument( 'accel', help='SPI Accelerator specification' )
   parser.add_argument( 'commands', metavar='command', nargs='+',
                        help='Command list' )
   args = parser.parse_args()

   accel, stride = parseAccel( args.accel )
   commands = parseCommands( args.commands )
   doRequest( args.pciAddr, accel, stride, commands )

if __name__ == '__main__':
   main()
