#!/usr/bin/python2.4
#
#
# Copyright 2008, 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.

"""Parser for test definition xml files."""

# Python imports
import xml.dom.minidom
import xml.parsers

# local imports
import errors
import logger
from instrumentation_test import InstrumentationTestSuite
from native_test import NativeTestSuite
from host_test import HostTestSuite


class TestDefinitions(object):
  """Accessor for a test definitions xml file data.

  See test_defs.xsd for expected format.
  """

  def __init__(self):
    # dictionary of test name to tests
    self._testname_map = {}

  def __iter__(self):
    ordered_list = []
    for k in sorted(self._testname_map):
      ordered_list.append(self._testname_map[k])
    return iter(ordered_list)

  def Parse(self, file_path):
    """Parse the test suite data from from given file path.

    Args:
      file_path: absolute file path to parse
    Raises:
      ParseError if file_path cannot be parsed
    """
    try:
      doc = xml.dom.minidom.parse(file_path)
      self._ParseDoc(doc)
    except IOError:
      logger.Log("test file %s does not exist" % file_path)
      raise errors.ParseError
    except xml.parsers.expat.ExpatError:
      logger.Log("Error Parsing xml file: %s " %  file_path)
      raise errors.ParseError
    except errors.ParseError, e:
      logger.Log("Error Parsing xml file: %s Reason: %s" %  (file_path, e.msg))
      raise e

  def ParseString(self, xml_string):
    """Alternate parse method that accepts a string of the xml data."""
    doc = xml.dom.minidom.parseString(xml_string)
    # TODO: catch exceptions and raise ParseError
    return self._ParseDoc(doc)

  def _ParseDoc(self, doc):
    root_element = self._GetRootElement(doc)
    for element in root_element.childNodes:
      if element.nodeType != xml.dom.Node.ELEMENT_NODE:
        continue
      test_suite = None
      if element.nodeName == InstrumentationTestSuite.TAG_NAME:
        test_suite = InstrumentationTestSuite()
      elif element.nodeName == NativeTestSuite.TAG_NAME:
        test_suite = NativeTestSuite()
      elif element.nodeName == HostTestSuite.TAG_NAME:
        test_suite = HostTestSuite()
      else:
        logger.Log("Unrecognized tag %s found" % element.nodeName)
        continue
      test_suite.Parse(element)
      self._AddTest(test_suite)

  def _GetRootElement(self, doc):
    root_elements = doc.getElementsByTagName("test-definitions")
    if len(root_elements) != 1:
      error_msg = "expected 1 and only one test-definitions tag"
      raise errors.ParseError(msg=error_msg)
    return root_elements[0]

  def _AddTest(self, test):
    """Adds a test to this TestManifest.

    If a test already exists with the same name, it overrides it.

    Args:
      test: TestSuite to add
    """
    if self.GetTest(test.GetName()) is not None:
      logger.Log("Overriding test definition %s" % test.GetName())
    self._testname_map[test.GetName()] = test

  def GetTests(self):
    return self._testname_map.values()

  def GetContinuousTests(self):
    con_tests = []
    for test in self.GetTests():
      if test.IsContinuous():
        con_tests.append(test)
    return con_tests

  def GetCtsTests(self):
    """Return list of cts tests."""
    cts_tests = []
    for test in self.GetTests():
      if test.IsCts():
        cts_tests.append(test)
    return cts_tests

  def GetTest(self, name):
    return self._testname_map.get(name, None)


def Parse(file_path):
  """Parses out a TestDefinitions from given path to xml file.

  Args:
    file_path: string absolute file path
  Returns:
    a TestDefinitions object containing data parsed from file_path
  Raises:
    ParseError if xml format is not recognized
  """
  tests_result = TestDefinitions()
  tests_result.Parse(file_path)
  return tests_result