# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# This module's classes provide an interface to mojo modules. Modules are
# collections of interfaces and structs to be used by mojo ipc clients and
# servers.
#
# A simple interface would be created this way:
# module = mojom.generate.module.Module('Foo')
# interface = module.AddInterface('Bar')
# method = interface.AddMethod('Tat', 0)
# method.AddParameter('baz', 0, mojom.INT32)


class Kind(object):
  def __init__(self, spec=None):
    self.spec = spec
    self.parent_kind = None


class ReferenceKind(Kind):
  """ReferenceKind represents pointer types and handle types.
  A type is nullable if null (for pointer types) or invalid handle (for handle
  types) is a legal value for the type.
  """

  def __init__(self, spec=None, is_nullable=False):
    assert spec is None or is_nullable == spec.startswith('?')
    Kind.__init__(self, spec)
    self.is_nullable = is_nullable
    self.shared_definition = {}

  def MakeNullableKind(self):
    assert not self.is_nullable

    if self == STRING:
      return NULLABLE_STRING
    if self == HANDLE:
      return NULLABLE_HANDLE
    if self == DCPIPE:
      return NULLABLE_DCPIPE
    if self == DPPIPE:
      return NULLABLE_DPPIPE
    if self == MSGPIPE:
      return NULLABLE_MSGPIPE
    if self == SHAREDBUFFER:
      return NULLABLE_SHAREDBUFFER

    nullable_kind = type(self)()
    nullable_kind.shared_definition = self.shared_definition
    if self.spec is not None:
      nullable_kind.spec = '?' + self.spec
    nullable_kind.is_nullable = True

    return nullable_kind

  @classmethod
  def AddSharedProperty(cls, name):
    """Adds a property |name| to |cls|, which accesses the corresponding item in
       |shared_definition|.

       The reason of adding such indirection is to enable sharing definition
       between a reference kind and its nullable variation. For example:
         a = Struct('test_struct_1')
         b = a.MakeNullableKind()
         a.name = 'test_struct_2'
         print b.name  # Outputs 'test_struct_2'.
    """
    def Get(self):
      return self.shared_definition[name]

    def Set(self, value):
      self.shared_definition[name] = value

    setattr(cls, name, property(Get, Set))


# Initialize the set of primitive types. These can be accessed by clients.
BOOL                  = Kind('b')
INT8                  = Kind('i8')
INT16                 = Kind('i16')
INT32                 = Kind('i32')
INT64                 = Kind('i64')
UINT8                 = Kind('u8')
UINT16                = Kind('u16')
UINT32                = Kind('u32')
UINT64                = Kind('u64')
FLOAT                 = Kind('f')
DOUBLE                = Kind('d')
STRING                = ReferenceKind('s')
HANDLE                = ReferenceKind('h')
DCPIPE                = ReferenceKind('h:d:c')
DPPIPE                = ReferenceKind('h:d:p')
MSGPIPE               = ReferenceKind('h:m')
SHAREDBUFFER          = ReferenceKind('h:s')
NULLABLE_STRING       = ReferenceKind('?s', True)
NULLABLE_HANDLE       = ReferenceKind('?h', True)
NULLABLE_DCPIPE       = ReferenceKind('?h:d:c', True)
NULLABLE_DPPIPE       = ReferenceKind('?h:d:p', True)
NULLABLE_MSGPIPE      = ReferenceKind('?h:m', True)
NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True)


# Collection of all Primitive types
PRIMITIVES = (
  BOOL,
  INT8,
  INT16,
  INT32,
  INT64,
  UINT8,
  UINT16,
  UINT32,
  UINT64,
  FLOAT,
  DOUBLE,
  STRING,
  HANDLE,
  DCPIPE,
  DPPIPE,
  MSGPIPE,
  SHAREDBUFFER,
  NULLABLE_STRING,
  NULLABLE_HANDLE,
  NULLABLE_DCPIPE,
  NULLABLE_DPPIPE,
  NULLABLE_MSGPIPE,
  NULLABLE_SHAREDBUFFER
)


ATTRIBUTE_MIN_VERSION = 'MinVersion'


class NamedValue(object):
  def __init__(self, module, parent_kind, name):
    self.module = module
    self.namespace = module.namespace
    self.parent_kind = parent_kind
    self.name = name
    self.imported_from = None

  def GetSpec(self):
    return (self.namespace + '.' +
        (self.parent_kind and (self.parent_kind.name + '.') or "") +
        self.name)


class BuiltinValue(object):
  def __init__(self, value):
    self.value = value


class ConstantValue(NamedValue):
  def __init__(self, module, parent_kind, constant):
    NamedValue.__init__(self, module, parent_kind, constant.name)
    self.constant = constant


class EnumValue(NamedValue):
  def __init__(self, module, enum, field):
    NamedValue.__init__(self, module, enum.parent_kind, field.name)
    self.enum = enum

  def GetSpec(self):
    return (self.namespace + '.' +
        (self.parent_kind and (self.parent_kind.name + '.') or "") +
        self.enum.name + '.' + self.name)


class Constant(object):
  def __init__(self, name=None, kind=None, value=None, parent_kind=None):
    self.name = name
    self.kind = kind
    self.value = value
    self.parent_kind = parent_kind


class Field(object):
  def __init__(self, name=None, kind=None, ordinal=None, default=None,
               attributes=None):
    if self.__class__.__name__ == 'Field':
      raise Exception()
    self.name = name
    self.kind = kind
    self.ordinal = ordinal
    self.default = default
    self.attributes = attributes

  @property
  def min_version(self):
    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
        if self.attributes else None


class StructField(Field): pass


class UnionField(Field): pass


class Struct(ReferenceKind):
  ReferenceKind.AddSharedProperty('name')
  ReferenceKind.AddSharedProperty('module')
  ReferenceKind.AddSharedProperty('imported_from')
  ReferenceKind.AddSharedProperty('fields')
  ReferenceKind.AddSharedProperty('attributes')

  def __init__(self, name=None, module=None, attributes=None):
    if name is not None:
      spec = 'x:' + name
    else:
      spec = None
    ReferenceKind.__init__(self, spec)
    self.name = name
    self.module = module
    self.imported_from = None
    self.fields = []
    self.attributes = attributes

  def AddField(self, name, kind, ordinal=None, default=None, attributes=None):
    field = StructField(name, kind, ordinal, default, attributes)
    self.fields.append(field)
    return field


class Union(ReferenceKind):
  ReferenceKind.AddSharedProperty('name')
  ReferenceKind.AddSharedProperty('module')
  ReferenceKind.AddSharedProperty('imported_from')
  ReferenceKind.AddSharedProperty('fields')
  ReferenceKind.AddSharedProperty('attributes')

  def __init__(self, name=None, module=None, attributes=None):
    if name is not None:
      spec = 'x:' + name
    else:
      spec = None
    ReferenceKind.__init__(self, spec)
    self.name = name
    self.module = module
    self.imported_from = None
    self.fields = []
    self.attributes = attributes

  def AddField(self, name, kind, ordinal=None, attributes=None):
    field = UnionField(name, kind, ordinal, None, attributes)
    self.fields.append(field)
    return field


class Array(ReferenceKind):
  ReferenceKind.AddSharedProperty('kind')
  ReferenceKind.AddSharedProperty('length')

  def __init__(self, kind=None, length=None):
    if kind is not None:
      if length is not None:
        spec = 'a%d:%s' % (length, kind.spec)
      else:
        spec = 'a:%s' % kind.spec

      ReferenceKind.__init__(self, spec)
    else:
      ReferenceKind.__init__(self)
    self.kind = kind
    self.length = length


class Map(ReferenceKind):
  ReferenceKind.AddSharedProperty('key_kind')
  ReferenceKind.AddSharedProperty('value_kind')

  def __init__(self, key_kind=None, value_kind=None):
    if (key_kind is not None and value_kind is not None):
      ReferenceKind.__init__(self,
                             'm[' + key_kind.spec + '][' + value_kind.spec +
                             ']')
      if IsNullableKind(key_kind):
        raise Exception("Nullable kinds cannot be keys in maps.")
      if IsStructKind(key_kind):
        # TODO(erg): It would sometimes be nice if we could key on struct
        # values. However, what happens if the struct has a handle in it? Or
        # non-copyable data like an array?
        raise Exception("Structs cannot be keys in maps.")
      if IsAnyHandleKind(key_kind):
        raise Exception("Handles cannot be keys in maps.")
      if IsInterfaceKind(key_kind):
        raise Exception("Interfaces cannot be keys in maps.")
      if IsArrayKind(key_kind):
        raise Exception("Arrays cannot be keys in maps.")
    else:
      ReferenceKind.__init__(self)

    self.key_kind = key_kind
    self.value_kind = value_kind


class InterfaceRequest(ReferenceKind):
  ReferenceKind.AddSharedProperty('kind')

  def __init__(self, kind=None):
    if kind is not None:
      if not isinstance(kind, Interface):
        raise Exception(
            "Interface request requires %r to be an interface." % kind.spec)
      ReferenceKind.__init__(self, 'r:' + kind.spec)
    else:
      ReferenceKind.__init__(self)
    self.kind = kind


class AssociatedInterfaceRequest(ReferenceKind):
  ReferenceKind.AddSharedProperty('kind')

  def __init__(self, kind=None):
    if kind is not None:
      if not isinstance(kind, InterfaceRequest):
        raise Exception(
            "Associated interface request requires %r to be an interface "
            "request." % kind.spec)
      assert not kind.is_nullable
      ReferenceKind.__init__(self, 'asso:' + kind.spec)
    else:
      ReferenceKind.__init__(self)
    self.kind = kind.kind if kind is not None else None


class Parameter(object):
  def __init__(self, name=None, kind=None, ordinal=None, default=None,
               attributes=None):
    self.name = name
    self.ordinal = ordinal
    self.kind = kind
    self.default = default
    self.attributes = attributes

  @property
  def min_version(self):
    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
        if self.attributes else None


class Method(object):
  def __init__(self, interface, name, ordinal=None, attributes=None):
    self.interface = interface
    self.name = name
    self.ordinal = ordinal
    self.parameters = []
    self.response_parameters = None
    self.attributes = attributes

  def AddParameter(self, name, kind, ordinal=None, default=None,
                   attributes=None):
    parameter = Parameter(name, kind, ordinal, default, attributes)
    self.parameters.append(parameter)
    return parameter

  def AddResponseParameter(self, name, kind, ordinal=None, default=None,
                           attributes=None):
    if self.response_parameters == None:
      self.response_parameters = []
    parameter = Parameter(name, kind, ordinal, default, attributes)
    self.response_parameters.append(parameter)
    return parameter

  @property
  def min_version(self):
    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
        if self.attributes else None


class Interface(ReferenceKind):
  ReferenceKind.AddSharedProperty('module')
  ReferenceKind.AddSharedProperty('name')
  ReferenceKind.AddSharedProperty('imported_from')
  ReferenceKind.AddSharedProperty('methods')
  ReferenceKind.AddSharedProperty('attributes')

  def __init__(self, name=None, module=None, attributes=None):
    if name is not None:
      spec = 'x:' + name
    else:
      spec = None
    ReferenceKind.__init__(self, spec)
    self.module = module
    self.name = name
    self.imported_from = None
    self.methods = []
    self.attributes = attributes

  def AddMethod(self, name, ordinal=None, attributes=None):
    method = Method(self, name, ordinal, attributes)
    self.methods.append(method)
    return method

  # TODO(451323): Remove when the language backends no longer rely on this.
  @property
  def client(self):
    return None


class AssociatedInterface(ReferenceKind):
  ReferenceKind.AddSharedProperty('kind')

  def __init__(self, kind=None):
    if kind is not None:
      if not isinstance(kind, Interface):
        raise Exception(
            "Associated interface requires %r to be an interface." % kind.spec)
      assert not kind.is_nullable
      ReferenceKind.__init__(self, 'asso:' + kind.spec)
    else:
      ReferenceKind.__init__(self)
    self.kind = kind


class EnumField(object):
  def __init__(self, name=None, value=None, attributes=None,
               numeric_value=None):
    self.name = name
    self.value = value
    self.attributes = attributes
    self.numeric_value = numeric_value

  @property
  def min_version(self):
    return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
        if self.attributes else None


class Enum(Kind):
  def __init__(self, name=None, module=None, attributes=None):
    self.module = module
    self.name = name
    self.imported_from = None
    if name is not None:
      spec = 'x:' + name
    else:
      spec = None
    Kind.__init__(self, spec)
    self.fields = []
    self.attributes = attributes


class Module(object):
  def __init__(self, name=None, namespace=None, attributes=None):
    self.name = name
    self.path = name
    self.namespace = namespace
    self.structs = []
    self.unions = []
    self.interfaces = []
    self.kinds = {}
    self.attributes = attributes

  def AddInterface(self, name, attributes=None):
    interface = Interface(name, self, attributes)
    self.interfaces.append(interface)
    return interface

  def AddStruct(self, name, attributes=None):
    struct = Struct(name, self, attributes)
    self.structs.append(struct)
    return struct

  def AddUnion(self, name, attributes=None):
    union = Union(name, self, attributes)
    self.unions.append(union)
    return union


def IsBoolKind(kind):
  return kind.spec == BOOL.spec


def IsFloatKind(kind):
  return kind.spec == FLOAT.spec


def IsIntegralKind(kind):
  return (kind.spec == BOOL.spec or
          kind.spec == INT8.spec or
          kind.spec == INT16.spec or
          kind.spec == INT32.spec or
          kind.spec == INT64.spec or
          kind.spec == UINT8.spec or
          kind.spec == UINT16.spec or
          kind.spec == UINT32.spec or
          kind.spec == UINT64.spec)


def IsStringKind(kind):
  return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec


def IsGenericHandleKind(kind):
  return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec


def IsDataPipeConsumerKind(kind):
  return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec


def IsDataPipeProducerKind(kind):
  return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec


def IsMessagePipeKind(kind):
  return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec


def IsSharedBufferKind(kind):
  return (kind.spec == SHAREDBUFFER.spec or
          kind.spec == NULLABLE_SHAREDBUFFER.spec)


def IsStructKind(kind):
  return isinstance(kind, Struct)


def IsUnionKind(kind):
  return isinstance(kind, Union)


def IsArrayKind(kind):
  return isinstance(kind, Array)


def IsInterfaceKind(kind):
  return isinstance(kind, Interface)


def IsAssociatedInterfaceKind(kind):
  return isinstance(kind, AssociatedInterface)


def IsInterfaceRequestKind(kind):
  return isinstance(kind, InterfaceRequest)


def IsAssociatedInterfaceRequestKind(kind):
  return isinstance(kind, AssociatedInterfaceRequest)


def IsEnumKind(kind):
  return isinstance(kind, Enum)


def IsReferenceKind(kind):
  return isinstance(kind, ReferenceKind)


def IsNullableKind(kind):
  return IsReferenceKind(kind) and kind.is_nullable


def IsMapKind(kind):
  return isinstance(kind, Map)


def IsObjectKind(kind):
  return IsPointerKind(kind) or IsUnionKind(kind)


def IsPointerKind(kind):
  return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) or
          IsMapKind(kind))


# Please note that interface is not considered as handle kind, since it is an
# aggregate type consisting of a handle and a version number.
def IsAnyHandleKind(kind):
  return (IsGenericHandleKind(kind) or
          IsDataPipeConsumerKind(kind) or
          IsDataPipeProducerKind(kind) or
          IsMessagePipeKind(kind) or
          IsSharedBufferKind(kind) or
          IsInterfaceRequestKind(kind))


def IsAssociatedKind(kind):
  return (IsAssociatedInterfaceKind(kind) or
          IsAssociatedInterfaceRequestKind(kind))


def IsMoveOnlyKind(kind):
  return (not IsStringKind(kind) and IsObjectKind(kind)) or \
      IsAnyHandleKind(kind) or IsInterfaceKind(kind) or IsAssociatedKind(kind)


def IsCloneableKind(kind):
  def _IsCloneable(kind, visited_kinds):
    if kind in visited_kinds:
      # No need to examine the kind again.
      return True
    visited_kinds.add(kind)
    if IsAnyHandleKind(kind) or IsInterfaceKind(kind) or IsAssociatedKind(kind):
      return False
    if IsArrayKind(kind):
      return _IsCloneable(kind.kind, visited_kinds)
    if IsStructKind(kind) or IsUnionKind(kind):
      for field in kind.fields:
        if not _IsCloneable(field.kind, visited_kinds):
          return False
    if IsMapKind(kind):
      # No need to examine the key kind, only primitive kinds and non-nullable
      # string are allowed to be key kinds.
      return _IsCloneable(kind.value_kind, visited_kinds)
    return True

  return _IsCloneable(kind, set())


def HasCallbacks(interface):
  for method in interface.methods:
    if method.response_parameters != None:
      return True
  return False


# Finds out whether an interface passes associated interfaces and associated
# interface requests.
def PassesAssociatedKinds(interface):
  def _ContainsAssociatedKinds(kind, visited_kinds):
    if kind in visited_kinds:
      # No need to examine the kind again.
      return False
    visited_kinds.add(kind)
    if IsAssociatedKind(kind):
      return True
    if IsArrayKind(kind):
      return _ContainsAssociatedKinds(kind.kind, visited_kinds)
    if IsStructKind(kind) or IsUnionKind(kind):
      for field in kind.fields:
        if _ContainsAssociatedKinds(field.kind, visited_kinds):
          return True
    if IsMapKind(kind):
      # No need to examine the key kind, only primitive kinds and non-nullable
      # string are allowed to be key kinds.
      return _ContainsAssociatedKinds(kind.value_kind, visited_kinds)
    return False

  visited_kinds = set()
  for method in interface.methods:
    for param in method.parameters:
      if _ContainsAssociatedKinds(param.kind, visited_kinds):
        return True
    if method.response_parameters != None:
      for param in method.response_parameters:
        if _ContainsAssociatedKinds(param.kind, visited_kinds):
          return True
  return False