# Copyright 2019 Google LLC
#
# 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
#
#     https://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.

"""Utilities for test code."""

from compiler.util import ir_data_utils


def proto_is_superset(proto, expected_values, path=""):
  """Returns true if every value in expected_values is set in proto.

  This is intended to be used in assertTrue in a unit test, like so:

      self.assertTrue(*proto_is_superset(proto, expected))

  Arguments:
    proto: The proto to check.
    expected_values: The reference proto.
    path: The path to the elements being compared.  Clients can generally leave
      this at default.

  Returns:
    A tuple; the first element is True if the fields set in proto are a strict
    superset of the fields set in expected_values.  The second element is an
    informational string specifying the path of a value found in expected_values
    but not in proto.

    Every atomic field that is set in expected_values must be set to the same
    value in proto; every message field set in expected_values must have a
    matching field in proto, such that proto_is_superset(proto.field,
    expected_values.field) is true.

    For repeated fields in expected_values, each element in the expected_values
    proto must have a corresponding element at the same index in proto; proto
    may have additional elements.
  """
  if path:
    path += "."
  for spec, expected_value in ir_data_utils.get_set_fields(expected_values):
    name = spec.name
    field_path = "{}{}".format(path, name)
    value = getattr(proto, name)
    if spec.is_dataclass:
      if spec.is_sequence:
        if len(expected_value) > len(value):
          return False, "{}[{}] missing".format(field_path,
                                                len(getattr(proto, name)))
        for i in range(len(expected_value)):
          result = proto_is_superset(value[i], expected_value[i],
                                     "{}[{}]".format(field_path, i))
          if not result[0]:
            return result
      else:
        if (expected_values.HasField(name) and
            not proto.HasField(name)):
          return False, "{} missing".format(field_path)
        result = proto_is_superset(value, expected_value, field_path)
        if not result[0]:
          return result
    else:
      # Zero-length repeated fields and not-there repeated fields are "the
      # same."
      if (expected_value != value and
          (not spec.is_sequence or
           len(expected_value))):
        if spec.is_sequence:
          return False, "{} differs: found {}, expected {}".format(
              field_path, list(value), list(expected_value))
        else:
          return False, "{} differs: found {}, expected {}".format(
              field_path, value, expected_value)
  return True, ""


def dict_file_reader(file_dict):
  """Returns a callable that retrieves entries from file_dict as files.

  This can be used to call glue.parse_emboss_file with file text declared
  inline.

  Arguments:
      file_dict: A dictionary from "file names" to "contents."

  Returns:
      A callable that can be passed to glue.parse_emboss_file in place of the
      "read" builtin.
  """

  def read(file_name):
    try:
      return file_dict[file_name], None
    except KeyError:
      return None, ["File '{}' not found.".format(file_name)]

  return read
