#!/usr/bin/python
#
# Copyright (C) 2012 Intel Corporation
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# Authors:
#              Jing,Wang  <jing.j.wang@intel.com>
#              Yuanyuan,Zou  <zouyuanx@intel.com>
""" testkit lite tools"""

import os
import sys
import traceback
import platform
import ConfigParser
import xml.etree.ElementTree as etree
from optparse import OptionParser, make_option
from datetime import datetime

# import logger
try:
    from commodule.log import LOGGER
except ImportError, err:
    print "[ Error: loading logging failed, error: %s ]\n" % err
    print "try to run command " \
        "'export PYTHONPATH=/usr/lib/python2.7/dist-packages' and " \
        "'export PYTHONPATH=/usr/share/pyshared/' to resolve this issue"
    sys.exit(1)

# import  process kill
try:
    from testkitlite.common.process_killall import kill_testkit_lite, clean_testxml
except ImportError, err:
    LOGGER.error("[ Error: loading module killall failed, error: %s ]\n" % err)
    LOGGER.info("try to run command \
'export PYTHONPATH=/usr/lib/python2.7/dist-packages' and \
'export PYTHONPATH=/usr/share/pyshared/' to resolve this issue")
    sys.exit(1)

# get platform version info
os_ver = platform.system()
JOIN = os.path.join
EXISTS = os.path.exists
DIRNAME = os.path.dirname
BASENAME = os.path.basename
ABSPATH = os.path.abspath
SPLIT = os.path.split
ISLINK = os.path.islink

TESTKIT_DIR = "/opt/testkit/lite"
if not os_ver == "Linux" and not os_ver == "Darwin":
    TESTKIT_DIR = DIRNAME(ABSPATH(__file__))
    sys.path += [JOIN(TESTKIT_DIR)]
    TESTKIT_DIR = JOIN(TESTKIT_DIR, "results")

LOG_DIR = TESTKIT_DIR
PID_FILE = JOIN(LOG_DIR, "pid.log")
TEST_PACKAGES_DIR = JOIN(TESTKIT_DIR, "test_packages")
COMMON_FILTERS = {
    "suite": [],
    "set": [],
    "id": [],
    "type": [],
    "component": []}
down_status = False
remote_test = False
can_merge_result = False

# start testkit-lite in Singleton mode
kill_testkit_lite(PID_FILE)

if not os_ver == "Linux" and not os_ver == "Darwin":
    try:
        if not EXISTS(LOG_DIR):
            os.makedirs(LOG_DIR)
    except OSError, err:
        LOGGER.error("[ Error: create results directory:"
                     " %s failed, error: %s ]\n" % (LOG_DIR, err))

try:
    with open(PID_FILE, "w") as fd:
        PID = str(os.getpid())
        fd.writelines(PID + '\n')
except OSError, e:
    LOGGER.error("[ Error: can't create pid log file: %s, error: %s ]\n" %
                (PID_FILE, err))
    sys.exit(1)

try:
    os.chmod(PID_FILE, 0666)
except OSError:
    pass

# detect version option
if "--version" in sys.argv:
    try:
        CONFIG = ConfigParser.ConfigParser()
        if platform.system() == "Linux":
            CONFIG.read('/opt/testkit/lite/VERSION')
        else:
            VERSION_FILE = JOIN(sys.path[0], 'VERSION')
            CONFIG.read(VERSION_FILE)
        VERSION = CONFIG.get('public_version', 'version')
        LOGGER.info("V%s" % VERSION)
    except ConfigParser.Error, err:
        LOGGER.error(
            "[ Error: fail to parse version info, error: %s ]\n" % err)
    sys.exit(1)

# detect internal version option
if "--internal-version" in sys.argv:
    try:
        CONFIG = ConfigParser.ConfigParser()
        if platform.system() == "Linux":
            CONFIG.read('/opt/testkit/lite/VERSION')
        else:
            VERSION_FILE = JOIN(sys.path[0], 'VERSION')
            CONFIG.read(VERSION_FILE)
        VERSION = CONFIG.get('internal_version', 'version')
        print VERSION
    except ConfigParser.Error, err:
        print "[ Error: fail to parse version info, error: %s ]\n" % err
    sys.exit(1)


# init test engine here
try:
    from testkitlite.engines.default.runner import TRunner
    from commodule.connector import Connector
except ImportError, err:
    LOGGER.error("[ Error: loading test engine failed, error: %s ]\n" % err)
    LOGGER.info("try to run command "
                "'export PYTHONPATH=/usr/lib/python2.7/dist-packages' and "
                "'export PYTHONPATH=/usr/share/pyshared/'to resolve this issue")
    sys.exit(1)


def varnarg(option, opt_str, value, parser):
    """ parser srg"""
    value = []
    import re
    for arg in parser.rargs:
        if re.search('^--.+', arg) or \
                re.search('^-[\D]', arg):
            break
        value.append(arg)

    del parser.rargs[:len(value)]
    setattr(parser.values, option.dest, value)

try:
    OPTION_LIST = [
        make_option("-f", "--testxml", dest="testxml",
                    action="callback", callback=varnarg,
                    help="Specify the path of test definition file (tests.xml)."
                    " If run more one test package,just list the all the path "
                    " of \"tests.xml\" and separate with a whitespace"),
        make_option("-D", "--dryrun", dest="bdryrun",
                    action="store_true",
                    help="Dry-run the selected test cases"),
        make_option("-M", "--manual-only", dest="bmanualonly",
                    action="store_true",
                    help="Enable only manual tests"),
        make_option("-A", "--auto-only", dest="bautoonly",
                    action="store_true",
                    help="Enable only auto tests"),
        make_option("-o", "--output", dest="resultfile",
                    help="Specify output file for result xml. \
                    If more than one testxml provided, \
                    results will be merged together to this output file"),
        make_option("-e", dest="exttest", action="store",
                    help="Launch external test with a launcher,\
                         supports browser or other web-runtime"),
        make_option("--version", dest="version_info", action="store_true",
                    help="Show version information"),
        make_option("--internal-version", dest="internal_version_info",
                    action="store_true",
                    help="Show internal version information"),
        make_option("--deviceid", dest="device_serial", action="store",
                    help="set device serial information"),
        make_option("--testprefix", dest="test_prefix", action="store",
                    help="set prefix for test case entry"),
        make_option("--comm", dest="commodule", action="store",
                    help="set commodule by default,"
                    "set \"localhost\" for local web testing"),
        make_option("--capability", dest="capability", action="store",
                    help="set platform for sepecfic device capability"),
        make_option("--quit", dest="quit", action="store_true",
                    help="quit testkit-lite"),
        make_option("--debug", dest="debug", action="store_true",
                    help="run in debug mode,more log information print out"),
        make_option("--rerun", dest="rerun", action="store_true",
                    help="check if rerun test mode"),
        make_option("--non-active", dest="non_active", action="store_true",
                    help="Disable the ability to set the result of \
                    core manual cases from the console")

    ]

    OPTION_LIST.extend([
        make_option("--%s" % flt,
                    dest="w%s" % flt, action="callback", callback=varnarg,
                    help="Select the specified filter-rules : %s" % flt)
        for flt in COMMON_FILTERS])

    try:
        # untrusted behaviour of %%prog
        USAGE = "%%prog [options] -f [prefix:]\"<somewhere/test.xml>\" -e \
\"<launcher-name>\"\n\
forms:    %%prog  -f [prefix:]\"<somewhere>/test.xml\"\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -D\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -A\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -M\n\
	  %%prog  -f [prefix:]\"<somewhere>/test.xml\" --type \"smoke\"\n\
	  %%prog  -f [prefix:]\"<somewhere>/test.xml\" --component \"TizenAPI/Communication/Messaging\"\n\
          %%prog  -f [prefix:]\"<somewhere>/test1.xml <somewhere>/test2.xml \
<somewhere>/test3.xml\" \n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -D -A \ --capability <capability file> --comm <commoduletype>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -D -A \
...\n\
exmaples: \n\
    run a web test package from device side with device WRT (it is default): \n\
          %%prog -f device:\"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -e \
'WRTLauncher' -A \n\
    run a web test package from local path with chrome browser: \n\
          %%prog -f \"/usr/share/webapi-webkit-tests/tests.xml\" -e \
'<somewhere>/chrome-startup' -A --comm localhost \n\
\n\
Note: \n\
          1) Proxy settings should be disabled when execute webapi packages\n\
          2) TestLog is stored to %s/latest\n\
          3) %%prog enables both auto and manual tests by default\n\
          4) Obviously -A and -M are conflict options\n\
          5) -e option does not support -D mode\n\
          6) The test cases' order in the result files might be arbitrary,\
when running same tests.xml with same options. This is caused \
by python's API 'getiterator' from module 'xml.etree.ElementTree'\n\
          7) run command 'testkit-lite', \
            it might not be able to locate module 'testkitlite.engines.\
default.runner', \
            run command 'export PYTHONPATH=/usr/share/pyshared/' \
            run command 'export PYTHONPATH=/usr/lib/python2.7/dist-packages' \
            to resolve this issue" % (LOG_DIR)
    except Exception:
        USAGE = None

    # detect non-params
    if len(sys.argv) == 1:
        sys.argv.append("-h")

    PARSERS = OptionParser(option_list=OPTION_LIST, usage=USAGE)

    (OPTIONS, ARGS) = PARSERS.parse_args()
    # detect quit action
    if OPTIONS.quit:
        try:
            LOGGER.info("[ Quit testkit-lite now ]")
            kill_testkit_lite(PID_FILE)
        except Exception, err:
            LOGGER.error("[ Error: fail to kill existing testkit-lite,"
                         " error: %s ]\n" % err)
            sys.exit(1)

    # detect conflict
    if OPTIONS.bautoonly and OPTIONS.bmanualonly:
        raise ValueError("-A and -M are conflict")
    if OPTIONS.commodule:
        if OPTIONS.commodule == 'localhost' and "device:" in OPTIONS.testxml[0]:
            raise ValueError("For single mode, pls set local file to test ")
        COMMODULE_TYPE = OPTIONS.commodule
    else:
        COMMODULE_TYPE = "tizenmobile"

    CONNECTOR = Connector({"testmode": COMMODULE_TYPE, "deviceid":OPTIONS.device_serial}).get_connector()
    if CONNECTOR == None:
        LOGGER.error("[ Error: init commodule error... ]\n")
        sys.exit(1)

    # create runner
    RUNNER = TRunner(CONNECTOR)
    RUNNER.set_pid_log(PID_FILE)

    # apply all options
    RUNNER.set_global_parameters(OPTIONS)

    # set capability
    if OPTIONS.capability:
        GET_CAPABILITY_STATUS = RUNNER.get_capability(OPTIONS.capability)
        if not GET_CAPABILITY_STATUS:
            sys.exit(1)

    if "device:" in OPTIONS.testxml[0]:
        remote_test = True
        try:
            if not EXISTS(TEST_PACKAGES_DIR):
                os.makedirs(TEST_PACKAGES_DIR)
        except OSError, err:
            LOGGER.error("[ Error: "
                "can't create test package directory: %s, error: %s ]\n" %
                        (TEST_PACKAGES_DIR, err))
            sys.exit(1)
        REMOTE_TESTLITS = OPTIONS.testxml[0]
        REMOTE_TESTLITS = REMOTE_TESTLITS.split(':')[1]
        TESTLISTARRARY = REMOTE_TESTLITS.split()
        LOCALARRY = []

        for remote_file in TESTLISTARRARY:
            tmp_remote_file = SPLIT(remote_file)
            tmp_remote_folder = BASENAME(tmp_remote_file[0])
            tmp_remote_test_xml = JOIN(
                tmp_remote_folder, tmp_remote_file[1])
            local_test_package = JOIN(
                TEST_PACKAGES_DIR, tmp_remote_test_xml)
            down_status = RUNNER.connector.download_file(remote_file, local_test_package)
            if not down_status:
                LOGGER.error("can not get test definition file, pls check file on device:%s"
                             % remote_file)
                sys.exit(1)
            LOCALARRY.append(local_test_package)
        OPTIONS.testxml = LOCALARRY
    else:
        if len(OPTIONS.testxml) == 1:
            i = 0
            start = 0
            end = 0
            LOCAL_TESTLISTS= []
            temp_xml = OPTIONS.testxml[0]
            while(i < len(temp_xml)):
                tmp = temp_xml[i:len(temp_xml)]
                if ".xml" in tmp :
                    index = tmp.index(".xml")+4+i
                    end = index
                    i=index+1
                    LOCAL_TESTLISTS.append(temp_xml[start:end])
                    start = index+1
                else:
                    print 'no xml found'
                    break
            OPTIONS.testxml = LOCAL_TESTLISTS

    # apply filter
    WFILTERS = {}
    for flt in COMMON_FILTERS:
        if eval('OPTIONS.w%s' % flt):
            WFILTERS[flt] = eval('OPTIONS.w%s' % flt)
    RUNNER.add_filter_rules(**WFILTERS)

    if not OPTIONS.testxml:
        LOGGER.error("[ Error: not specify a test xml... ]\n")
        sys.exit(1)

    # 1) prepare log dir
    if os_ver == "Linux" or os_ver == "Darwin":
        SESSION = datetime.today().isoformat('-')
    else:
        SESSION = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
    CURRENT_LOG_DIR = JOIN(LOG_DIR, SESSION)
    LATEST_DIR = JOIN(LOG_DIR, "latest")
    try:
        if EXISTS(LATEST_DIR):
            os.remove(LATEST_DIR)
        if ISLINK(LATEST_DIR):
            os.remove(LATEST_DIR)
        os.makedirs(CURRENT_LOG_DIR)
        if os.name == "posix":
            os.symlink(CURRENT_LOG_DIR, LATEST_DIR)
    except IOError, err:
        LOGGER.error("[ Error: create session log directory: "
                     "%s failed, error: %s ]\n" % (CURRENT_LOG_DIR, err))

    # 2) prepare run test
    # run more than one tests.xml
    # 1. run all auto cases from the xmls
    # 2. run all manual cases from the xmls
    TESTXMLS = set(OPTIONS.testxml)
    for t in TESTXMLS:
        if EXISTS(t):
            filename = t
            filename = os.path.splitext(filename)[0]
            if os_ver == "Linux" or os_ver == "Darwin":
                if not filename.startswith('/'):
                    LOGGER.error("[ Error:"
                          " xml file %s should start with '/' ]" % filename)
                    sys.exit(1)
                else:
                    filename = filename.split('/')[-2]
                    if filename == "":
                        LOGGER.error("[ Error:"
                             " unable to find package name from %s ]" % t)
                        sys.exit(1)
            else:
                filename = filename.split('\\')[-2]
            filename = "%s.total" % BASENAME(filename)
            resultfile = "%s.xml" % filename
            resultfile = JOIN(CURRENT_LOG_DIR, resultfile)
            try:
                ep = etree.parse(t)
                suiteparent = ep.getroot()
            except etree.ParseError:
                LOGGER.error("[ Error: no case found in testxml, "
                             "pls check the test package ]\n")
                sys.exit(1)
            no_test_definition = 1
            for tf in ep.getiterator('test_definition'):
                no_test_definition = 0
            if no_test_definition:
                suiteparent = etree.Element('test_definition')
                suiteparent.tail = "\n"
                for suite in ep.getiterator('suite'):
                    suite.tail = "\n"
                    suiteparent.append(suite)
            if OPTIONS.bautoonly:
                WFILTERS['execution_type'] = ["auto"]
                RUNNER.add_filter_rules(**WFILTERS)
            if OPTIONS.bmanualonly:
                WFILTERS['execution_type'] = ["manual"]
                RUNNER.add_filter_rules(**WFILTERS)
            RUNNER.apply_filter(suiteparent)
            # just leave suite and set for merge result
            for suite in ep.getiterator('suite'):
                for tset in suite.getiterator('set'):
                    for testcase in tset.getiterator('testcase'):
                        tset.remove(testcase)
            try:
                with open(resultfile, 'w') as output:
                    tree = etree.ElementTree(element=suiteparent)
                    tree.write(output)
            except IOError, err:
                LOGGER.error(
                    "[ Error: create filtered total result file: %s failed, "
                    "error: %s ]\n" % (resultfile, err))
        else:
            print "[ Have no test xml found ]"
            sys.exit(1)
    START_TIME = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
    if not OPTIONS.bautoonly:
        if OPTIONS.bmanualonly:
            for t in TESTXMLS:
                try:
                    WFILTERS['execution_type'] = ["manual"]
                    RUNNER.add_filter_rules(**WFILTERS)
                    RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR)
                except IOError, err:
                    LOGGER.error("[ Error: prepare_run test xml: "
                      "%s from testkit-lite failed, error: %s ]\n" % (t, err))
        else:
            for t in TESTXMLS:
                try:
                    WFILTERS['execution_type'] = ["auto"]
                    RUNNER.add_filter_rules(**WFILTERS)
                    RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR)
                except IOError, err:
                    LOGGER.error("[ Error: prepare_run test xml: "
                      "%s from testkit-lite failed, error: %s ]\n" % (t, err))
            for t in TESTXMLS:
                try:
                    WFILTERS['execution_type'] = ["manual"]
                    RUNNER.add_filter_rules(**WFILTERS)
                    RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR)
                except IOError, err:
                    LOGGER.error("[ Error: prepare_run test xml: "
                      "%s from testkit-lite failed, error: %s ]\n" % (t, err))
    else:
        for t in TESTXMLS:
            try:
                WFILTERS['execution_type'] = ["auto"]
                RUNNER.add_filter_rules(**WFILTERS)
                RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR)
            except IOError, err:
                clean_testxml(TESTXMLS, remote_test)
                LOGGER.error("[ Error: prepare_run test xml: "
                  "%s from testkit-lite failed, error: %s ]\n" % (t, err))

    try:
        can_merge_result = True
        RUNNER.run_case(CURRENT_LOG_DIR)
    except Exception, err:
        clean_testxml(TESTXMLS, remote_test)
        traceback.print_exc()
        LOGGER.error("[ Error: run_test_case failed ,error: %s ]\n" % err)
    try:
        RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR)
        clean_testxml(TESTXMLS, remote_test)
        LOGGER.info("[ all tasks for testkit lite are accomplished, goodbye ]")
    except Exception, err:
        traceback.print_exc()
        clean_testxml(TESTXMLS,remote_test)
        LOGGER.error("[ Error: merge_resultfile "
                     "failed,error: %s ]\n" % err)

except KeyboardInterrupt, err:
    if RUNNER.session_id:
        RUNNER.finalize_test(RUNNER.session_id)
    if can_merge_result:
        RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR)
    if down_status:
        clean_testxml(OPTIONS.testxml, remote_test)
    LOGGER.info("\n[ exiting testkit-lite on user cancel ]\n")
    sys.exit(1)
except Exception, err:
    if RUNNER.session_id:
        RUNNER.finalize_test(RUNNER.session_id)
    if can_merge_result:
        RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR)
    if down_status:
        clean_testxml(OPTIONS.testxml, remote_test)
    LOGGER.error("\n[ Error: exiting testkit-lite "
                 "by catching a critical error: %s ]\n" % err)
    traceback.print_exc()
    sys.exit(1)
