#! /usr/bin/env python
import os
import re
import sys
import time

import isi.app.lib.files as filelib

import isi.hw.check.lib.args as argslib
import isi.hw.check.lib.cto as cto
import isi.hw.check.lib.filenames as files
import isi.hw.check.lib.log as loglib
import isi.hw.check.lib.misc as misc
import isi.hw.check.lib.ramdisk as ramdisklib
import isi.hw.check.lib.stats as stats
import isi.hw.check.lib.version as hwcheck_ver

log = None

id_dirs = ['safe.id']
safe_dirs = ['safe.short', 'safe.long', 'safe.human']
unsafe_dirs = ['unsafe.short', 'unsafe.long']
mfg_dirs = ['mfg.id', 'mfg.short', 'mfg.long']

standard_tests = [
    {'name' : 'safe.short.fcbstatus'},
    {'name' : 'safe.short.nvramread'},
    {'name' : 'safe.short.memspeed'},
    {'name' : 'safe.short.diskspeed'},
    {'name' : 'safe.id.dmilog',
     'args' : {'regather' : True},
    },
    {'name' : 'safe.short.phyerror'},
    {'name' : 'safe.short.drivehealth'},
    ]

# If we have any cluster-based test modules to run, add here.
# Script will dynamically enable -h display of option "--cluster".
# Note previously we had safe.short.cluster_network, rm'ed Dec 2005.
cluster_tests = [
    ]
use_cluster_tests = len(cluster_tests) != 0

def menu(names_and_mdirs):
    modules = []
    i = 1
    print 'Hardware Check Menu'
    print '--------------------------------------------'
    for (name, mdirs) in names_and_mdirs:
        newmods = misc.load_modules_from_dirs(mdirs)
        if len(newmods) == 0:
            continue
        
        for newmod in newmods:
            print '%d) %s' % (i, newmod.__name__)
            i += 1

        modules = modules + newmods
    print '--------------------------------------------'
    try:
        x = raw_input('Please select a test to run: ')
        if x == 'exit' or x == 'quit' or x == 'bye':
            raise EOFError()
    except EOFError:
        sys.exit(0)
    try:
        x = int(x)
        if x < 1 or x > len(modules):
            raise ValueError
    except ValueError:
        print ('ERROR: please specify a number between 1 and %d inclusive' %
               len(modules))
        return None
    return modules[x-1]

def main():
    cluster_option = ""
    if use_cluster_tests:
        cluster_option = \
"""  --cluster              Run --std tests, plus a cluster-wide network test
"""

    usage = \
"""Usage: %s [OPTIONS]

OPTIONS:
  -s                     Don't auto-run id tests
  -m <MODULE>            Run the given module
  -l <LOGDIR>            Directory for log files (defaults to
                         a new, temporary directory)
  --menu                 Present menu of tests to run
  --id-tests-in-menu     Include id tests in test menu
  --enable-unsafe-tests  Include unsafe (i.e destructive) tests in menu
  --enable-mfg-tests     Include manufacturing tests in menu
  --std                  Run standard suite of tests (no interaction required)
%s
  -v, --version          Show hwcheck library version only
  -h, --help             Show this text
  
If no options are specified, "isi_hw_check --std" is assumed
""" % (argslib.appname(), cluster_option)

    options = {
        'skip_identify':
        {'opts'       : ['s'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },

        'modules_to_run':
        {'opts'       : ['m:'],
         'default'    : [],
         'func_attrs' : argslib.modules_to_run_func(),
         },

        'id_in_menu':
        {'opts'       : ['id-tests-in-menu'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },

        'enable_unsafe_tests':
        {'opts'       : ['enable-unsafe-tests'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },
    
        'enable_mfg_tests':
        {'opts'       : ['enable-mfg-tests'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },
    
        'run_standard_tests':
        {'opts'       : ['std'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },

        'run_cluster_tests':
        {'opts'       : ['cluster'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },

        'show_menu':
        {'opts'       : ['menu'],
         'default'    : False,
         'func'       : argslib.true_func(),
         },

        'logdir':
        {'opts'       : ['l:'],
         'default'    : None,
         'func'       : str,
         },

        'debug_level':
        {'opts'       : ['debug-level='],
         'default'    : 0,
         'func'       : int,
         },

        'debug_unhandled_exceptions':
        {'opts'       : ['debug-unhandled-exceptions'],
         'default'    : False,
         'func'       : misc.set_debug_unhandled_exceptions,
         },

        'fake_stats_dir':
        {'opts'       : ['fake-stats='],
         'default'    : None,
         'func'       : str,
         },

        'version':
        {'opts'       : ['v', 'version'],
         'func'       : misc.hwcheck_version_func,
         },

        'help':
        {'opts'       : ['h', 'help'],
         'func'       : argslib.help_func(usage),
         },
        }
        
    return argslib.run(lambda :run(options), usage)


def run(options):
    def run_options_count(args):
        count = 0
        count += len(args['modules_to_run'])
        if args['run_standard_tests']:
            count += 1
        if args['run_cluster_tests']:
            count += 1
        if args['show_menu']:
            count += 1
        return count
    
    global log

    # Process cmd line args
    args = argslib.Args(options)
    non_opts = args.process(sys.argv[1:])    
    if len(non_opts) > 0:
        raise argslib.Error('too many arguments: %s' % ' '.join(non_opts))

    # Check to see if no module running options were specified. In
    # this case, just do --std tests.
    if run_options_count(args) == 0:
        args['run_standard_tests'] = True

    if not files.init():
        sys.exit(1)
    loglib.init(args['logdir'])

    log = loglib.get_logger()
    log.debug_level(args['debug_level'])
    log.out(time.asctime(time.localtime(log.start_time())))
    log.simple_out('')
    log.out('Logging to local directory: %s' % log.local_logdir())
    log.simple_out('')

    # Report Build (for easier debug of failures)
    build = misc.get_build()
    log.out('Build: %s' % build)
    log.simple_out('')

    # Set mode before calling modules
    misc.set_mode('hwc')

    if cto.supportsCto():
        cto_options = cto.get_options()
        cto_options['method'] = cto.CMDB_ACCESS_SKIP
        if args['debug_level']:
            cto_options['debug'] = args['debug_level']
        if args['debug_unhandled_exceptions']:
            if not cto_options['debug']:
                cto_options['debug'] = 1
        cto.cto_update(cto_options)
        log.simple_out('')

    stats.fail_on_error = False
    stats.log_stats(fake_stats_dir=args['fake_stats_dir'])
    log.simple_out('')

    #ramdisklib.init()

    # Run id tests
    if not args['skip_identify']:
        id_mods = misc.load_modules_from_dirs(id_dirs)
        log.exit_on_fail = 0
        for mod in id_mods:
            misc.run_module(mod)

    result = 0

    # Run "standard" tests, if requested
    if args['run_standard_tests']:
        misc.run_modules(standard_tests)
        
    # Run "cluster" tests, if requested
    if args['run_cluster_tests']:
        misc.run_modules(cluster_tests)

    # If any modules were specified on the command line, run them
    if len(args['modules_to_run']) > 0:
        misc.run_modules(args['modules_to_run'])

    # If we ran standard or cluster tests, and/or specific
    # modules, exit; otherwise, run the menu
    if args['show_menu']:
        
        fail_count = log.fail_count()
        if fail_count > 0:
            log.out('%d identification test%s failed' %
                    (fail_count, misc.plural_str(fail_count)), 'NOTE')
            log.simple_out('')

        # Run menu
        names_and_mdirs = []
        if args['id_in_menu']:
            names_and_mdirs.append(('Id Tests', id_dirs))
        names_and_mdirs.append(('Safe Tests', safe_dirs))
        if args['enable_unsafe_tests']:
            names_and_mdirs.append(('Unsafe Tests', unsafe_dirs))
        if args['enable_mfg_tests']:
            names_and_mdirs.append(('Manufacturing Tests', mfg_dirs))
        while 1:        
            module = menu(names_and_mdirs)                       
            if module != None:
                log.simple_out('')
                misc.run_module(module)

    result = run_final_cleanup(args)

    return result

def run_final_cleanup(args):
    log = loglib.get_logger()

    # Gather and log final stat info
    log.simple_out('')
    stats.log_stats(fake_stats_dir=args['fake_stats_dir'],
                    final_stats=True)

    # Final pass/fail status
    fail_count = log.fail_count()
    if fail_count > 0:
        result = 1
    else:
        result = 0

    # If we ran standard or cluster tests, and/or specific modules:
    if (args['run_standard_tests'] or args['run_cluster_tests'] or
        len(args['modules_to_run']) > 0):
        log.simple_out('')
        log.title('summary')
        if fail_count > 0:
            log.fail('Tests logged %d failure%s' %
                     (fail_count, misc.plural_str(fail_count)), 0)
        else:
            log.out('All tests succeeded', 'PASSED')

    return result
  
#
# Start running here
#
if __name__ == '__main__':
    sys.exit(main())
