"""
mbed SDK
Copyright (c) 2011-2013 ARM Limited

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.
"""
import sys
import argparse
import xml.etree.ElementTree as ET
import requests
import urlparse

def create_headers(args):
    return { 'X-Api-Key':  args.api_key }

def finish_command(command, response):
    print(command, response.status_code, response.reason)
    print(response.text)

    if response.status_code < 400:
        sys.exit(0)
    else:
        sys.exit(2)

def create_build(args):
    build = {}
    build['buildType'] = args.build_type
    build['number'] = args.build_number
    build['source'] = args.build_source
    build['status'] = 'running'

    if build['buildType'] == 'Pull_Request':
        build['buildType'] = 'Pull Request'

    r = requests.post(urlparse.urljoin(args.url, "api/builds"), headers=create_headers(args), json=build)

    if r.status_code < 400:
        if args.property_file_format:
            print("MBED_BUILD_ID=" + r.text)
        else:
            print(r.text)

        sys.exit(0)
    else:
        sys.exit(2)

def finish_build(args):
    data = {}
    data['status'] = 'completed'

    r = requests.put(urlparse.urljoin(args.url, "api/builds/" + args.build_id), headers=create_headers(args), json=data)
    finish_command('finish-build', r)

def abort_build(args):
    data = {}
    data['status'] = 'aborted'

    r = requests.put(urlparse.urljoin(args.url, "api/builds/" + args.build_id), headers=create_headers(args), json=data)
    finish_command('abort-build', r)

def add_project_runs(args):
    '''
    -------------------------------------
    Notes on 'project_run_data' structure:
    --------------------------------------
        'projectRuns' - Tree structure used to keep track of what projects have
            been logged in different report files. The tree is organized as follows:

            'projectRuns': {                - Root element of tree

                'hostOs': {                 - Host OS on which project was built/tested
                                                - ex. windows, linux, or mac

                    'platform': {           - Platform for which project was built/tested
                                              (Corresponds to platform names in targets.py)
                                                - ex. K64F, LPC1768, NRF51822, etc.

                        'toolchain': {      - Toolchain with which project was built/tested
                                              (Corresponds to TOOLCHAIN_CLASSES names in toolchains/__init__.py)
                                                - ex. ARM, uARM, GCC_ARM, etc.

                            'project': {    - Project that was build/tested
                                              (Corresponds to test id in tests.py or library id in libraries.py)
                                                - For tests, ex. MBED_A1, MBED_11, DTCT_1 etc.
                                                - For libraries, ex. MBED, RTX, RTOS, etc.

                            },
                            ...
                        },
                        ...
                    },
                    ...
                }
            }

        'platforms_set' - Set of all the platform names mentioned in the given report files

        'toolchains_set' - Set of all the toolchain names mentioned in the given report files

        'names_set' - Set of all the project names mentioned in the given report files

        'hostOses_set' - Set of all the host names given (only given by the command line arguments)
    '''

    project_run_data = {}
    project_run_data['projectRuns'] = {}
    project_run_data['platforms_set'] = set()
    project_run_data['toolchains_set'] = set()
    project_run_data['names_set'] = set()
    project_run_data['hostOses_set'] = set()
    project_run_data['hostOses_set'].add(args.host_os)

    add_report(project_run_data, args.build_report, True, args.build_id, args.host_os)

    if (args.test_report):
        add_report(project_run_data, args.test_report, False, args.build_id, args.host_os)

    ts_data = format_project_run_data(project_run_data)
    r = requests.post(urlparse.urljoin(args.url, "api/projectRuns"), headers=create_headers(args), json=ts_data)
    finish_command('add-project-runs', r)

def format_project_run_data(project_run_data):
    ts_data = {}
    ts_data['projectRuns'] = []

    for hostOs in project_run_data['projectRuns'].values():
        for platform in hostOs.values():
            for toolchain in platform.values():
                for project in toolchain.values():
                    ts_data['projectRuns'].append(project)

    ts_data['platforms'] = list(project_run_data['platforms_set'])
    ts_data['toolchains'] = list(project_run_data['toolchains_set'])
    ts_data['names'] = list(project_run_data['names_set'])
    ts_data['hostOses'] = list(project_run_data['hostOses_set'])

    return ts_data

def find_project_run(projectRuns, project):
    keys = ['hostOs', 'platform', 'toolchain', 'project']

    elem = projectRuns

    for key in keys:
        if not project[key] in elem:
            return None

        elem = elem[project[key]]

    return elem

def add_project_run(projectRuns, project):
    keys = ['hostOs', 'platform', 'toolchain']

    elem = projectRuns

    for key in keys:
        if not project[key] in elem:
            elem[project[key]] = {}

        elem = elem[project[key]]

    elem[project['project']] = project

def update_project_run_results(project_to_update, project):
    project_to_update['pass'] = project['pass']
    project_to_update['result'] = project['result']

    if 'buildOutput' in project:
        project_to_update['buildOutput'] = project['buildOutput']
    else:
        project_to_update['testOutput'] = project['testOutput']

def update_project_run(projectRuns, project):
    found_project = find_project_run(projectRuns, project)
    if found_project:
        update_project_run_results(found_project, project)
    else:
        add_project_run(projectRuns, project)

def add_report(project_run_data, report_file, is_build, build_id, host_os):
    tree = None

    try:
        tree = ET.parse(report_file)
    except:
        print(sys.exc_info()[0])
        print('Invalid path to report: %s', report_file)
        sys.exit(1)

    test_suites = tree.getroot()

    for test_suite in test_suites:
        platform = ""
        toolchain = ""
        for properties in test_suite.findall('properties'):
            for property in properties.findall('property'):
                if property.attrib['name'] == 'target':
                    platform = property.attrib['value']
                    project_run_data['platforms_set'].add(platform)
                elif property.attrib['name'] == 'toolchain':
                    toolchain = property.attrib['value']
                    project_run_data['toolchains_set'].add(toolchain)

        for test_case in test_suite.findall('testcase'):
            projectRun = {}
            projectRun['build'] = build_id
            projectRun['hostOs'] = host_os
            projectRun['platform'] = platform
            projectRun['toolchain'] = toolchain
            projectRun['project'] = test_case.attrib['classname'].split('.')[-1]

            project_run_data['names_set'].add(projectRun['project'])

            skipped = test_case.findall('skipped')

            if not skipped:
                system_outs = test_case.findall('system-out')

                output = ""
                if system_outs:
                    output = system_outs[0].text

                if is_build:
                    projectRun['buildOutput'] = output
                else:
                    projectRun['testOutput'] = output

                errors = test_case.findall('error')
                failures = test_case.findall('failure')

                if errors:
                    projectRun['pass'] = False
                    projectRun['result'] = errors[0].attrib['message']
                elif failures:
                    projectRun['pass'] = False
                    projectRun['result'] = failures[0].attrib['message']
                else:
                    projectRun['pass'] = True
                    projectRun['result'] = 'OK'

                update_project_run(project_run_data['projectRuns'], projectRun)

def main(arguments):
    # Register and parse command line arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--url', required=True, help='url to ci site')
    parser.add_argument('-k', '--api-key', required=True, help='api-key for posting data')

    subparsers = parser.add_subparsers(help='subcommand help')

    create_build_parser = subparsers.add_parser('create-build', help='create a new build')
    create_build_parser.add_argument('-b', '--build-number', required=True, help='build number')
    create_build_parser.add_argument('-T', '--build-type', choices=['Nightly', 'Limited', 'Pull_Request'], required=True, help='type of build')
    create_build_parser.add_argument('-s', '--build-source', required=True, help='url to source of build')
    create_build_parser.add_argument('-p', '--property-file-format', action='store_true', help='print result in the property file format')
    create_build_parser.set_defaults(func=create_build)

    finish_build_parser = subparsers.add_parser('finish-build', help='finish a running build')
    finish_build_parser.add_argument('-b', '--build-id', required=True, help='build id')
    finish_build_parser.set_defaults(func=finish_build)

    abort_build_parser = subparsers.add_parser('abort-build', help='abort a running build')
    abort_build_parser.add_argument('-b', '--build-id', required=True, help='build id')
    abort_build_parser.set_defaults(func=abort_build)

    add_project_runs_parser = subparsers.add_parser('add-project-runs', help='add project runs to a build')
    add_project_runs_parser.add_argument('-b', '--build-id', required=True, help='build id')
    add_project_runs_parser.add_argument('-r', '--build-report', required=True, help='path to junit xml build report')
    add_project_runs_parser.add_argument('-t', '--test-report', required=False, help='path to junit xml test report')
    add_project_runs_parser.add_argument('-o', '--host-os', required=True, help='host os on which test was run')
    add_project_runs_parser.set_defaults(func=add_project_runs)

    args = parser.parse_args(arguments)
    args.func(args)

if __name__ == '__main__':
    main(sys.argv[1:])
