# Copyright 2015 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.

"""
The metaclasses used by the mojo python bindings for interfaces.

It is splitted from mojo_bindings.reflection because it uses some generated code
that would create a cyclic dependency.
"""

import logging
import sys

# pylint: disable=F0401
import interface_control_messages_mojom
import mojo_bindings.messaging as messaging
import mojo_bindings.promise as promise
import mojo_bindings.reflection as reflection
import mojo_bindings.serialization as serialization
import mojo_system


class MojoInterfaceType(type):
  """Meta class for interfaces.

  Usage:
    class MyInterface(object):
      __metaclass__ = MojoInterfaceType
      DESCRIPTOR = {
        'fully_qualified_name': 'service::MyInterface'
        'version': 3,
        'methods': [
          {
            'name': 'FireAndForget',
            'ordinal': 0,
            'parameters': [
              SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
            ]
          },
          {
            'name': 'Ping',
            'ordinal': 1,
            'parameters': [
              SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
            ],
            'responses': [
              SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
            ],
          },
        ],
      }
  """

  def __new__(mcs, name, bases, dictionary):
    # If one of the base class is already an interface type, do not edit the
    # class.
    for base in bases:
      if isinstance(base, mcs):
        return type.__new__(mcs, name, bases, dictionary)

    descriptor = dictionary.pop('DESCRIPTOR', {})

    methods = [_MethodDescriptor(x) for x in descriptor.get('methods', [])]
    for method in methods:
      dictionary[method.name] = _NotImplemented
    fully_qualified_name = descriptor['fully_qualified_name']

    interface_manager = InterfaceManager(
        fully_qualified_name, descriptor['version'], methods)
    dictionary.update({
        'manager': None,
        '_interface_manager': interface_manager,
    })

    interface_class = type.__new__(mcs, name, bases, dictionary)
    interface_manager.interface_class = interface_class
    return interface_class

  @property
  def manager(cls):
    return cls._interface_manager

  # Prevent adding new attributes, or mutating constants.
  def __setattr__(cls, key, value):
    raise AttributeError('can\'t set attribute')

  # Prevent deleting constants.
  def __delattr__(cls, key):
    raise AttributeError('can\'t delete attribute')


class InterfaceManager(object):
  """
  Manager for an interface class. The manager contains the operation that allows
  to bind an implementation to a pipe, or to generate a proxy for an interface
  over a pipe.
  """

  def __init__(self, name, version, methods):
    self.name = name
    self.version = version
    self.methods = methods
    self.interface_class = None
    self._proxy_class = None
    self._stub_class = None

  def Proxy(self, handle, version=0):
    router = messaging.Router(handle)
    error_handler = _ProxyErrorHandler()
    router.SetErrorHandler(error_handler)
    router.Start()
    return self._InternalProxy(router, error_handler, version)

  # pylint: disable=W0212
  def Bind(self, impl, handle):
    router = messaging.Router(handle)
    router.SetIncomingMessageReceiver(self._Stub(impl))
    error_handler = _ProxyErrorHandler()
    router.SetErrorHandler(error_handler)

    # Retain the router, until an error happen.
    retainer = _Retainer(router)
    def Cleanup(_):
      retainer.release()
    error_handler.AddCallback(Cleanup)

    # Give an instance manager to the implementation to allow it to close
    # the connection.
    impl.manager = InstanceManager(self, router, error_handler)

    router.Start()

  def NewRequest(self):
    pipe = mojo_system.MessagePipe()
    return (self.Proxy(pipe.handle0), reflection.InterfaceRequest(pipe.handle1))

  def _InternalProxy(self, router, error_handler, version):
    if error_handler is None:
      error_handler = _ProxyErrorHandler()

    if not self._proxy_class:
      dictionary = {
        '__module__': __name__,
        '__init__': _ProxyInit,
      }
      for method in self.methods:
        dictionary[method.name] = _ProxyMethodCall(method)
      self._proxy_class = type(
          '%sProxy' % self.name,
          (self.interface_class, reflection.InterfaceProxy),
          dictionary)

    proxy = self._proxy_class(router, error_handler)
    # Give an instance manager to the proxy to allow to close the connection.
    proxy.manager = ProxyInstanceManager(
        self, proxy, router, error_handler, version)
    return proxy

  def _Stub(self, impl):
    if not self._stub_class:
      accept_method = _StubAccept(self.methods)
      dictionary = {
        '__module__': __name__,
        '__init__': _StubInit,
        'Accept': accept_method,
        'AcceptWithResponder': accept_method,
      }
      self._stub_class = type('%sStub' % self.name,
                              (messaging.MessageReceiverWithResponder,),
                              dictionary)
    return self._stub_class(impl)


class InstanceManager(object):
  """
  Manager for the implementation of an interface or a proxy. The manager allows
  to control the connection over the pipe.
  """
  def __init__(self, interface_manager, router, error_handler):
    self.interface_manager = interface_manager
    self._router = router
    self._error_handler = error_handler
    assert self._error_handler is not None

  def Close(self):
    self._error_handler.OnClose()
    self._router.Close()

  def PassMessagePipe(self):
    self._error_handler.OnClose()
    return self._router.PassMessagePipe()

  def AddOnErrorCallback(self, callback):
    self._error_handler.AddCallback(lambda _: callback(), False)


class ProxyInstanceManager(InstanceManager):
  """
  Manager for the implementation of a proxy. The manager allows to control the
  connection over the pipe.
  """
  def __init__(self, interface_manager, proxy, router, error_handler, version):
    super(ProxyInstanceManager, self).__init__(