"""
mbed SDK
Copyright (c) 2011-2016 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.

Author: Azim Khan <Azim.Khan@arm.com>
"""

"""
This module contains classes to represent Test Specification interface that defines the data to be generated by/from
a build system to give enough information to Greentea.
"""

import json


class TestBinary:
    """
    Class representing a Test Binary.
    """
    KW_BIN_TYPE = "binary_type"
    KW_BIN_PATH = "path"

    BIN_TYPE_BOOTABLE = "bootable"
    BIN_TYPE_DEFAULT = BIN_TYPE_BOOTABLE
    SUPPORTED_BIN_TYPES = [BIN_TYPE_BOOTABLE]

    def __init__(self, path, binary_type):
        """
        ctor.

        :param path:
        :param binary_type:
        :return:
        """
        assert binary_type in TestBinary.SUPPORTED_BIN_TYPES, \
            "Binary type %s not supported. Supported types [%s]" % \
            (binary_type, ", ".join(TestBinary.SUPPORTED_BIN_TYPES))
        self.__path = path
        self.__flash_method = binary_type

    def get_path(self):
        """
        Gives binary path.
        :return:
        """
        return self.__path


class Test:
    """
    class representing a Test artifact that may contain more than one test binaries.
    """
    KW_TEST_NAME = "name"
    KW_TEST_BINS = "binaries"

    def __init__(self, name, default_flash_method=None):
        """
        ctor.

        :param name:
        :param default_flash_method:
        :return:
        """
        self.__name = name
        self.__default_flash_method = default_flash_method
        self.__binaries_by_flash_method = {}

    def get_name(self):
        """
        Gives test name.

        :return:
        """
        return self.__name

    def get_binary(self, binary_type=TestBinary.BIN_TYPE_DEFAULT):
        """
        Gives a test binary of specific flash type.

        :param binary_type:
        :return:
        """
        return self.__binaries_by_flash_method.get(binary_type, None)

    def parse(self, test_json):
        """
        Parse json contents into object.

        :param test_json:
        :return:
        """
        assert Test.KW_TEST_BINS in test_json, "Test spec should contain key `binaries`"
        for binary in test_json[Test.KW_TEST_BINS]:
            mandatory_keys = [TestBinary.KW_BIN_PATH]
            assert set(mandatory_keys).issubset(set(binary.keys())), \
                "Binary spec should contain key [%s]" % ",".join(mandatory_keys)
            fm = binary.get(TestBinary.KW_BIN_TYPE, self.__default_flash_method)
            assert fm is not None, "Binary type not specified in build and binary spec."
            tb = TestBinary(binary[TestBinary.KW_BIN_PATH], fm)
            self.__binaries_by_flash_method[fm] = tb

    def add_binary(self, path, binary_type):
        """
        Add binary to the test.

        :param path:
        :param binary_type:
        :return:
        """
        self.__binaries_by_flash_method[binary_type] = TestBinary(path, binary_type)


class TestBuild:
    """
    class for Test build.
    """
    KW_TEST_BUILD_NAME = "name"
    KW_PLATFORM = "platform"
    KW_TOOLCHAIN = "toolchain"
    KW_BAUD_RATE = "baud_rate"
    KW_BUILD_BASE_PATH = "base_path"
    KW_TESTS = "tests"
    KW_BIN_TYPE = "binary_type"

    def __init__(self, name, platform, toolchain, baud_rate, base_path, default_flash_method=None):
        """
        ctor.

        :param name:
        :param platform:
        :param toolchain:
        :param baud_rate:
        :param base_path:
        :param default_flash_method:
        :return:
        """
        self.__name = name
        self.__platform = platform
        self.__toolchain = toolchain
        self.__baud_rate = baud_rate
        self.__base_path = base_path
        self.__default_flash_method = default_flash_method
        self.__tests = {}

    def get_name(self):
        """
        Gives build name.

        :return:
        """
        return self.__name

    def get_platform(self):
        """
        Gives mbed classic platform name.

        :return:
        """
        return self.__platform

    def get_toolchain(self):
        """
        Gives toolchain

        :return:
        """
        return self.__toolchain

    def get_baudrate(self):
        """
        Gives baud rate.

        :return:
        """
        return self.__baud_rate

    def get_path(self):
        """
        Gives path.

        :return:
        """
        return self.__base_path

    def get_tests(self):
        """
        Gives tests dict keyed by test name.

        :return:
        """
        return self.__tests

    def parse(self, build_spec):
        """
        Parse Test build json.

        :param build_spec:
        :return:
        """
        assert TestBuild.KW_TESTS in build_spec, "Build spec should contain key '%s'" % TestBuild.KW_TESTS
        for name, test_json in build_spec[TestBuild.KW_TESTS].iteritems():
            test = Test(name, default_flash_method=self.__default_flash_method)
            test.parse(test_json)
            self.__tests[name] = test

    def add_test(self, name, test):
        """
        Add test.

        :param name:
        :param test:
        :return:
        """
        self.__tests[name] = test


class TestSpec:
    """
    Test specification. Contains Builds.
    """
    KW_BUILDS = "builds"
    test_spec_filename = 'runtime_load'

    def __init__(self, test_spec_filename=None):
        """
        ctor.

        :return:
        """
        self.__target_test_spec = {}
        if test_spec_filename:
            self.test_spec_filename = test_spec_filename
            self.load(self.test_spec_filename)

    def load(self, test_spec_filename):
        """
        Load test spec directly from file

        :param test_spec_filename: Name of JSON file with TestSpec to load
        :return: Treu if load was successful
        """
        try:
            with open(test_spec_filename, "r") as f:
                self.parse(json.load(f))
        except Exception as e:
            print "TestSpec::load('%s')"% test_spec_filename, str(e)
            return False

        self.test_spec_filename = test_spec_filename
        return True

    def parse(self, spec):
        """
        Parse test spec json.

        :param spec:
        :return:
        """
        assert TestSpec.KW_BUILDS, "Test spec should contain key '%s'" % TestSpec.KW_BUILDS
        for build_name, build in spec[TestSpec.KW_BUILDS].iteritems():
            mandatory_keys = [TestBuild.KW_PLATFORM, TestBuild.KW_TOOLCHAIN,
                              TestBuild.KW_BAUD_RATE,
                              TestBuild.KW_BUILD_BASE_PATH]
            assert set(mandatory_keys).issubset(set(build.keys())), \
                "Build spec should contain keys [%s]. It has [%s]" % (",".join(mandatory_keys), ",".join(build.keys()))
            platform = build[TestBuild.KW_PLATFORM]
            toolchain = build[TestBuild.KW_TOOLCHAIN]

            # If there is no 'name' property in build, we will use build key as build name
            name = build.get(TestBuild.KW_TEST_BUILD_NAME, build_name)

            tb = TestBuild(name, platform, toolchain,
                           build[TestBuild.KW_BAUD_RATE],
                           build[TestBuild.KW_BUILD_BASE_PATH],
                           build.get(TestBuild.KW_BIN_TYPE, None))
            tb.parse(build)
            self.__target_test_spec[name] = tb

    def get_test_builds(self, filter_by_names=None):
        """
        Gives test builds.
        :param filter_by_names: List of names of builds you want to filter in your result
        :return:
        """
        result = []
        if filter_by_names:
            assert type(filter_by_names) is list
            for tb in self.__target_test_spec.values():
                if tb.get_name() in filter_by_names:
                    result.append(tb)
        else:
            # When filtering by name is not defined we will return all builds objects
            result = self.__target_test_spec.values()
        return result

    def get_test_build(self, build_name):
        """
        Gives test build with given name.

        :param build_name:
        :return:
        """
        return self.__target_test_spec.get(build_name, None)

    def add_test_builds(self, name, test_build):
        """
        Add test build.

        :param name:
        :param test_build:
        :return:
        """
        self.__target_test_spec[name] = test_build
