#!/usr/bin/env python2.7
'''
dns_sprockets - A command-line tool to validate DNS zones.
----------------------------------------------------------
.. Copyright (c) 2015 Neustar, Inc. All rights reserved.
.. See COPYRIGHT.txt for full notice.  See LICENSE.txt for terms and conditions.
'''


import os
import time

from dns_sprockets_version import VERSION
import dns_sprockets_lib.utils as utils
import dns_sprockets_lib.loaders as loaders
import dns_sprockets_lib.loader_classes as loader_classes
import dns_sprockets_lib.validators as validators
import dns_sprockets_lib.validator_classes as validator_classes
from dns_sprockets_lib.dns_sprockets_impl import DNSSprocketsImpl


_PRODUCT = 'dns_sprockets'
_BANNER = '%s (%s) - A DNS Zone validation tool' % (_PRODUCT, VERSION)
_DEFAULT_ZONE = 'www.ultradns.com'
_DEFAULT_LOADER = 'xfr'
_DEFAULT_SOURCE = '127.0.0.1#53'
_FATAL_RETCODE = 255


def _run_main(avail_loaders, avail_tests, args):
    '''
    Constructs and runs a DNSSprocketsImpl instance.
    '''
    start_time = time.time()

    # Construct and run a DNSSprocketsImpl instance:
    try:
        (load_time, _, ret_code) = DNSSprocketsImpl(avail_loaders, avail_tests, args).run()
        total_time = time.time() - start_time
        print '# TOTAL ELAPSED TIME: %f SECS  LOAD TIME: %f SECS  TEST TIME: %f SECS' % (
            total_time, load_time, total_time - load_time)
        os._exit(ret_code >= _FATAL_RETCODE and _FATAL_RETCODE - 1 or ret_code)

    except StandardError as err:
        print 'FATAL: {%s} %s' % (err.__class__.__name__, err)
        if args.verbose:
            raise
        os._exit(_FATAL_RETCODE)


def run():
    '''
    Run the sprocket command-line application.
    '''
    # Print startup banner:
    print '#', _BANNER

    # Determine available loaders and tests:
    avail_loaders = utils.public_modules_in_package(loaders, ['tests'])
    avail_tests = utils.public_modules_in_package(validators, ['tests'])

    # Parse command-line:
    import argparse
    parser = argparse.ArgumentParser(
        epilog='''
Use @filename to read some/all arguments from a file.\n\n
Use -d's to define optional, module-specific parameters if desired (e.g. to tell
'xfr' loader to use a specific source address, use "-d xfr_source=1.2.3.4").
The optional parameters are listed under each loader and test description in
DEFINE lines, if available.\n\n
By default, all tests are run.  Use -i\'s to explicitly specify desired tests,
or -x\'s to eliminate undesired tests.\n\n
The list of available loaders is:
---------------------------------------------------------------------------\n%s\n\n
The list of available tests is:
---------------------------------------------------------------------------\n%s
            ''' % (loader_classes.get_formatted_descriptions(),
                   validator_classes.get_formatted_descriptions()),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        fromfile_prefix_chars='@')
    parser.add_argument('-z', '--zone', dest='zone',
        default=_DEFAULT_ZONE, metavar='s',
        help='Name of zone to validate [%(default)s]')
    parser.add_argument('-l', '--loader', dest='loader',
        default=_DEFAULT_LOADER, metavar='s', choices=avail_loaders,
        help='Zone loader method to use (one of: %(choices)s) [%(default)s]')
    parser.add_argument('-s', '--source', dest='source',
        default=_DEFAULT_SOURCE, metavar='s',
        help='Loader source to use [%(default)s]')
    parser.add_argument('-i', '--include', dest='include_tests',
        action='append', metavar='s', choices=avail_tests,
        help='Only include this test (can use multiple times)')
    parser.add_argument('-x', '--exclude', dest='exclude_tests',
        action='append', metavar='s', choices=avail_tests,
        help='Exclude this test (can use multiple times)')
    parser.add_argument('-d', '--define', dest='defines',
        action='append', metavar='s',
        help='Define other params (can use multiple times)')
    parser.add_argument('-f', '--force-dnssec-type', dest='force_dnssec_type',
        default='detect', metavar='s',
        choices=['detect', 'unsigned', 'NSEC', 'NSEC3'],
        help='Use DNSSEC type (one of: %(choices)s) [%(default)s]')
    parser.add_argument('-e', '--errors-only', dest='errors_only',
        default=False, action='store_true',
        help='Show validation errors only [%(default)s]')
    parser.add_argument('-v', '--verbose', dest='verbose',
        default=False, action='store_true',
        help='Show detailed processing info [%(default)s]')
    args = parser.parse_args()

    # Fix-up args:
    if args.include_tests is None:
        args.include_tests = []
    if args.exclude_tests is None:
        args.exclude_tests = []
    if args.defines is None:
        args.defines = []
    for sec_param in args.defines:
        (p_name, p_val) = sec_param.split('=')[:2]
        setattr(args, p_name, p_val)

    # Run the app:
    _run_main(avail_loaders, avail_tests, args)


if __name__ == '__main__':
    run()

# end of file
