#!/usr/bin/python2.7

# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Generate a CTS test XML file from a text file containing every single class#method per line
#
# For example, given an input file:
#
#          foo.txt:
#                com.android.ClassName#methodNameA
#                com.android.ClassName#methodNameB
#
# Will generate the output file:
#
#          TestPackage.xml:
#                <TestPackage>
#                  <TestSuite name="com">
#                    <TestSuite name="android">
#                      <TestCase name="ClassName">
#                        <Test name="methodNameA" />
#                        <Test name="methodNameB" />
#                      </TestCase>
#                    </TestSuite>
#                  </TestSuite>
#                </TestPackage>
#

import argparse
import sys

INDENTATION_INCREASE=2

class BaseNode(object):
    def __init__(self, name=None):
        self._children = []
        self._name = name
        self._properties = []

    def _get_children(self):
        return self._children

    def _set_children(self, value):
        self._children = value

    children = property(_get_children, _set_children, doc="Get/set list of children BaseNode")

    def append_child(self, child):
        self._children.append(child)

    def has_children(self):
        return not not self._children

    def _get_name(self):
        return self._name

    def _set_name(self, value):
        self._name = value

    name = property(_get_name, _set_name, doc="Get/set the name property of the current XML node")

    def _get_type_name(self):
        return type(self).__name__

    type_name = property(_get_type_name, doc="Get the name of the current XML node")

    def _set_properties(self, value):
        self._properties = value

    def _get_properties(self):
        return self._properties

    properties = property(_get_properties, _set_properties, doc="Get/set additional XML properties such as appPackageName (as a dict)")

    def write_xml(self, out, indent=0):
        out.write(' ' * indent)
        out.write('<' + self.type_name)

        if self.name is not None:
            out.write(' name="')
            out.write(self.name)
            out.write('"')

        if self.properties:
            for key, value in self.properties.iteritems():
                out.write(' ' + key + '="' + value + '"')

        if not self.has_children():
            out.write(' />')
            out.write('\n')
            return

        out.write('>\n')

        #TODO: print all the properties

        for child in self.children:
            child.write_xml(out, indent + INDENTATION_INCREASE)

        out.write(' ' * indent)
        out.write('</' + self.type_name + '>')
        out.write('\n')

class _SuiteContainer(BaseNode):
    def get_or_create_suite(self, package_list):
        debug_print("get_or_create_suite, package_list = " + str(package_list))
        debug_print("name = " + self.name)
        # If we are empty, then we just reached the TestSuite which we actually wanted. Return.
        if not package_list:
            return self

        current_package = package_list[0]
        rest_of_packages = package_list[1:]

        # If a suite already exists for the requested package, then have it look/create recursively.
        for child in self.children:
            if child.name == current_package:
                return child.get_or_create_suite(rest_of_packages)

        # No suite exists yet, create it recursively
        new_suite = TestSuite(name=current_package)
        self.append_child(new_suite)
        return new_suite.get_or_create_suite(rest_of_packages)

class TestPackage(_SuiteContainer):
    def add_class_and_method(self, fq_class_name, method):
        debug_print("add_class_and_method, fq_class_name=" + fq_class_name + ", method=" + method)
        package_list = fq_class_name.split(".")[:-1] # a.b.c -> ['a', 'b']
        just_class_name = fq_class_name.split(".")[-1] # a.b.c -> 'c'

        test_suite = self.get_or_create_suite(package_list)

        if test_suite == self:
            raise Exception("The suite cannot be the package")

        return test_suite.add_class_and_method(just_class_name, method)

class TestSuite(_SuiteContainer):
    def add_class_and_method(self, just_class_name, method_name):
        test_case = self.get_or_create_test_case(just_class_name)
        return test_case.add_method(method_name)

    def get_or_create_test_case(self, just_class_name):
        for child in self.children:
            if child.name == just_class_name:
                return child

        new_test_case = TestCase(name=just_class_name)
        self.append_child(new_test_case)
        return new_test_case

class TestCase(BaseNode):
    def add_method(self, method_name):
        tst = Test(name=method_name)
        self.append_child(tst)
        return tst

class Test(BaseNode):
    def __init__(self, name):
        super(Test, self).__init__(name)
        self._children = None

def debug_print(x):
    #print x
    pass

def build_xml_test_package(input, name, xml_properties):
    root = TestPackage(name=name)

    for line in input:
        class_and_method_name = line.split('#')
        fq_class_name = class_and_method_name[0].strip()
        method_name = class_and_method_name[1].strip()

        root.add_class_and_method(fq_class_name, method_name)

    root.properties = xml_properties
    return root

def write_xml(out, test_package):
    out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    test_package.write_xml(out)

def main():
    parser = argparse.ArgumentParser(description='Process a test methods list file to generate CTS test xml.')

    # Named required
    parser.add_argument('--cts-name', help="name (e.g. CtsJdwp)", required=True)
    parser.add_argument('--app-package-name', help="appPackageName (e.g. android.jdwp)", required=True)
    parser.add_argument('--jar-path', help="jarPath (e.g. CtsJdwp.jar)", required=True)

    # Named optionals
    parser.add_argument('--test-type', help="testType (default testNGDeviceTest)",
                        default="testNGDeviceTest")
    parser.add_argument('--runtime-args', help="runtimeArgs (e.g. -XXlib:libart.so)")
    parser.add_argument('--version', help="version (default 1.0)", default="1.0")

    # Positional optionals
    parser.add_argument('input-filename', nargs='?',
                               help='name of the cts test file (stdin by default)')
    parser.add_argument('output-filename', nargs='?',
                               help='name of the cts output file (stdout by default)')

    # Map named arguments into the xml <TestPackage> property key name
    argv_to_xml = {
            'app_package_name' : 'appPackageName',
            'jar_path' : 'jarPath',
            'test_type' : 'testType',
            'runtime_args' : 'runtimeArgs',
            'version' : 'version'
    }

    args = parser.parse_args()
    argv = vars(args) # convert Namespace to Dict

    xml_properties = {}
    for key, value in argv_to_xml.iteritems():
        if argv.get(key):
            xml_properties[value] = argv[key]

    debug_print(argv['input-filename'])
    debug_print(argv['output-filename'])

    name_in = argv['input-filename']
    name_out = argv['output-filename']

    file_in = name_in and open(name_in, "r") or sys.stdin
    file_out = name_out and open(name_out, "w+") or sys.stdout

    # read all the input
    test_package = build_xml_test_package(file_in, args.cts_name, xml_properties)
    # write all the output
    write_xml(file_out, test_package)

if __name__ == "__main__":
    main()