import pickle
import math

import network

# Set this to True if you want the bytes sent and received to be added
# symbolically, in terms of the numbers of each type of network message.
# You will need sympy installed for this to work.
symbolic_byte_counters = False

try:
    import sympy
except:
    pass

class NetMsg:
    """The parent class of network messages.  Subclass this class to
    implement specific kinds of network messages."""

    def size(self):
        """Return the size of this network message.  For now, just
        pickle it and return the length of that.  There's some
        unnecessary overhead in this method; if you want specific
        messages to have more accurate sizes, override this method in
        the subclass.  Alternately, if symbolic_byte_counters is set,
        return a symbolic representation of the message size instead, so
        that the total byte counts will clearly show how many of each
        message type were sent and received."""
        if symbolic_byte_counters:
            sz = sympy.symbols(type(self).__name__)
        else:
            sz = len(pickle.dumps(self))
            # logging.info("%s size %d", type(self).__name__, sz)

        return sz


class StringNetMsg(NetMsg):
    """Send an arbitratry string as a NetMsg."""
    def __init__(self, data):
        self.data = data

    def __str__(self):
        return self.data.__str__()

#########################
# DIRAUTH
#########################

class DirAuthNetMsg(NetMsg):
    """The subclass of NetMsg for messages to and from directory
    authorities."""


class DirAuthUploadDescMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for uploading a relay
    descriptor."""

    def __init__(self, desc):
        self.desc = desc


class DirAuthDelDescMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for deleting a relay
    descriptor."""

    def __init__(self, desc):
        self.desc = desc


class DirAuthGetConsensusMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for fetching the consensus."""


class DirAuthConsensusMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for returning the consensus."""

    def __init__(self, consensus):
        self.consensus = consensus


class DirAuthGetConsensusDiffMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for fetching the consensus, if the
    requestor already has the previous consensus."""


class DirAuthConsensusDiffMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for returning the consensus, if the
    requestor already has the previous consensus.  We don't _actually_
    produce the diff at this time; we just charge fewer bytes for this
    message."""

    def __init__(self, consensus):
        self.consensus = consensus

    def size(self):
        if symbolic_byte_counters:
            return super().size()
        return math.ceil(DirAuthConsensusMsg(self.consensus).size() \
                            * network.P_Delta)

class DirAuthGetENDIVEMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for fetching the ENDIVE."""


class DirAuthENDIVEMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for returning the ENDIVE."""

    def __init__(self, endive):
        self.endive = endive


class DirAuthGetENDIVEDiffMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for fetching the ENDIVE, if the
    requestor already has the previous ENDIVE."""


class DirAuthENDIVEDiffMsg(DirAuthNetMsg):
    """The subclass of DirAuthNetMsg for returning the ENDIVE, if the
    requestor already has the previous consensus.  We don't _actually_
    produce the diff at this time; we just charge fewer bytes for this
    message in Merkle mode.  In threshold signature mode, we would still
    need to download at least the new signatures for every SNIP in the
    ENDIVE, so for now, just assume there's no gain from ENDIVE diffs in
    threshold signature mode."""

    def __init__(self, endive):
        self.endive = endive

    def size(self):
        if symbolic_byte_counters:
            return super().size()
        if network.thenetwork.snipauthmode == \
                network.SNIPAuthMode.THRESHSIG:
            return DirAuthENDIVEMsg(self.endive).size()
        return math.ceil(DirAuthENDIVEMsg(self.endive).size() \
                            * network.P_Delta)

#########################
# RELAY
#########################

class RelayNetMsg(NetMsg):
    """The subclass of NetMsg for messages between relays and either
    relays or clients."""


class RelayGetConsensusMsg(RelayNetMsg):
    """The subclass of RelayNetMsg for fetching the consensus. Sent by
    clients to relays."""


class RelayConsensusMsg(RelayNetMsg):
    """The subclass of RelayNetMsg for returning the consensus from
    relays to clients, in response to RelayGetConsensusMsg."""

    def __init__(self, consensus):
        self.consensus = consensus


class RelayGetDescMsg(RelayNetMsg):
    """The subclass of RelayNetMsg sent by clients to their guards for
    retrieving the guard's current descriptor."""


class RelayDescMsg(RelayNetMsg):
    """The subclass of RelayNetMsg sent by guards to clients for
    reporting their current descriptor."""

    def __init__(self, desc):
        self.desc = desc


class RelayGetConsensusDiffMsg(RelayNetMsg):
    """The subclass of RelayNetMsg for fetching the consensus, if the
    requestor already has the previous consensus. Sent by clients to
    relays."""


class RelayConsensusDiffMsg(RelayNetMsg):
    """The subclass of RelayNetMsg for returning the consensus, if the
    requestor already has the previous consensus.  We don't _actually_
    produce the diff at this time; we just charge fewer bytes for this
    message. Sent by relays to clients in response to
    RelayGetConsensusDiffMsg."""

    def __init__(self, consensus):
        self.consensus = consensus

    def size(self):
        if symbolic_byte_counters:
            return super().size()
        return math.ceil(RelayConsensusMsg(self.consensus).size() \
                            * network.P_Delta)


class RelayRandomHopMsg(RelayNetMsg):
    """A message used for testing, that hops from relay to relay
    randomly until its TTL expires."""

    def __init__(self, ttl):
        self.ttl = ttl

    def __str__(self):
        return "RandomHop TTL=%d" % self.ttl


class CircuitCellMsg(RelayNetMsg):
    """Send a message tagged with a circuit id. This is the container
    class for all RelayCell messages."""

    def __init__(self, circuitid, cell):
        self.circid = circuitid
        self.cell = cell

    def __str__(self):
        return "C%d:%s" % (self.circid, self.cell)

    def size(self):
        # circuitids are 4 bytes
        return 4 + self.cell.size()


# It is intentional that VanillaCreateCircuitMsg is a RelayNetMsg and
# not a RelayCell.  This is the message that _creates_ the circuit, so
# it can't be sent as a cell _within_ the circuit.
class VanillaCreateCircuitMsg(RelayNetMsg):
    """The message for requesting circuit creation in Vanilla Onion
    Routing."""

    def __init__(self, circid, ntor_request):
        self.circid = circid
        self.ntor_request = ntor_request


# It is intentional that TelescopingCreateCircuitMsg is a RelayNetMsg and
# not a RelayCell.  This is the message that _creates_ the circuit, so
# it can't be sent as a cell _within_ the circuit.
class TelescopingCreateCircuitMsg(RelayNetMsg):
    """The message for requesting circuit creation in Telescoping Onion
    Routing."""

    def __init__(self, circid, ntor_request):
        self.circid = circid
        self.ntor_request = ntor_request


class SinglePassCreateCircuitMsg(RelayNetMsg):
    """The message for requesting circuit creation in Single Pass Onion
    Routing. This is used to extend a Single-Pass circuit by clients."""

    def __init__(self, circid, ntor_request, client_path_selection_key,
            ttl=2):
        self.circid = circid
        self.ntor_request = ntor_request
        self.clipathselkey = bytes(client_path_selection_key)
        self.ttl = ttl
