#!/usr/local/bin/python
import sys
import getopt
import isi.gconfig as gcfg
import isi.hw.hal as hal
import isi.hw.version as hwver
import isi.app.lib.procs as procs
import isi.hw.check.lib.filenames as files

sensors_default = {}

THRESHOLDS = {
    'lnr' : 'Lower Non-recoverable',
    'lcr' : 'Lower Critical',
    'lnc' : 'Lower Non-Critical',
    'unc' : 'Upper Non-Critical',
    'ucr' : 'Upper Critical',
    'unr' : 'Upper Non-recoverable',
}

RAW_THRESHOLDS = {
    'lnr' : '0x04',
    'lcr' : '0x02',
    'lnc' : '0x01',
    'unc' : '0x08',
    'ucr' : '0x10',
    'unr' : '0x20',
}

def run_ipmitool_cmd(cmd):
    ipmitool = files.commands['ipmitool']
    error, output = procs.get_cmd_output("%s %s" % (ipmitool, cmd))
    if error:
        print "ipmitool command failed: %s %s" % (ipmitool, cmd)
    return error, output

def init_sensor_defaults():
    global sensors_default
    sensors_default = hal.getDefaultSensors()

def dump_table():
    cmd = '/usr/bin/isi_hwtools/isi_ipmicmc -s -p t -a 20' 
    error, output = procs.get_cmd_output(cmd)
    if error:
        print 'Unable to dump sensor table'
        return
    for line in output:
        print line

def clear_all_sensors():
    global sensors_default
    success = 0
    for sensor in sensors_default:
        if not clear_sensor(sensor):
            success += 1

def clear_sensor(name, sev=None):
    global sensors_default
    result = 0
    severities = ['lnr', 'lcr', 'lnc', 'unr', 'ucr', 'unc']
    if sev is not None:
        severities = filter(lambda x: x == sev, severities)
    sensor = sensors_default.get(name)
    for sev in severities:
        val = sensor.get(sev)
        if val == 'na':
            continue
        cmd = 'sensor thresh "%s" %s %s' % (name, sev, val)
        print ('Setting %s %s threshold to default %s' %
              (name, THRESHOLDS.get(sev), val))
        error, output = run_ipmitool_cmd(cmd)
        if error:
            result = 1
    return result

def inject_sel_entry(name, sev, addr=''):
    global sensors_default
    upper = False
    upper_thresh = ['unc', 'ucr', 'unr']
    lower_thresh = ['lnc', 'lcr', 'lnr']
    sensors = hal.getSensors()
    sensor = sensors.get(name)
    config_sensor = sensors_default.get(name)
    sen_val = sensor.get('value')
    if sen_val == 'na':
        print ('%s value is not a numeric value' % name)
        return
    else:
        sen_val = float(sen_val)
    if sev in upper_thresh:
        upper = True
        sev_index = upper_thresh.index(sev)
        thresh_list = upper_thresh[:sev_index + 1]
        boundary_sev = get_boundary_sev(sen_val, config_sensor, lower_thresh, upper) 
    elif sev in lower_thresh:
        lower = True
        sev_index = lower_thresh.index(sev)
        thresh_list = lower_thresh[:sev_index + 1]
        boundary_sev = get_boundary_sev(sen_val, config_sensor, upper_thresh, upper) 
    else:
        print "Invalid threshold severity specified: %s" % sev
        return

    if sensor.get(sev) == 'na':
        print ('%s threshold cannot be modified for sensor %s' %
              (THRESHOLDS.get(sev), name))
        return

    boundary = config_sensor.get(boundary_sev)
    if not boundary == 'na':
        boundary = float(boundary) 
    else:
        # Threshold boundaries that are set with hysteresis are
        # handled differently. We need to issue IPMI raw commands
        # to properly get the proper override value for a given
        # sensor
        boundary = boundary_hysteresis(sensor, upper)
        set_hyst_thresh(name, sensor, thresh_list, boundary, addr)
        return

    for sev in (lower_thresh + upper_thresh):
        config_val = config_sensor.get(sev)
        if config_val == 'na':
            continue
        else:
            config_val = float(config_val)
        # If we are modifying an upper threshold and it's an
        # upper severity, set to boundary threshold if its config
        # threshold is larger than the value read else set to
        # config value
        if upper and sev in lower_thresh:
            if config_val > sen_val: 
                thresh_val = boundary
            else:
                thresh_val = config_val
        # If we are modifying a lower threshold and it's a
        # lower severity, set to boundary threshold if its config
        # threshold is less than the value read else set to
        # config value
        elif not upper and sev in upper_thresh:
            if config_val < sen_val:
                thresh_val = boundary
            else:
                thresh_val = config_val
        # If it is in the thresh_list to change, set to
        # boundary value else set to config value
        else:
            if sev in thresh_list:
                thresh_val = boundary
            else:
                thresh_val = config_val
        set_thresh(name, sev, thresh_val, addr)

def boundary_hysteresis(sensor, upper):
    value = None
    sensornum = sensor.get('sensornum')
    poshys = int(sensor.get('poshys'))
    neghys = int(sensor.get('neghys'))
    cmd = "raw 0x04 0x2d 0x%02x" % sensornum
    error, output = run_ipmitool_cmd(cmd) 
    if not error:
        rawoutput = output[0].strip(' ').split(' ')
        value = rawoutput[0]
        value = int(value, 16)
        value = value + (upper and -(poshys) or neghys)
    return value

def get_boundary_sev(value, config_sensor, thresh_list, upper):
    # Set to highest severity to start with
    boundary = thresh_list[-1]
    for thresh in thresh_list:
        thresh_val = config_sensor.get(thresh)
        if thresh_val == 'na':
            continue
        thresh_val = float(thresh_val)
        if (upper and thresh_val < value) or (not upper and thresh_val > value):
            boundary = thresh
            break
    return boundary

def set_thresh(name, sev, value, addr=''):
    if not len(addr) == 0:
        addr = '-t %s' % addr
    cmd = '%s sensor thresh "%s" %s %s' % (addr, name, sev, value)
    print ('Setting %s %s threshold to %s' %
          (name, THRESHOLDS.get(sev), value))
    run_ipmitool_cmd(cmd)

def set_hyst_thresh(name, sensor, thresh_list, value, addr):
    sensornum = sensor.get('sensornum')
    if not len(addr) == 0:
        addr = '-t %s' % addr
    for sev in thresh_list:
        # Setting threshold value per IPMI spec
        sev_raw = RAW_THRESHOLDS.get(sev)
        sev_str = THRESHOLDS.get(sev)
        val_str = '0x%02x' % value
        lnr = (sev == 'lnr') and val_str or '0x00' 
        lcr = (sev == 'lcr') and val_str or '0x00'
        lnc = (sev == 'lnc') and val_str or '0x00'
        unc = (sev == 'unc') and val_str or '0x00'
        ucr = (sev == 'ucr') and val_str or '0x00'
        unr = (sev == 'unr') and val_str or '0x00'
        cmd = ('raw 0x04 0x26 0x%02x %s %s %s %s %s %s %s' %
              (sensornum, sev_raw, lnc, lcr, lnr, unc, ucr, unr))
        print 'Setting %s %s threshold' % (name, sev_str)
        run_ipmitool_cmd(cmd)

def usage():
    print 'Usage:'
    print '    Show Help'
    print '        isi_sel_inject -h'
    print ''
    print '    Dump Sensor Table   : isi_sel_inject -a dump'
    print '    Assert              : isi_sel_inject -a assert -n <name> -s <sev>'
    print '    Default Sensor All  : isi_sel_inject -a default -n <name>'
    print '    Default Sensor Sev  : isi_sel_inject -a default -n <name> -s <sev>'
    print '    Default All Sensors : isi_sel_inject -a default-all'
    print ''
    print '    name: sensor name'
    print '    severity: lnr, lcr, lnc, unc, ucr, unr'
    print '        lnr: Lower Non-Recoverable'
    print '        lcr: Lower Critical'
    print '        lnc: Lower Non-Critical'
    print '        unc: Upper Non-Critical'
    print '        ucr: Upper Critical'
    print '        unr: Upper Non-Recoverable'
    print '    action: assert, default, default-all, dump'
    print '        assert: Assert sensor'
    print '        default : Set specific sensor thresholds back to default'
    print '        default-all : Set all sensor thresholds back to default'
    print '        dump: Dump sensor table'
    print ''
    return

def main(argv):
    name = None 
    sev = None
    action = None 
    # This utiliy is only used with motherboards that support a BMC IPMI device
    if not hal.supportsBmcSensorMonitoring():
        print 'Hardware does not support BMC sensor monitoring'
        return
    try:
        opts, args = getopt.getopt(argv, "hn:s:a:")
    except getopt.GetoptError:
        usage()
        return
    for opt, arg in opts:
        if opt == '-h':
            usage()
            return
        elif opt == '-n':
            name = arg
        elif opt == '-s':
            sev = arg
        elif opt == '-a':
            action = arg
        else:
            usage()
            return
    if action is None:
        print 'No action specified, exiting.'
        return
    elif action not in ['assert', 'dump', 'default', 'default-all']:
        print 'Incorrect action specified: %s' % action
        return
    elif name is None and not action in ['dump', 'default-all']:
        print 'No sensor name specified, exiting.'
        return
    elif sev is None and not action in ['dump', 'default', 'default-all']:
        print 'No severity specified, exiting.'
        return

    init_sensor_defaults()
    if action == 'assert':
        inject_sel_entry(name, sev)
    elif action == 'default':
        clear_sensor(name, sev) 
    elif action == 'default-all':
        clear_all_sensors()
    elif action == 'dump':
        dump_table()
    else:
        print 'Incorrect action specified: %s' % action
        return

if __name__ == "__main__":
    main(sys.argv[1:])
