"""
Stream Control Transmission Protocol.
http://tools.ietf.org/html/rfc3286
http://tools.ietf.org/html/rfc2960
"""
import logging

from packetracer import packetracer, triggerlist, checksum
# handler
from packetracer.layer567 import diameter
from packetracer.packetracer_meta import FIELD_FLAG_AUTOUPDATE
from packetracer.structcbs import unpack_H, unpack_I

logger = logging.getLogger("packetracer")

# Chunk Types
DATA = 0
INIT = 1
INIT_ACK = 2
SACK = 3
HEARTBEAT = 4
HEARTBEAT_ACK = 5
ABORT = 6
SHUTDOWN = 7
SHUTDOWN_ACK = 8
ERROR = 9
COOKIE_ECHO = 10
COOKIE_ACK = 11
ECNE = 12
CWR = 13
SHUTDOWN_COMPLETE = 14


class Chunk(packetracer.Packet):
    __hdr__ = (
        ("type", "B", INIT),
        ("flags", "B", 0),
        ("len", "H", 0)  # length of header + data = 4 + x Bytes
    )


class SCTP(packetracer.Packet):
    __hdr__ = (
        ("sport", "H", 0),
        ("dport", "H", 0),
        ("vtag", "I", 0),
        ("sum", "I", 0, FIELD_FLAG_AUTOUPDATE),
        ("chunks", None, triggerlist.TriggerList)
    )

    # handle padding attribute
    def __get_padding(self):
        try:
            return self._padding
        except:
            return b""

    def __set_padding(self, padding):
        self._padding = padding

    padding = property(__get_padding, __set_padding)

    __handler__ = {
        123: diameter.Diameter,
    }

    def _dissect(self, buf):
        # parse chunks
        chunks = []
        off = 12
        blen = len(buf)

        # logger.debug("SCTP: parsing chunks")
        chunktype = -1

        # TODO: use lazy dissect, possible?
        while off + 4 < blen:
            dlen = unpack_H(buf[off + 2: off + 4])[0]
            # check for padding (this should be a data chunk)
            if off + dlen < blen:
                self.padding = buf[off + dlen:]
            # logger.debug("found padding: %s" % self.padding)

            chunk = Chunk(buf[off: off + dlen])
            # logger.debug("SCTP: Chunk; %s " % chunk)
            chunks.append(chunk)

            # get payload chunktype from DATA chunks
            if chunk.type == 0:
                chunktype = unpack_I(buf[off + chunk.header_len + 8: off + chunk.header_len + 8 + 4])[0]
                # logger.debug("got DATA chunk, chunktype: %d" % chunktype)
                # remove data from chunk: use bytes for handler
                chunk.body_bytes = b""
                off += len(chunk)
                # assume DATA is the last chunk
                break

            off += dlen

        self.chunks.extend(chunks)

        chunktype = unpack_H(buf[2: 4])[0]
        self._init_handler(chunktype, buf[off:-len(self.padding)])
        return off

    def _update_fields(self):
        if self.sum_au_active and self._changed():
            # logger.debug("updating checksum")
            self._calc_sum()

    def bin(self, update_auto_fields=True):
        return packetracer.Packet.bin(self, update_auto_fields=update_auto_fields) + self.padding

    def _calc_sum(self):
        # mark as changed
        self.sum = 0
        s = checksum.crc32_add(0xFFFFFFFF, self._pack_header())
        padlen = len(self.padding)

        if padlen == 0:
            s = checksum.crc32_add(s, self.body_bytes)
        else:
            # logger.debug("checksum with padding")
            s = checksum.crc32_add(s, self.body_bytes[:-padlen])

        self.sum = checksum.crc32_done(s)

    def direction(self, other):
        # logger.debug("checking direction: %s<->%s" % (self, other))
        if self.sport == other.sport and self.dport == other.dport:
            # consider packet to itself: can be DIR_REV
            return packetracer.Packet.DIR_SAME | packetracer.Packet.DIR_REV
        if self.sport == other.dport and self.dport == other.sport:
            return packetracer.Packet.DIR_REV
        return packetracer.Packet.DIR_UNKNOWN

    def reverse_address(self):
        self.sport, self.dport = self.dport, self.sport
