# Copied from pyaaf2

"""

Excerpt from SMPTE ST 330 (Focus on Basic UMID)::

    5 General Specification

    A unique material identifier (UMID) provides for the globally unique identification of any audiovisual material.
    This standard defines a dual approach through the specification of a basic UMID and an extended UMID.
    The basic UMID provides a globally unique identification for audiovisual material that comprises an integer
    number of one or more contiguous material units. The basic UMID has no embedded mechanism to
    distinguish between individual material units within a single instance of audiovisual material. The data in the
    basic UMID can be created through automatic generation.

    The extended UMID comprises the basic UMID followed immediately by a source pack that provides a
    signature for material units. The source pack comprises a fixed length metadata pack of 32 bytes that
    provides sufficient metadata by which source ?when, where and who (or what)? information can be identified
    regardless of current ownership or status. The extended UMID also provides a mechanism to distinguish
    between individual material units within a single instance of audiovisual material.

    The basic UMID is 32 bytes long and the extended UMID is 64 bytes long.
    Both UMID types use the key-length-value construct defined by SMPTE ST 336. The key is a 16-byte
    universal label truncated to 12 bytes.
    In the case of the basic UMID, the length field has a value of 13h and the value is formed by the combination
    of a material number and an instance number.
    In the case of the extended UMID, the length field has a value of 33h and the value is formed by the
    combination of the material and the instance numbers followed by the source pack.
    All components of the UMID have a defined byte order for consistent application in storage and streaming
    environments.

    The components of the basic UMID are:
    1. A 12-byte universal label,
    2. A 1-byte length value,
    3. A 3-byte instance number, and
    4. A 16-byte material number.

    The combination of the instance and material numbers can be treated as a dumb number.
    Note: The material number does not indicate the status of the material (such as copy number) or its representation
    (such as the compression kind). The material number can be identical in copies and in different representations of
    the material. The purpose of the instance number is to separately identify different representations or instances of
    audiovisual material. Thus, for example, a high-resolution picture and a thumbnail can both have the same
    material number because they both represent the same picture but, because they are different instances, they will
    have different instance numbers for the different representations. Guidance for the consistent application of new
    material numbers and instance numbers is given in SMPTE RP 205.


    UMID univeral label (SMPTELabel)

    Byte No.   Description            Value (hex)                 Meaning
    ----------------------------------------------------------------------------------------
    1          Object identifier      06h                         Universal label start
    2          Label size             0Ah                         12-byte Universal label
    3          Designation: ISO       2Bh                         ISO registered
    4          Designation: SMPTE     34h                         SMPTE registered
    5          Registry category      01h                         Dictionaries
    6          Specific category      01h                         Metadata dictionaries
    7          Structure              01h                         Dictionary standard (SMPTE ST 335)
    8          Version number         05h                         Version of the metadata dictionary (defined in SMPTE RP 210)
    9          Class                  01h                         Identifiers and locators
    10         Subclass               01h                         Globally unique identifiers
    11         Material type          XXh                         See Section 6.1.2.1
    12         Number creation method YYh                         See Section 6.1.2.2

    6.1.2.1 - Meterial type identification

    Byte 11 of the UL shall define the material type being identified using one of the values defined in Table 2.
    The use of material types '01h', '02h', '03h' and '04h' shall be deprecated for use in implementations using
    this revised standard. These values are preserved only for compatibility with systems implemented using
    SMPTE ST 330:2000#

    Table 2

    Byte value    Meaning                                                            Examples and notes
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    01h           picture material                                                   Deprecated
    02h           audio material                                                     Deprecated
    03h           data material                                                      Deprecated
    04h           other material                                                     Deprecated (originally not only picture, audio, or data material, but also a combination of material types)
    05h           single picture component                                           e.g. Y component
    06h           Two or more picture components in a single container               e.g. interleaved Y, Cb and Cr components
    08h           single audio component                                             e.g mono audio
    09h           two or more audio components in a single container                 e.g. AES3 audio pair
    0Bh           single auxiliary (or data) component                               e.g. sub-titles only
    0Ch           two or more auxiliary (or data) components in a single container   e.g. multiple sub-titles streams in different languages
    0Dh           mixed group of components in a single container                    e.g. video & stereo audio pair
    0Fh           material type is not identified

    6.1.2.2 Number creation method identification

    Byte 12 of the UL shall define the method by which the material and instance numbers are created. This byte
    is divided into top and bottom nibbles for the purpose of this definition.
    The top nibble shall occupy the 4 most significant bits (MSBs) of the byte and the value shall be used to
    define the method of material number creation. The values used by this nibble shall be limited to the range 0
    to 7h so that byte 12 conforms to the ASN.1 BER short form coding rules used by SMPTE ST 298.
    The methods of material number generation shall be as defined in table 3 and the specification of the each
    method shall be as defined in Annex A.
    Note: New material number generation methods can be added by amendment or revision of this document. Each
    addition will provide the proposed value (within the range of values currently identified as "Reserved but not
    defined") for inclusion in Table 3 together with the supporting definition to be added to Annex A.

    Table 3 - Identification of material number generation method::

        Value (hex)   Method
        -------------------------------------------------------------
        0             No defined method
        1             SMPTE method
        2             UUID/UL method
        3             Masked method
        4             IEEE 1394 network method
        5~7           Reserved but not defined


Notes from Pixar 10/30/17

Final note of discussion with Avid engineers how the top nibble in the 12th byte in the SMPTELabel should be set.
(In the past we always had it set to 00, i.e. "no defined method", we had some confusion about how to set it when using a uuid for the material)

Avid Engineer:

"The specification of number creation identification is very clear about using the 4 MSBs for the material
number so I am pretty sure that the numbers in the sub-titles of Annex A (e.g. 02h) should not be interpreted literally as values of byte 12.
20h is the correct value of the byte 12 for UL/UUID method/No defined method.

By the way, I don't see anything wrong with setting byte 12 to 00h (No defined method / No defined method)"

We at Pixar decided to set the byte to 20h, since it (even if already very minimal) completely eliminates the possibility to collide with any
MOB ID created by our old MOB ID generation algorithm.
"""

from __future__ import (
    unicode_literals,
    absolute_import,
    print_function,
    division,
    )

import uuid
import struct
from .utils import (int_from_bytes, bytes_from_int,
                    unpack_u16le_from, unpack_u32le_from)

MOBID_STRUCT = struct.Struct(str(''.join(( '<',
   '12B',  # UInt8Array12   SMPTELabel      0
   'B',    # UInt8          length         12
   'B',    # UInt8          instanceHigh   13
   'B',    # UInt8          instanceMid    14
   'B',    # UInt8          instanceLow    15
   'I',    # UInt32         Data1          16
   'H',    # UInt16         Data2          20
   'H',    # UInt16         Data3          22
   '8B',   # UInt8Array8    Data4          24
 ))))
def UniqueMobID():
    m = MobID()              # Description              Meaning
    m.SMPTELabel = [0x06,    # Object identifier        Universal label start
                    0x0a,    # Label size               12-byte Universal label
                    0x2b,    # Designation: ISO         ISO registered
                    0x34,    # Designation: SMPTE       SMPTE registered
                    0x01,    # Registry category        Dictionaries
                    0x01,    # Specific category        Metadata dictionaries
                    0x01,    # Structure                Dictionary standard (SMPTE ST 335)
                    0x05,    # Version number           Version of the metadata dictionary (defined in SMPTE RP 210)
                    0x01,    # Class                    Identifiers and locators
                    0x01,    # Subclass                 Globally unique identifiers
                    0x0f,    # Material type            See Section 6.1.2.1
                    0x20     # Number creation method   See Section 6.1.2.2     # Using UUID/UL method, Note 10/30/17 - matching pixar recommendation
                   ]
    m.length = 0x13          # Length, 13h = Basic UMID, 33h = Extended UMID
    m.instanceHigh = 0x00
    m.instanceMid = 0x00
    m.instanceLow = 0x00
    m.material = uuid.uuid4() # 16 byte material slot, filled with uuid according to RFC4122
    return m

class MobID(object):
    __slots__ = ('bytes_le')
    def __init__(self, mobid=None, bytes_le=None, int=None):

        if bytes_le:
            self.bytes_le = bytearray(bytes_le)
        else:
            self.bytes_le = bytearray(32)

            if mobid is not None:
                self.urn = mobid

            if int is not None:
                self.int = int

    @staticmethod
    def new():
        """
        Static method for generating unique MobIDs. Uses uuid.uuid4() for generation.
        """
        return UniqueMobID()

    @property
    def material(self):
        """
        MobID material representation as a UUID
        """
        return uuid.UUID(bytes_le=bytes(self.bytes_le[16:]))

    @material.setter
    def material(self, value):
        self.bytes_le[16:] = value.bytes_le

    @property
    def SMPTELabel(self):
        return self.bytes_le[0:12]

    @SMPTELabel.setter
    def SMPTELabel(self, value):
        struct.pack_into(str('12B'), self.bytes_le, 0, *value)

    @property
    def length(self):
        return self.bytes_le[12]

    @length.setter
    def length(self, value):
        self.bytes_le[12] = value

    @property
    def instanceHigh(self):
        return self.bytes_le[13]

    @instanceHigh.setter
    def instanceHigh(self, value):
        self.bytes_le[13] = value

    @property
    def instanceMid(self):
        return self.bytes_le[14]

    @instanceMid.setter
    def instanceMid(self, value):
        self.bytes_le[14] = value

    @property
    def instanceLow(self):
        return self.bytes_le[15]

    @instanceLow.setter
    def instanceLow(self, value):
        self.bytes_le[15] = value

    @property
    def Data1(self):
        return unpack_u32le_from(self.bytes_le, 16)

    @Data1.setter
    def Data1(self, value):
        struct.pack_into(str('<I'), self.bytes_le, 16, value)

    @property
    def Data2(self):
        return unpack_u16le_from(self.bytes_le, 20)

    @Data2.setter
    def Data2(self, value):
        struct.pack_into(str('<H'), self.bytes_le, 20, value)

    @property
    def Data3(self):
        return unpack_u16le_from(self.bytes_le, 22)

    @Data3.setter
    def Data3(self, value):
        struct.pack_into(str('<H'), self.bytes_le, 22, value)

    @property
    def Data4(self):
        return self.bytes_le[24:32]

    @Data4.setter
    def Data4(self, value):
        struct.pack_into(str('8B'), self.bytes_le, 24, *value)

    def from_dict(self, d):
        """
        Set MobID from a dict
        """
        self.length = d.get("length", 0)
        self.instanceHigh = d.get("instanceHigh", 0)
        self.instanceMid = d.get("instanceMid", 0)
        self.instanceLow = d.get("instanceLow", 0)

        material = d.get("material", {'Data1':0, 'Data2':0, 'Data3':0, 'Data4': [0 for i in range(8)]})

        self.Data1 = material.get('Data1', 0)
        self.Data2 = material.get('Data2', 0)
        self.Data3 = material.get('Data3', 0)

        self.Data4 = material.get("Data4", [0 for i in range(8)])
        self.SMPTELabel = d.get("SMPTELabel", [0 for i in range(12)])

    def to_dict(self):
        """
        MobID representation as dict
        """

        material = {'Data1': self.Data1,
                    'Data2': self.Data2,
                    'Data3': self.Data3,
                    'Data4': list(self.Data4)
                    }

        return {'material':material,
                'length': self.length,
                'instanceHigh': self.instanceHigh,
                'instanceMid': self.instanceMid,
                'instanceLow': self.instanceLow,
                'SMPTELabel': list(self.SMPTELabel)
                }
    @property
    def int(self):
        """
        MobID representation as a int
        """
        return int_from_bytes(self.bytes_le, byte_order='big')

    @int.setter
    def int(self, value):
        # NOTE: interpreted as big endian
        self.bytes_le = bytearray(bytes_from_int(value, 32, byte_order='big'))

    def __int__(self):
        return self.int

    def __eq__(self, other):
        if isinstance(other, MobID):
            return self.bytes_le == other.bytes_le
        return NotImplemented

    def __ne__(self, other):
        if isinstance(other, MobID):
            return self.bytes_le != other.bytes_le
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, MobID):
            return self.int < other.int
        return NotImplemented

    def __le__(self, other):
        if isinstance(other, MobID):
            return self.int <= other.int
        return NotImplemented

    def __gt__(self, other):
        if isinstance(other, MobID):
            return self.int > other.int
        return NotImplemented

    def __ge__(self, other):
        if isinstance(other, MobID):
            return self.int >= other.int
        return NotImplemented

    def __hash__(self):
        return hash(bytes(self.bytes_le))

    @property
    def urn(self):
        """
        MobID Uniform Resource Name representation.
        https://en.wikipedia.org/wiki/Uniform_Resource_Name
        """

        SMPTELabel = self.SMPTELabel
        Data4 = self.Data4

        # handle case UMIDs where the material number is half swapped
        if (SMPTELabel[11] == 0x00 and
            Data4[0] == 0x06 and Data4[1] == 0x0E and
            Data4[2] == 0x2B and Data4[3] == 0x34 and
            Data4[4] == 0x7F and Data4[5] == 0x7F):

            # print("case 1")
            f = "urn:smpte:umid:%02x%02x%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x." + \
             "%02x"  + \
             "%02x%02x%02x." + \
             "%02x%02x%02x%02x.%02x%02x%02x%02x.%08x.%04x%04x"

            return f % (
                 SMPTELabel[0], SMPTELabel[1], SMPTELabel[2],  SMPTELabel[3],
                 SMPTELabel[4], SMPTELabel[5], SMPTELabel[6],  SMPTELabel[7],
                 SMPTELabel[8], SMPTELabel[9], SMPTELabel[10], SMPTELabel[11],
                 self.length,
                 self.instanceHigh, self.instanceMid, self.instanceLow,
                 Data4[0], Data4[1], Data4[2], Data4[3],
                 Data4[4], Data4[5], Data4[6], Data4[7],
                 self.Data1, self.Data2, self.Data3)
        else:
            # print("case 2")
            f = "urn:smpte:umid:%02x%02x%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x." + \
             "%02x"  + \
             "%02x%02x%02x." + \
             "%08x.%04x%04x.%02x%02x%02x%02x.%02x%02x%02x%02x"

            return f % (
                 SMPTELabel[0], SMPTELabel[1], SMPTELabel[2],  SMPTELabel[3],
                 SMPTELabel[4], SMPTELabel[5], SMPTELabel[6],  SMPTELabel[7],
                 SMPTELabel[8], SMPTELabel[9], SMPTELabel[10], SMPTELabel[11],
                 self.length,
                 self.instanceHigh, self.instanceMid, self.instanceLow,
                 self.Data1, self.Data2, self.Data3,
                 Data4[0], Data4[1], Data4[2], Data4[3],
                 Data4[4], Data4[5], Data4[6], Data4[7])

    @urn.setter
    def urn(self, value):
        s = str(value).lower()
        for item in ("urn:smpte:umid:", ".", '-', '0x'):
            s = s.replace(item, '')
        assert len(s) == 64

        SMPTELabel = [0 for i in range(12)]
        start = 0
        for i in range(12):
            end = start + 2
            v = s[start:end]
            SMPTELabel[i] = int(v, 16)
            start = end
        self.SMPTELabel = SMPTELabel
        self.length = int(s[24:26], 16)
        self.instanceHigh = int(s[26:28], 16)
        self.instanceMid = int(s[28:30], 16)
        self.instanceLow = int(s[30:32], 16)

        start = 32
        data = [0 for i in range(6)]
        for i in range(6):
            end = start + 2
            v = s[start:end]
            data[i] = int(v, 16)
            start = end
        # print(s[32:start])
        if (SMPTELabel[11] == 0x00 and
                   data[0] == 0x06 and data[1] == 0x0E and
                   data[2] == 0x2B and data[3] == 0x34 and
                   data[4] == 0x7F and data[5] == 0x7F):

            start = 32
            data4 = [0 for i in range(8)]
            for i in range(8):
                end = start + 2
                v = s[start:end]
                data4[i] = int(v, 16)
                start = end

            self.Data4 = data4
            self.Data1 = int(s[48:56], 16)
            self.Data2 = int(s[56:60], 16)
            self.Data3 = int(s[60:64], 16)

        else:
            self.Data1 = int(s[32:40], 16)
            self.Data2 = int(s[40:44], 16)
            self.Data3 = int(s[44:48], 16)

            start = 48
            data4 = [0 for i in range(8)]
            for i in range(8):
                end = start + 2
                v = s[start:end]
                data4[i] = int(v, 16)
                start = end
            self.Data4 = data4

    def __repr__(self):
        return str(self.urn)

if __name__ == "__main__":

    t = "urn:smpte:umid:060a2b34.01010101.01010f00.13000000.060e2b34.7f7f2a80.4fa5c20f.4e301e50"
    m = MobID(t)
    print(t)
    print(m)
    assert str(m) == t
