#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2012 Canonical
# Author: Thomi Richards
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.

from __future__ import absolute_import

from datetime import datetime
from imp import find_module
import os
import os.path
from os import remove, putenv
from platform import node
from subprocess import call
import sys
from tempfile import NamedTemporaryFile
from testtools import iterate_tests
from textwrap import dedent
from argparse import ArgumentParser
from unittest.loader import TestLoader
from unittest.runner import TextTestRunner
from unittest import TestSuite


# list autopilot depends here, with the form:
# ('python module name', 'ubuntu package name'),
DEPENDS = [
    ('compizconfig', 'python-compizconfig'),
    ('dbus', 'python-dbus'),
    ('gconf', 'python-gconf'),
    ('gobject', 'python-gobject'),
    ('gtk', 'python-gtk2'),
    ('ibus', 'python-ibus'),
    ('junitxml', 'python-junitxml'),
    ('testscenarios', 'python-testscenarios'),
    ('testtools', 'python-testtools'),
    ('xdg', 'python-xdg'),
    ('Xlib', 'python-xlib'),
    ('pydot', 'python-pydot'),
]


def check_depends():
    """Check for required dependancies, and print a helpful message if any are
    missing.

    If all required modules are present, return True, False otherwise.
    """
    missing = []
    for module_name, package_name in DEPENDS:
        try:
            find_module(module_name)
        except ImportError:
            missing.append(package_name)
    if missing:
        print dedent("""\
            You are missing one or more packages required to run autopilot.
            They are:

            %s

            Please install these packages and re-run this script.
            """ % (' '.join(missing))
            )
        return False
    return True


def parse_arguments():
    """Parse command-line arguments, and return an argparse arguments
    object.
    """
    parser = ArgumentParser(description=dedent("""\
        Autopilot test tool.
        """))
    subparsers = parser.add_subparsers(help='Run modes', dest="mode")

    parser_run = subparsers.add_parser('run', help="Run autopilot tests")
    parser_run.add_argument('-o', "--output", required=False,
                            help='Write test result report to file.\
                            Defaults to stdout.\
                            If given a directory instead of a file will \
                            write to a file in that directory named: \
                            <hostname>_<dd.mm.yyy_HHMMSS>.log')
    parser_run.add_argument('-f', "--format", choices=['text', 'xml'],
                            default='text',
                            required=False,
                            help='Specify desired output format. \
                            Default is "text".')
    parser_run.add_argument('-r', '--record', action='store_true',
                            default=False, required=False,
                            help="Record failing tests. Required \
                            'recordmydesktop' app to be installed.\
                            Videos are stored in /tmp/autopilot.")
    parser_run.add_argument("-rd", "--record-directory", required=False,
                            default="/tmp/autopilot", type=str,
                            help="Directory to put recorded tests \
                            (only if -r) specified.")
    parser_run.add_argument('-v', '--verbose', default=False, required=False,
                            action='store_true',
                            help="If set, autopilot will output test log data \
                            to stderr during a test run.")
    parser_run.add_argument("suite", nargs="+",
                            help="Specify test suite(s) to run.")

    parser_list = subparsers.add_parser('list', help="List autopilot tests")
    parser_list.add_argument("-ro", "--run-order", required=False, default=False,
                            action="store_true",
                            help="List tests in run order, rather than alphabetical \
                            order (the default).")
    parser_list.add_argument("suite", nargs="+",
                             help="Specify test suite(s) to run.")

    parser_vis = subparsers.add_parser('vis',
                                      help="Create introspection visualisation.\
                                      Opens visualisation in xdot when not\
                                      passed an output path to write file to.")
    args = parser.parse_args()

    return args


def list_tests(args):
    """Print a list of tests we find inside autopilot.tests."""
    num_tests = 0
    test_suite = load_test_suite_from_name(args.suite)

    if args.run_order:
        test_list_fn = lambda: iterate_tests(test_suite)
    else:
        test_list_fn = lambda: sorted(iterate_tests(test_suite),
            lambda a, b: cmp(a.id(), b.id()))

    for test in test_list_fn():
        has_scenarios = hasattr(test, "scenarios") and type(test.scenarios) is list
        if has_scenarios:
            num_tests += len(test.scenarios)
            print " *%d %s" % (len(test.scenarios), test.id())
        else:
            num_tests += 1
            print "   ", test.id()
    print "\n\n %d total tests." % (num_tests)


def run_tests(args):
    """Run tests, using input from `args`."""
    import junitxml
    import autopilot.globals

    if args.record:
        autopilot.globals.video_recording_enabled = True
        autopilot.globals.video_record_directory = args.record_directory
    if args.verbose:
        autopilot.globals.log_verbose = True

    test_suite = load_test_suite_from_name(args.suite)

    if args.output == None:
        results_stream = sys.stdout
    else:
        try:
            path = os.path.dirname(args.output)
            if path != '' and not os.path.exists(path):
                os.makedirs(path)
            log_file = args.output
            if os.path.isdir(log_file):
                default_log = "%s_%s.log" % (node(),
                                             datetime.now().strftime("%d.%m.%y-%H%M%S"))
                log_file = os.path.join(log_file, default_log)
                print "Using default log filename: %s " % default_log
            results_stream = open(log_file, 'w')
        except:
            results_stream = sys.stdout
    if args.format == "xml":
        result = junitxml.JUnitXmlResult(results_stream)
        result.startTestRun()
        test_suite.run(result)
        result.stopTestRun()
        results_stream.close()
        if not result.wasSuccessful:
            exit(1)
    elif args.format == "text":
        runner = TextTestRunner(stream=results_stream)
        success = runner.run(test_suite).wasSuccessful()
        if not success:
            exit(1)


def load_test_suite_from_name(test_names):
    """Returns a test suite object given a dotted test names."""
    loader = TestLoader()
    if isinstance(test_names, basestring):
        test_names = list(test_names)
    elif not isinstance(test_names, list):
        raise TypeError("test_names must be either a string or list, not %r"
                        % (type(test_names)))

    tests = []
    for test_name in test_names:
        top_level_pkg = test_name.split('.')[0]
        package = __import__(top_level_pkg)
        package_parent_path = os.path.abspath(
            os.path.join(
                os.path.dirname(package.__file__),
                '..'
                )
            )
        tests.append(loader.discover(top_level_pkg, top_level_dir=package_parent_path))
    all_tests = TestSuite(tests)

    requested_tests = {}
    for test in iterate_tests(all_tests):
        # The test loader returns tests that start with 'unittest.loader' if for
        # whatever reason the test failed to load. We run the tests without the
        # built-in exception catching turned on, so we can get at the raised
        # exception, which we print, so the user knows that something in their
        # tests is broken.
        if test.id().startswith('unittest.loader'):
            try:
                test.debug()
            except Exception as e:
                print e
        elif any([test.id().startswith(name) for name in test_names]):
            requested_tests[test.id()] = test

    return TestSuite(requested_tests.values())


def main():
    args = parse_arguments()
    if args.mode == 'list':
        list_tests(args)
    elif args.mode == 'run':
        run_tests(args)
    elif args.mode == 'vis':
        # importing this requires that DISPLAY is set. Since we don't always want
        # that requirement, do the import here:
        from autopilot.vis import vis_main

        # XXX - in quantal, overlay scrollbars make this process consume 100% of
        # the CPU. It's a known bug:
        #
        # https://bugs.launchpad.net/ubuntu/quantal/+source/qt4-x11/+bug/1005677
        #
        # Once that's been fixed we can remove the following line:
        #
        putenv('LIBOVERLAY_SCROLLBAR', '0')
        vis_main()


if __name__ == "__main__":
    if not check_depends():
        exit(1)
    main()
