"""
:class:`GameCoordinator` is used to proxy messages from/to GC.
It takes care of the encapsulation details, but on its own is not
enough to communicate with a given GC.

Example implementation of Dota 2 GC client with inheritance.

.. code:: python

    import myDotaModule
    from steam.client import SteamClient
    from steam.core.msg import GCMsgHdrProto
    from steam.client.gc import GameCoordinator

    class ExampleDotaClient(GameCoordinator):
        def __init__(self, steam):
            GameCoordinator.__init__(self, steam, 570)

        def _process_gc_message(self, emsg, header, body):
            if emsg == 4004: # EMsgGCClientWelcome
                message = myDotaModule.gcsdk_gcmessages_pb2.CMsgClientWelcome()
                message.ParseFromString(body)
                print message

        def send_hello(self):
            header = GCMsgHdrProto(4006)  # EMsgGCClientHello
            body = myDotaModule.gcsdk_gcmessages_pb2.CMsgClientHello()
            self.send(header, body.SerializeToString())

    client = SteamClient()
    dota = ExampleDotaClient(client)

    client.login()
    client.games_played([570])
    dota.send_hello()

The above code assumes that we have a ``myDotaModule`` that contains the appropriate
protobufs needed to (de)serialize message for communication with GC.
"""
import logging

from eventemitter import EventEmitter
from steam.client import SteamClient
from steam.core.msg import GCMsgHdr, GCMsgHdrProto, MsgProto
from steam.enums.emsg import EMsg
from steam.utils.proto import set_proto_bit, clear_proto_bit, is_proto


class GameCoordinator(EventEmitter):
    """
    ``GameCoordinator`` is used to proxy messages from/to GC

    :param steam_client: steam client instance
    :type steam_client: :class:`steam.client.SteamClient`
    :param app_id: app id of the application
    :type app_id: :class:`int`

    Incoming messages are emitted as events using their ``EMsg`` as an event identifier.

    :param header: message header
    :type header: :class:`steam.core.msg.GCMsgHdr`
    :param body: raw message body
    :type body: :class:`bytes`
    """

    def __init__(self, steam_client, app_id):
        if not isinstance(steam_client, SteamClient):
            raise ValueError("Expected an instance of SteamClient as first argument")

        self.steam = steam_client
        self.app_id = app_id
        self._LOG = logging.getLogger("GC(appid:%d)" % app_id)

        # listen for GC messages
        self.steam.on(EMsg.ClientFromGC, self._handle_from_gc)

    def emit(self, event, *args):
        if event is not None:
            self._LOG.debug("Emit event: %s" % repr(event))
        EventEmitter.emit(self, event, *args)

    def send(self, header, body):
        """
        Send a message to GC

        :param header: message header
        :type header: :class:`steam.core.msg.GCMsgHdr`
        :param body: serialized body of the message
        :type body: :class:`bytes`
        """
        message = MsgProto(EMsg.ClientToGC)
        message.header.routing_appid = self.app_id
        message.body.appid = self.app_id
        message.body.msgtype = (set_proto_bit(header.msg)
                                if header.proto
                                else header.msg
                                )
        message.body.payload = header.serialize() + body
        self.steam.send(message)

    def _handle_from_gc(self, msg):
        if msg.body.appid != self.app_id:
            return

        emsg = msg.body.msgtype

        if is_proto(emsg):
            header = GCMsgHdrProto(emsg, msg.body.payload)
            header_size = GCMsgHdrProto._size + header.headerLength
        else:
            header = GCMsgHdr(emsg, msg.body.payload)
            header_size = GCMsgHdr._size

        body = msg.body.payload[header_size:]

        self._process_gc_message(clear_proto_bit(emsg), header, body)

    def _process_gc_message(self, emsg, header, body):
        self.emit(emsg, header, body)
