#!/usr/bin/env python3
# Copyright (c) 2009-2010 Arastra, Inc.  All rights reserved.
# Arastra, Inc. Confidential and Proprietary.

# pylint: disable=consider-using-f-string

import Tac, Tracing
import sys, os, optparse # pylint: disable=deprecated-module

from PyClient import PyClient

t0 = Tracing.trace0

TIMEOUT = 240

fanConfig = {}
fanStatus = {}

def getPmbusAndThermostat( sysdbRoot ):
   _pmbusConfig = sysdbRoot.entity[ "hardware/powerSupply/pmbus11" ]
   _thermostatConfig = sysdbRoot.entity[ "environment/thermostat/config" ]
   _thermostatStatus = sysdbRoot.entity[ "environment/thermostat/status" ]
   return _pmbusConfig, _thermostatConfig, _thermostatStatus

def exitWithError( reason ):
   print( reason )
   sys.exit( 1 )

def speedIs( speed ):
   # Write speed for all fans.
   for fanIdNum in fanConfig: # pylint: disable=consider-using-dict-items
      fanConfig[ fanIdNum ].speed = speed

def waitAllFans( f, target, tolerance=5, timeout=TIMEOUT, description="" ):
   failedFans = []
   for fanIdNum in fanConfig:
      status = fanStatus[ fanIdNum ]
      try:
         # pylint: disable-next=cell-var-from-loop
         Tac.waitFor( lambda: f( status, target, tolerance=tolerance ),
                      description=f"fan {status.name} {description}",
                      timeout=TIMEOUT, maxDelay=5 )
      except Tac.Timeout:
         failedFans.append( ( status.name, status.speed,
                              status.fanSpeedOverriddenByHardware,
                              status.speedStable ) )
      
   if failedFans != []: # pylint: disable=use-implicit-booleaness-not-comparison
      exitString = "Failed for reasons: "
      for fanName, speed, fsobh, stable in failedFans:
         exitString += "%s failed with current speed %s. " \
             "fanSpeedOverriddenByHardware=%s speedStable=%s.\n" \
             % ( fanName, speed, fsobh, stable )
         
      exitWithError( exitString )

def compareTarget( status, target, tolerance=5 ):
   minRange = target - tolerance if target - tolerance >= 0 else 0
   maxRange = target + tolerance if target + tolerance <= 100 else 100

   withinTolerance = status.speed >= minRange and status.speed <= maxRange

   t0( "status=%s minRange=%s maxRange=%s withinTolerance=%s fsobh=%s stable=%s" \
       % ( status.speed, minRange, maxRange, withinTolerance, 
           status.fanSpeedOverriddenByHardware, status.speedStable ) )

   valueOk = status.speed >= minRange if status.fanSpeedOverriddenByHardware else \
       withinTolerance
   
   return valueOk and status.speedStable

def main():
   myName = "pmbus11DeviceFanTest"
   defaultSysname = os.environ.get( 'SYSNAME', "ar" )

   parser = optparse.OptionParser()
   parser.add_option( "-s", "--sysname", action="store",
                      help="System name (default: %default)",
                      default=defaultSysname )
   parser.add_option( "", "--powerSupply", action="store",
                      help="The power supply to test " )
   ( options, args ) = parser.parse_args()

   if args:
      parser.error( "unexpected arguments" )
   if not options.powerSupply:
      parser.error( "you must specify a power supply to test" )

   sysRoot = PyClient( options.sysname, "Sysdb" )

   pmbusConfig, thermostatConfig, thermostatStatus = getPmbusAndThermostat( sysRoot )

   pmbusDevice = pmbusConfig.powerSupply.get( int( options.powerSupply ) )
   if not pmbusDevice:
      exitWithError( "Could not find config for supply %s" % options.powerSupply )

   print( myName, "testing fan speed control." )

   for fanId in pmbusDevice.fan:
      fanConfig[ fanId ] = pmbusDevice.fan[ fanId ].fanEnvConfig
      fanStatus[ fanId ] = pmbusDevice.fan[ fanId ].fanEnvStatus

   savedThermostatMode = thermostatConfig.mode
   try:
      # Set the thermostat agent into debug mode so that it does not attempt to
      # configure the fan speeds
      thermostatConfig.mode = 'debug'
      # Wait for the Thermostat agent to acknowledge the mode change
      Tac.waitFor( lambda: thermostatStatus.mode == 'debug',
                   timeout=TIMEOUT,
                   description="Thermostat agent to go into debug mode" )

      speedIs( 100 )
      waitAllFans( compareTarget, 100, tolerance=12, description="to go to >=88%" )

      speedIs( 50 )
      waitAllFans( compareTarget, 50, description="to get between 45% and 55%" )

      speedIs( 30 )
      waitAllFans( compareTarget, 30, description="to get between 25% and 35%" )

   finally:
      # Undo all configuration
      thermostatConfig.mode = savedThermostatMode
      # Wait for the Thermostat agent to acknowledge the mode change
      Tac.waitFor( lambda: thermostatStatus.mode == savedThermostatMode,
                   timeout=TIMEOUT,
                   description="Thermostat agent to go back to it's original mode" )
      # Make sure all changes get to sysdb before we exit. Although at this point
      # there shouldn't be any...
      Tac.flushEntityLog()

   print( "pmbus11DeviceFanTest: power supply", options.powerSupply, "passed!" )


if __name__ == "__main__":
   main()
