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

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

import Tac

from PowerDiagLib import OptionParser
from PowerDiagLib import ( exitWithError, hwPowerSupplySlotConfig, envPowerStatusDir,
                           hwPowerSupplyCellConfig )
from PyClient import PyClient

parser = OptionParser()
parser.add_option( "", "--powerSupply", action="store",
                   help="The power supply to test "
                        "(Default is to test all supplies in the system)" )
parser.add_option( "", "--checkPresence", action="store_true",
                   help="Check power supply presence" )
parser.add_option( "", "--checkStatus", action="store_true",
                   help="Check power supply status" )

( options, args ) = parser.parse_args()

if args:
   parser.error( "unexpected arguments" )

sysdbRoot = PyClient( options.sysname, "Sysdb" ).agentRoot()

hwSlotConfig = hwPowerSupplySlotConfig( sysdbRoot )
hwCellConfig = hwPowerSupplyCellConfig( sysdbRoot )
powerStatusDir = envPowerStatusDir( sysdbRoot )

slotConfigs = {}
supplyStatuses = {}
if not options.powerSupply:
   slotConfigs = hwSlotConfig.slotConfig
else:
   slotConfig = hwSlotConfig.slotConfig.get( options.powerSupply )
   slotConfigs = { options.powerSupply : slotConfig }

# Check that all power supplies to be tested are present
for ( powerSupply, slotConfig ) in sorted( slotConfigs.items() ):
   supplyStatus = powerStatusDir.get( "PowerSupply%s" % powerSupply )
   supplyStatuses[ "PowerSupply%s" % powerSupply ] = supplyStatus

   if not supplyStatus or not slotConfig:
      exitWithError( "Could not find supply %s. Check that it is inserted." %
                     powerSupply )

for ( powerSupply, slotConfig ) in sorted( slotConfigs.items() ):
   print( "powerSupplyTest: testing power supply", powerSupply )

   supplyStatus = supplyStatuses[ "PowerSupply%s" % powerSupply ]

   if options.checkPresence:
      print( "Checking power supply presence" )
      if hwCellConfig.psuPresentGpo:
         # This system does not have dsh support, so just check the Gpo
         if ( hwCellConfig.psuPresentGpo.get( slotConfig.slotId ) and
              hwCellConfig.psuPresentGpo[ slotConfig.slotId ].asserted ):
            print( "Power supply insertion detected" )
         else:
            exitWithError(
               "Power supply present bit is not set. "
               "Is the power supply inserted?" )
      else:
         print( "No presence detection defined." )

   try:
      Tac.waitFor( lambda: supplyStatus.state != "unknown",
                   description="Power supply to be detected and initialized",
                   timeout=60  # The default timeout of 10 minutes is too long - we
                               # can't allow our manufacturing scripts to site
                               # waiting for something that will never happen,
                               # as we're paying by the hour.
                   )
   except Tac.Timeout:
      exitWithError( "Software could not detect and initialize the power supply."
                     "Check that the power supply is properly inserted. Try running "
                     "the power supply's register test to make sure that it is "
                     "accessible and properly programmed." )

   if options.checkStatus:
      print( "Checking power supply status" )

      if supplyStatus.state == "powerLoss":
         exitWithError( "ERROR: Power loss detected. Is %s plugged in?" % (
            supplyStatus.name ) )
      elif supplyStatus.state != "ok":
         exitWithError( "ERROR: {} failed, state is {}".format(
            supplyStatus.name, supplyStatus.state ) )

      error = False

      # Sanity check the various status attributes
      def isValid( value ):
         return value != float( "-INFINITY" )

      # Check that the input voltage is reasonable
      if supplyStatus.inputVoltageSensor and isValid(
         supplyStatus.inputVoltageSensor.voltage ):
         inputVoltage = supplyStatus.inputVoltageSensor.voltage
         # BUG15220 we need to understand the power supply readings better- DS460
         #   readings seem to be off by ~10% or more
         # 265V assumes 10% accuracy on measuring an input voltage of up to 240V
         if inputVoltage <= 0 or inputVoltage > 265:
            print( f"ERROR: Input voltage of {supplyStatus.name} outside of "
                   f"acceptible range: {inputVoltage}V" )
            error = True

      # Check that input current is not 0 (if it is, then presumably the power supply
      # should have reported an power loss.
      if supplyStatus.inputCurrentSensor and isValid(
         supplyStatus.inputCurrentSensor.current ):
         inputCurrent = supplyStatus.inputCurrentSensor.current
         if inputCurrent <= 0:
            print( "ERROR: Invalid input current measured from "
                   f"{supplyStatus.name}: {inputCurrent}A" )
            error = True

      # Check that the input power is not 0
      if isValid( supplyStatus.inputPower ):
         if supplyStatus.inputPower <= 0:
            print( "ERROR: Invalid input power measured from {}: {}W".format(
               supplyStatus.name, supplyStatus.inputPower ) )
            error = True

      # Check that the powerOk Gpo is good (if there is one)
      if slotConfig.powerOk:
         slotConfig.powerOk.impl.doRead()
         if slotConfig.powerOk.impl.lastReadState == 0:
            print( "ERROR: Slot power OK Gpo is false, measured from %s" % (
               supplyStatus.name ) )
            error = True

      # Check that the output current is not 0
      if supplyStatus.outputCurrentSensor and isValid(
         supplyStatus.outputCurrentSensor.current ):
         outputCurrent = supplyStatus.outputCurrentSensor.current
         if outputCurrent <= 0:
            print( "ERROR: Invalid output current measured from {}: {}A".format(
               supplyStatus.name, outputCurrent ) )
            error = True

      # Check that the output power is reasonable
      if isValid( supplyStatus.outputPower ):
         if supplyStatus.outputPower <= 0 or \
                supplyStatus.outputPower > ( supplyStatus.capacity * 1.05 ):
            print( f"ERROR: Output power of {supplyStatus.name} outside of "
                   f"acceptible range: {supplyStatus.outputPower}W" )
            error = True

      # Check that the output voltage is close to 12V
      # XXX_APECH This assumes a 12V power supply obviously
      if ( not supplyStatus.outputVoltageSensor or
           not isValid( supplyStatus.outputVoltageSensor.voltage ) ):
         # Try to estimate the output voltage
         if ( supplyStatus.outputCurrentSensor and
              isValid( supplyStatus.outputCurrentSensor.current ) and
              isValid( supplyStatus.outputPower ) ):
            outputVoltage = ( ( 1.0 * supplyStatus.outputPower ) /
                              supplyStatus.outputCurrentSensor.current )
            # XXX_APECH One thing that's not quite right here is that there's
            # no guarantee that the output power and output current are from
            # the power supply at the same point in time. Since the output
            # current and output power values are presumably read from
            # 2 registers that can change independently, we don't have a
            # guarantee that the 2 values in the tac model were measured
            # in the supply at the same point in time. In practice, this is
            # only a problem if we're running the test while the system
            # is drastically changing it's power load, so this should be
            # sufficient for manufacturing
         else:
            # Can't estimate. Oh well
            outputVoltage = float( "-INFINITY" )
      else:
         outputVoltage = supplyStatus.outputVoltageSensor.voltage
      if isValid( outputVoltage ):
         # XXX_APECH Eventually we want to tighten up this threshold. The
         # ColdWatt power supply claims that the output voltage is 12V +- 5%.
         # However, the output current and outputCurrent are read with pretty
         # low accuracy, so it's unclear how precise we can be in this check.
         if outputVoltage < (12 * 0.85 ) or outputVoltage > (12 * 1.15):
            print( "ERROR: Output voltage of %s outside of acceptible "
                  "range (12V +-15%%): %sV" % (
               supplyStatus.name, outputVoltage ) )
            error = True
         elif outputVoltage < (12 * 0.90 ) or outputVoltage > (12 * 1.10):
            # NOTE - we only warn when we're more than 10% off, as there
            # is some error in out calculating the output voltage from
            # the output current and output power due to the number of
            # significant digits in these 2 measurements.
            print( "WARNING: Output voltage of", supplyStatus.name,
                   "outside of spec'd range (12V +-5%%): %sV" % outputVoltage )

      if error:
         exitWithError( "Power supply status check for %s failed" % (
            supplyStatus.name ) )

   print( "powerSupplyTest:", supplyStatus.name, "passed!" )
