"""
Ported to Python 3.
"""

import struct

import attr

from ..util.hashutil import (
    tagged_hash,
)
from .lease import (
    LeaseInfo,
)
from .lease_schema import (
    v1_mutable,
    v2_mutable,
)

def _magic(version):
    # type: (int) -> bytes
    """
    Compute a "magic" header string for a container of the given version.

    :param version: The version number of the container.
    """
    # Make it easy for people to recognize
    human_readable = u"Tahoe mutable container v{:d}\n".format(version).encode("ascii")
    # But also keep the chance of accidental collision low
    if version == 1:
        # It's unclear where this byte sequence came from.  It may have just
        # been random.  In any case, preserve it since it is the magic marker
        # in all v1 share files.
        random_bytes = b"\x75\x09\x44\x03\x8e"
    else:
        # For future versions, use a reproducable scheme.
        random_bytes = tagged_hash(
            b"allmydata_mutable_container_header",
            human_readable,
            truncate_to=5,
        )
    magic = human_readable + random_bytes
    assert len(magic) == 32
    if version > 1:
        # The chance of collision is pretty low but let's just be sure about
        # it.
        assert magic != _magic(version - 1)

    return magic

def _header(magic, extra_lease_offset, nodeid, write_enabler):
    # type: (bytes, int, bytes, bytes) -> bytes
    """
    Construct a container header.

    :param nodeid: A unique identifier for the node holding this
        container.

    :param write_enabler: A secret shared with the client used to
        authorize changes to the contents of this container.
    """
    fixed_header = struct.pack(
        ">32s20s32sQQ",
        magic,
        nodeid,
        write_enabler,
        # data length, initially the container is empty
        0,
        extra_lease_offset,
    )
    blank_leases = b"\x00" * LeaseInfo().mutable_size() * 4
    extra_lease_count = struct.pack(">L", 0)

    return b"".join([
        fixed_header,
        # share data will go in between the next two items eventually but
        # for now there is none.
        blank_leases,
        extra_lease_count,
    ])


_HEADER_FORMAT = ">32s20s32sQQ"

# This size excludes leases
_HEADER_SIZE = struct.calcsize(_HEADER_FORMAT)

_EXTRA_LEASE_OFFSET = _HEADER_SIZE + 4 * LeaseInfo().mutable_size()


@attr.s(frozen=True)
class _Schema:
    """
    Implement encoding and decoding for the mutable container.

    :ivar int version: the version number of the schema this object supports

    :ivar lease_serializer: an object that is responsible for lease
        serialization and unserialization
    """
    version = attr.ib()
    lease_serializer = attr.ib()
    _magic = attr.ib()

    @classmethod
    def for_version(cls, version, lease_serializer):
        return cls(version, lease_serializer, magic=_magic(version))

    def magic_matches(self, candidate_magic):
        # type: (bytes) -> bool
        """
        Return ``True`` if a candidate string matches the expected magic string
        from a mutable container header, ``False`` otherwise.
        """
        return candidate_magic[:len(self._magic)] == self._magic

    def header(self, nodeid, write_enabler):
        return _header(self._magic, _EXTRA_LEASE_OFFSET, nodeid, write_enabler)

ALL_SCHEMAS = {
    _Schema.for_version(version=2, lease_serializer=v2_mutable),
    _Schema.for_version(version=1, lease_serializer=v1_mutable),
}
ALL_SCHEMA_VERSIONS = {schema.version for schema in ALL_SCHEMAS}
NEWEST_SCHEMA_VERSION = max(ALL_SCHEMAS, key=lambda schema: schema.version)

def schema_from_header(header):
    # (int) -> Optional[type]
    """
    Find the schema object that corresponds to a certain version number.
    """
    for schema in ALL_SCHEMAS:
        if schema.magic_matches(header):
            return schema
    return None
