"""

This file contains suggar coating over malcat's CPP types

"""
import malcat
import struct


######################################## BASIC TYPES

class Type:
    HEADER = malcat.HEADER
    FUNCTION = malcat.CODE
    DEBUG = malcat.DEBUG
    FIXUP = malcat.FIXUP
    DATA = malcat.DATA
    RESOURCE = malcat.RESOURCE
    META = malcat.METADATA
    ANOMALY = malcat.ANOMALY

    def __init__(self, name="", category=HEADER, comment="", parent=None, values=[]):
        self.name = name
        self.category = category
        self.comment = comment
        self.parent = parent

    def __repr__(self):
        return "{}({})".format(self.name, self.__class__.__name__)


class Unused(Type):
    def __init__(self, size, **kwargs):
        if not "name" in kwargs:
            kwargs["name"] = "Unused"
        Type.__init__(self, **kwargs)
        self.native = malcat.Unspecified(size, kwargs.get("comment", ""))


# TODO: remove all this proxy thing and use native malcat directly ?

class BasicType(Type):
    def __init__(self, binding, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = binding(kwargs.get("comment", ""), kwargs.get("values", []))


class UInt8(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt8, **kwargs)


class UInt8Xor(Type):
    def __init__(self, key, **kwargs):
        Type.__init__(self, **kwargs)
        self.native =  malcat.UInt8Xor(key, kwargs.get("comment", ""), kwargs.get("values", []))

class Int8(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int8, **kwargs)        


class UInt16(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt16, **kwargs)


class UInt16Xor(Type):
    def __init__(self, key, **kwargs):
        Type.__init__(self, **kwargs)
        self.native =  malcat.UInt16Xor(key, kwargs.get("comment", ""), kwargs.get("values", []))


class Int16(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int16, **kwargs)        


class UInt16BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt16BE, **kwargs)


class Int16BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int16BE, **kwargs)                


class UInt24(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.UInt24(kwargs.get("comment", ""), kwargs.get("values", []))


class UInt24BE(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.UInt24BE(kwargs.get("comment", ""), kwargs.get("values", []))

class Int24(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.Int24(kwargs.get("comment", ""))


class Int24BE(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.Int24BE(kwargs.get("comment", ""))


class UInt32(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt32, **kwargs)


class UInt32Xor(Type):
    def __init__(self, key, **kwargs):
        Type.__init__(self, **kwargs)
        self.native =  malcat.UInt32Xor(key, kwargs.get("comment", ""), kwargs.get("values", []))


class Int32(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int32, **kwargs)        


class UInt32BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt32BE, **kwargs)


class Int32BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int32BE, **kwargs)        


class UInt48(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.UInt48(kwargs.get("comment", ""))


class Int48(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.Int48(kwargs.get("comment", ""))


class UInt48BE(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.UInt48BE(kwargs.get("comment", ""))


class Int48BE(BasicType):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.Int48BE(kwargs.get("comment", ""))


class UInt64(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt64, **kwargs)


class Int64(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int64, **kwargs)        


class UInt64BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.UInt64BE, **kwargs)


class Int64BE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Int64BE, **kwargs)                


class Float(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Float, **kwargs)


class Double(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.Double, **kwargs)        


class FloatBE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.FloatBE, **kwargs)


class DoubleBE(BasicType):
    def __init__(self, **kwargs):
        BasicType.__init__(self, malcat.DoubleBE, **kwargs)                


class Filetime(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.Filetime(kwargs.get("comment", ""), [])        
        

class TimestampBE(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.TimestampBE(kwargs.get("comment", ""), [])


class Timestamp(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.Timestamp(kwargs.get("comment", ""), [])


class Timestamp2000(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.Timestamp2000(kwargs.get("comment", ""), [])


class DosDateTime(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.DosDateTime(kwargs.get("comment", ""), [])        


class DosDate(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.DosDate(kwargs.get("comment", ""), [])


class DosTime(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.DosTime(kwargs.get("comment", ""), [])        


class String(Type):
    def __init__(self, size, zero_terminated=False, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.String(size, zero_terminated, kwargs.get("comment", ""))


class Bytes(Type):
    def __init__(self, size, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.Bytes(size, kwargs.get("comment", ""))        


class StringUtf8(Type):
    def __init__(self, size, zero_terminated=False, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.U8String(size, zero_terminated, kwargs.get("comment", ""))


class StringUtf16le(Type):
    def __init__(self, size, zero_terminated=False, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.U16String(size, zero_terminated, kwargs.get("comment", ""), True)


class StringUtf16be(Type):
    def __init__(self, size, zero_terminated=False, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.U16String(size, zero_terminated, kwargs.get("comment", ""), False)


class GUID(Type):
    def __init__(self, microsoft_order=True, **kwargs):
        Type.__init__(self, **kwargs)        
        self.native = malcat.GUID(microsoft_order, kwargs.get("comment", ""), [(x, y.upper()) for x, y in kwargs.get("values", [])])


class Bit(Type):
    def __init__(self, **kwargs):
        Type.__init__(self, **kwargs)
        self.native = malcat.SingleBitField(kwargs.get("comment", ""))


class NullBits:     # convenience/macro class for BitField
    def __init__(self, count):
        self.count = count


########################################## AGGREGATES

class AggregateField(Type): pass

class BitsField(AggregateField):
    def __init__(self, *bits, lsb=True, **kwargs):
        Type.__init__(self, **kwargs)
        size = 0
        self.native = malcat.BitsField(kwargs.get("comment", ""), lsb)
        for bit in bits:
            if isinstance(bit, NullBits):
                for i in range(bit.count):
                    size += 1
                    self.native.add("", malcat.SingleBitField("<null>"))
            elif isinstance(bit, Bit):
                size += 1
                self.native.add(bit.name, bit.native)
            else:
                raise ValueError("Invalid type for a BitsField {}".format(bit.__class__.__name__))
        while (size % 8) != 0:
            size += 1
            self.native.add("", malcat.SingleBitField("<null>"))

class BitsFieldBE(AggregateField):
    def __init__(self, *bits, **kwargs):
        Type.__init__(self, **kwargs)
        size = 0
        self.native = malcat.BitsFieldBE(kwargs.get("comment", ""))
        for bit in bits:
            if isinstance(bit, NullBits):
                for i in range(bit.count):
                    size += 1
                    self.native.add("", malcat.SingleBitField("<null>"))
            elif isinstance(bit, Bit):
                size += 1
                self.native.add(bit.name, bit.native)
            else:
                raise ValueError("Invalid type for a BitsField {}".format(bit.__class__.__name__))
        while (size % 8) != 0:
            size += 1
            self.native.add("", malcat.SingleBitField("<null>"))            


class Struct(AggregateField):
    """
    A structure
    """

    def __init__(self, **kwargs):
        if not "name" in kwargs:
            kwargs["name"] = self.__class__.__name__
        AggregateField.__init__(self, **kwargs)
        self.native = malcat.StructField(self.comment)
        self.parser = None
        self.offset = None

    def parse(self):
        raise NotImplementedError

    def __len__(self):
        """
        return the current size in bytes of the structure
        """
        return self.native.size

    def look_ahead(self, num_bytes=1):
        """
        return the next <num_bytes> bytes that would need to be parsed
        can only be called from within the parse method
        """
        if self.parser is None:
            raise ValueError("method can only be called from within the parse() method")
        return self.parser.read(size=num_bytes)

    def remaining(self):
        """
        return how many bytes there is to parse
        can only be called from within the parse method
        """
        if self.parser is None:
            raise ValueError("method can only be called from within the parse() method")
        return self.parser.remaining()


class StaticStruct(Struct):    
    """
    An optimized version of struct where all field are static, i.e. the structure's layout does not depend on the actual file data at the structure's location (C-style structures)
    """

    @staticmethod
    def is_type_supported(t):
        if isinstance(t, DelayedType) or isinstance(t, DynamicArray):
            return False
        elif isinstance(t, Struct) and not isinstance(t, StaticStruct):
            return False
        elif isinstance(t, Array):
            return StaticStruct.is_type_supported(t.subtype)
        else:
            return True



    @classmethod
    def static_iterate(cls):
        if not hasattr(cls, "__fields"):
            cls.__fields = []
            for f in cls.parse():
                if not StaticStruct.is_type_supported(f):
                    raise TypeError(f"Unsupported type for StaticStruct in {cls.__name__}: {f.__class__.__name__} ({f.name})")
                cls.__fields.append(f)
        for f in cls.__fields:
            yield f

    @classmethod
    def parse(cls):
        raise NotImplementedError

    def look_ahead(self, num_bytes=1):
        raise ValueError("Invalid operation for StaticStruct")

class Array(AggregateField):
    """
    Array of fixed size. Note that all cells must have the same size. To construct the array, only the first cell is actually parsed(), and the rest of the cells are assumed to have the same size.
    If the cell type is a Struct(), you won't be able to do the same things as usual, such as reading fields
    """
    def __init__(self, count, subtype, **kwargs):
        Type.__init__(self, **kwargs)
        self.subtype = subtype
        self.count = count


class DynamicArray(AggregateField):
    """
    An array, but the size of the array is given by the predicate: fn_terminator(cell, i) == True
    """

    def __init__(self, fn_terminator, subtype, **kwargs):
        Type.__init__(self, **kwargs)
        self.subtype = subtype
        self.fn_terminator = fn_terminator



class VariableArray(Struct):
    """
    An array, but its elements can have different sizes. In practice, this is a structure with N fields.
    """

    def __init__(self, count, subtype_class, **kwargs):
        Struct.__init__(self, **kwargs)
        self._count = count
        self._subtype_class = subtype_class


    def parse(self):
        for i in range(self._count):
            yield self._subtype_class(name=f"{self.name}[{i}]")


########################################## POINTERS

class PointerType(Type): pass

class Rva(PointerType):
    def __init__(self, hint=None, zero_is_invalid=True, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Rva(hint, zero_is_invalid, kwargs.get("comment", ""))

class Rva64(PointerType):
    def __init__(self, hint=None, zero_is_invalid=True, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Rva64(hint, zero_is_invalid, kwargs.get("comment", ""))

class Va32(PointerType):
    def __init__(self, hint=None, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Va32(hint, zero_is_invalid, kwargs.get("comment", ""))

class Va32BE(PointerType):
    def __init__(self, hint=None, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Va32BE(hint, zero_is_invalid, kwargs.get("comment", ""))        

class Va64(PointerType):
    def __init__(self, hint=None, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Va64(hint, zero_is_invalid, kwargs.get("comment", ""))

class Va64BE(PointerType):
    def __init__(self, hint=None, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Va64BE(hint, zero_is_invalid, kwargs.get("comment", ""))        


class Offset16(PointerType):
    def __init__(self, hint=None, base=0, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Offset16(hint, base, zero_is_invalid, kwargs.get("comment", ""))

class Offset32(PointerType):
    def __init__(self, hint=None, base=0, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Offset32(hint, base, zero_is_invalid, kwargs.get("comment", ""))

class Offset32BE(PointerType):
    def __init__(self, hint=None, base=0, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Offset32BE(hint, base, zero_is_invalid, kwargs.get("comment", ""))        

class Offset64(PointerType):
    def __init__(self, hint=None, base=0, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Offset64(hint, base, zero_is_invalid, kwargs.get("comment", ""))

class Offset64BE(PointerType):
    def __init__(self, hint=None, base=0, zero_is_invalid=False, **kwargs):
        PointerType.__init__(self, **kwargs)        
        if not hasattr(hint, "native"):
            hint = None
        else:
            hint = hint.native
        self.native = malcat.Offset64BE(hint, base, zero_is_invalid, kwargs.get("comment", ""))        


class LanguageId(UInt32):

    ALL = [   ("unk", 0x0000), ("ar", 0x0001), ("bg", 0x0002), ("ca", 0x0003), ("zh-hans", 0x0004), ("cs", 0x0005), ("da", 0x0006), ("de", 0x0007), ("el", 0x0008), ("en", 0x0009), ("es", 0x000a), ("fi", 0x000b), ("fr", 0x000c), ("he", 0x000d), ("hu", 0x000e), ("is", 0x000f), ("it", 0x0010), ("ja", 0x0011), ("ko", 0x0012), ("nl", 0x0013), ("no", 0x0014), ("pl", 0x0015), ("pt", 0x0016), ("rm", 0x0017), ("ro", 0x0018), ("ru", 0x0019), ("hr", 0x001a), ("sk", 0x001b), ("sq", 0x001c), ("sv", 0x001d), ("th", 0x001e), ("tr", 0x001f), ("ur", 0x0020), ("id", 0x0021), ("uk", 0x0022), ("be", 0x0023), ("sl", 0x0024), ("et", 0x0025), ("lv", 0x0026), ("lt", 0x0027), ("tg", 0x0028), ("fa", 0x0029), ("vi", 0x002a), ("hy", 0x002b), ("az", 0x002c), ("eu", 0x002d), ("hsb", 0x002e), ("mk", 0x002f), ("st", 0x0030), ("ts", 0x0031), ("tn", 0x0032), ("ve", 0x0033), ("xh", 0x0034), ("zu", 0x0035), ("af", 0x0036), ("ka", 0x0037), ("fo", 0x0038), ("hi", 0x0039), ("mt", 0x003a),
    ("se", 0x003b), ("ga", 0x003c), ("yi,", 0x003d), ("ms", 0x003e), ("kk", 0x003f), ("ky", 0x0040), ("sw", 0x0041), ("tk", 0x0042), ("uz", 0x0043), ("tt", 0x0044), ("bn", 0x0045), ("pa", 0x0046), ("gu", 0x0047), ("or", 0x0048), ("ta", 0x0049), ("te", 0x004a), ("kn", 0x004b), ("ml", 0x004c), ("as", 0x004d), ("mr", 0x004e), ("sa", 0x004f), ("mn", 0x0050), ("bo", 0x0051), ("cy", 0x0052), ("km", 0x0053), ("lo", 0x0054), ("my", 0x0055), ("gl", 0x0056), ("kok", 0x0057), ("mni,", 0x0058), ("sd", 0x0059), ("syr", 0x005a), ("si", 0x005b), ("chr", 0x005c), ("iu", 0x005d), ("am", 0x005e), ("tzm", 0x005f), ("ks", 0x0060), ("ne", 0x0061), ("fy", 0x0062), ("ps", 0x0063), ("fil", 0x0064), ("dv", 0x0065), ("bin,", 0x0066), ("ff", 0x0067), ("ha", 0x0068), ("ibb,", 0x0069), ("yo", 0x006a), ("quz", 0x006b), ("nso", 0x006c), ("ba", 0x006d), ("lb", 0x006e), ("kl", 0x006f), ("ig", 0x0070), ("kr,", 0x0071), ("om", 0x0072), ("ti", 0x0073), ("gn", 0x0074), ("haw", 0x0075), ("la,", 0x0076), ("so,", 0x0077),
    ("ii", 0x0078), ("pap,", 0x0079), ("arn", 0x007a), ("moh", 0x007c), ("br", 0x007e), ("ug", 0x0080), ("mi", 0x0081), ("oc", 0x0082), ("co", 0x0083), ("gsw", 0x0084), ("sah", 0x0085), ("qut", 0x0086), ("rw", 0x0087), ("wo", 0x0088), ("prs", 0x008c), ("gd", 0x0091), ("ku", 0x0092), ("quc,", 0x0093), ("default", 0x0400), ("ar-sa", 0x0401), ("bg-bg", 0x0402), ("ca-es", 0x0403), ("zh-tw", 0x0404), ("cs-cz", 0x0405), ("da-dk", 0x0406), ("de-de", 0x0407), ("el-gr", 0x0408), ("en-us", 0x0409),
("es-es_tradnl", 0x040a), ("fi-fi", 0x040b), ("fr-fr", 0x040c), ("he-il", 0x040d), ("hu-hu", 0x040e), ("is-is", 0x040f), ("it-it", 0x0410), ("ja-jp", 0x0411), ("ko-kr", 0x0412), ("nl-nl", 0x0413), ("nb-no", 0x0414), ("pl-pl", 0x0415), ("pt-br", 0x0416), ("rm-ch", 0x0417), ("ro-ro", 0x0418), ("ru-ru", 0x0419), ("hr-hr", 0x041a), ("sk-sk", 0x041b), ("sq-al", 0x041c), ("sv-se", 0x041d), ("th-th", 0x041e), ("tr-tr", 0x041f), ("ur-pk", 0x0420), ("id-id", 0x0421), ("uk-ua", 0x0422), ("be-by", 0x0423), ("sl-si", 0x0424), ("et-ee", 0x0425), ("lv-lv", 0x0426), ("lt-lt", 0x0427), ("tg-cyrl-tj", 0x0428), ("fa-ir", 0x0429), ("vi-vn", 0x042a), ("hy-am", 0x042b), ("az-latn-az", 0x042c), ("eu-es", 0x042d), ("hsb-de", 0x042e), ("mk-mk", 0x042f), ("tn-za", 0x0432), ("xh-za", 0x0434),
("zu-za", 0x0435), ("af-za", 0x0436), ("ka-ge", 0x0437), ("fo-fo", 0x0438), ("hi-in", 0x0439), ("mt-mt", 0x043a), ("se-no", 0x043b), ("ms-my", 0x043e), ("kk-kz", 0x043f), ("ky-kg", 0x0440), ("sw-ke", 0x0441), ("tk-tm", 0x0442), ("uz-latn-uz", 0x0443), ("tt-ru", 0x0444), ("bn-in", 0x0445), ("pa-in", 0x0446), ("gu-in", 0x0447), ("or-in", 0x0448), ("ta-in", 0x0449), ("te-in", 0x044a), ("kn-in", 0x044b), ("ml-in", 0x044c), ("as-in", 0x044d), ("mr-in", 0x044e), ("sa-in", 0x044f), ("mn-mn", 0x0450), ("bo-cn", 0x0451), ("cy-gb", 0x0452), ("km-kh", 0x0453), ("lo-la", 0x0454), ("gl-es", 0x0456), ("kok-in", 0x0457), ("sd-deva-in", 0x0459), ("syr-sy", 0x045a), ("si-lk", 0x045b), ("chr-cher-us", 0x045c), ("iu-cans-ca", 0x045d), ("am-et", 0x045e), ("ne-np", 0x0461),
("fy-nl", 0x0462), ("ps-af", 0x0463), ("fil-ph", 0x0464), ("dv-mv", 0x0465), ("ha-latn-ng", 0x0468), ("yo-ng", 0x046a), ("quz-bo", 0x046b), ("nso-za", 0x046c), ("ba-ru", 0x046d), ("lb-lu", 0x046e), ("kl-gl", 0x046f), ("ig-ng", 0x0470), ("ti-et", 0x0473), ("haw-us", 0x0475), ("ii-cn", 0x0478), ("arn-cl", 0x047a), ("moh-ca", 0x047c), ("br-fr", 0x047e), ("ug-cn", 0x0480), ("mi-nz", 0x0481), ("oc-fr", 0x0482), ("co-fr", 0x0483), ("gsw-fr", 0x0484), ("sah-ru", 0x0485), ("quc-latn-gt", 0x0486), ("rw-rw", 0x0487), ("wo-sn", 0x0488), ("prs-af", 0x048c), ("gd-gb", 0x0491), ("ku-arab-iq", 0x0492), ("ar-iq", 0x0801), ("ca-es-valencia", 0x0803), ("zh-cn", 0x0804), ("de-ch", 0x0807),
("en-gb", 0x0809), ("es-mx", 0x080a), ("fr-be", 0x080c), ("it-ch", 0x0810), ("nl-be", 0x0813), ("nn-no", 0x0814), ("pt-pt", 0x0816), ("sr-latn-cs", 0x081a), ("sv-fi", 0x081d), ("ur-in", 0x0820), ("az-cyrl-az", 0x082c), ("dsb-de", 0x082e), ("tn-bw", 0x0832), ("se-se", 0x083b), ("ga-ie", 0x083c), ("ms-bn", 0x083e), ("uz-cyrl-uz", 0x0843), ("bn-bd", 0x0845), ("pa-arab-pk", 0x0846), ("ta-lk", 0x0849), ("mn-mong-cn", 0x0850), ("sd-arab-pk", 0x0859), ("iu-latn-ca", 0x085d), ("tzm-latn-dz", 0x085f), ("ff-latn-sn", 0x0867), ("quz-ec", 0x086b), ("ti-er", 0x0873), ("ti-er", 0x0873), ("ar-eg", 0x0c01), ("zh-hk", 0x0c04), ("de-at", 0x0c07), ("en-au", 0x0c09), ("es-es", 0x0c0a), ("fr-ca", 0x0c0c), ("sr-cyrl-cs", 0x0c1a), ("se-fi", 0x0c3b), ("quz-pe", 0x0c6b), ("ar-ly", 0x1001), ("zh-sg", 0x1004), ("de-lu", 0x1007), ("en-ca", 0x1009), ("es-gt", 0x100a),
("fr-ch", 0x100c), ("hr-ba", 0x101a), ("smj-no", 0x103b), ("tzm-tfng-ma", 0x105f), ("ar-dz", 0x1401), ("zh-mo", 0x1404), ("de-li", 0x1407), ("en-nz", 0x1409), ("es-cr", 0x140a), ("fr-lu", 0x140c), ("bs-latn-ba", 0x141a), ("smj-se", 0x143b), ("ar-ma", 0x1801), ("en-ie", 0x1809), ("es-pa", 0x180a), ("fr-mc", 0x180c), ("sr-latn-ba", 0x181a), ("sma-no", 0x183b), ("ar-tn", 0x1c01), ("en-za", 0x1c09), ("es-do", 0x1c0a), ("sr-cyrl-ba", 0x1c1a), ("sma-se", 0x1c3b), ("ar-om", 0x2001), ("en-jm", 0x2009), ("es-ve", 0x200a), ("bs-cyrl-ba", 0x201a), ("sms-fi", 0x203b), ("ar-ye", 0x2401), ("en-029", 0x2409), ("es-co", 0x240a), ("sr-latn-rs", 0x241a), ("smn-fi", 0x243b), ("ar-sy", 0x2801),
("en-bz", 0x2809), ("es-pe", 0x280a), ("sr-cyrl-rs", 0x281a), ("ar-jo", 0x2c01), ("en-tt", 0x2c09), ("es-ar", 0x2c0a), ("sr-latn-me", 0x2c1a), ("ar-lb", 0x3001), ("en-zw", 0x3009), ("es-ec", 0x300a), ("sr-cyrl-me", 0x301a), ("ar-kw", 0x3401), ("en-ph", 0x3409), ("es-cl", 0x340a), ("ar-ae", 0x3801), ("es-uy", 0x380a), ("ar-bh", 0x3c01), ("es-py", 0x3c0a), ("ar-qa", 0x4001), ("en-in", 0x4009), ("es-bo", 0x400a), ("en-my", 0x4409), ("es-sv", 0x440a), ("en-sg", 0x4809), ("es-hn", 0x480a), ("es-ni", 0x4c0a), ("es-pr", 0x500a), ("es-us", 0x540a), ("zh-cht", 0x7c04), ]

    def __init__(self, *args, **kwargs):
        kwargs["values"] = LanguageId.ALL
        UInt32.__init__(self, *args, **kwargs)


        

########################################## CUSTOM STRUCTS

class PascalString(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="string size")
        if sz:
            yield String(sz, zero_terminated=False, name="String")


class DotnetString(Struct):

    def parse(self):
        first, = self.look_ahead(1)
        if (first & 0xC0) == 0xC0:
            x, y, z = struct.unpack_from("BBB", self.look_ahead(4), offset=1)
            sz = ((first & 0x1F) << 24) + (x << 16) + (y << 8) + z
            cls = UInt32
        elif (first & 0x80) != 0:
            x, = struct.unpack_from("B", self.look_ahead(2), offset=1)
            sz = ((first & 0x3F) << 8) + x
            cls = UInt16
        else:
            sz = first & 0x7f
            cls = UInt8
        yield cls(name="Size", comment="string size")
        if sz:
            yield StringUtf8(sz, zero_terminated=False, name="String")            


class PascalString64(Struct):

    def parse(self):
        sz = yield UInt64(name="Size", comment="string size")
        if sz:
            yield String(sz, zero_terminated=False, name="String")            

class PascalUnicodeString(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="string size")
        if sz:
            yield StringUtf16le(sz // 2, zero_terminated=False, name="String")

class PascalString8(Struct):

    def parse(self):
        sz = yield UInt8(name="Size", comment="string size")
        if sz:
            yield String(sz, zero_terminated=False, name="String")            

class PascalBytes(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="buffer size")
        if sz:
            yield Bytes(sz, name="Bytes")

class UnicodeString(Struct):

    def __init__(self, size_in_bytes=False, **kwargs):
        Struct.__init__(self, **kwargs)
        self.size_in_bytes = size_in_bytes

    def parse(self):
        sz = yield UInt32(name="Size", comment="number of characters")
        if self.size_in_bytes:
            sz = sz // 2
        if sz:
            yield StringUtf16le(sz, zero_terminated=True, name="String")        



########################################## DYNAMIC TYPES

class DelayedType(Type):
    """
    non-aggregate types where the type size depends on the actual data
    """

    def build(self, parser):
        raise NotImplementedError


class PrefixedVarUInt64(DelayedType):
    """
    variable uint64 where first byte tells how many bytes there are (always BE)
    """
    def __init__(self, **kwargs):
        DelayedType.__init__(self, **kwargs)
        
    def build(self, parser):
        self.native = malcat.PrefixedVarUInt64(parser._file, parser.tell(), self.comment)

    @staticmethod
    def parse(parser, where):
        return malcat.PrefixedVarUInt64.parse(parser._file, where)


class SizedVarUInt64(DelayedType):
    """
    variable uint64 where first byte tells how many bytes there are (always BE). a value of 0xffffffffffffffff and a size of 1 means infinite.
    """
    def __init__(self, **kwargs):
        DelayedType.__init__(self, **kwargs)
        
    def build(self, parser):
        self.native = malcat.SizedVarUInt64(parser._file, parser.tell(), self.comment)

    @staticmethod
    def parse(parser, where):
        return malcat.SizedVarUInt64.parse(parser._file, where)



class VarUInt64(DelayedType):
    """
    variable uint64 where first bit (0x80) of every byte tells if there is more data to come
    """
    def __init__(self, **kwargs):
        DelayedType.__init__(self, **kwargs)
        
    def build(self, parser):
        self.native = malcat.VarUInt64(parser._file, parser.tell(), self.comment)

    @staticmethod
    def parse(parser, where):
        return malcat.VarUInt64.parse(parser._file, where)


class VarUInt64BE(DelayedType):
    """
    variable uint64 where first bit (0x80) tells if there is more data to come
    """
    def __init__(self, **kwargs):
        DelayedType.__init__(self, **kwargs)
        
    def build(self, parser):
        self.native = malcat.VarUInt64BE(parser._file, parser.tell(), self.comment)

    @staticmethod
    def parse(parser, where):
        return malcat.VarUInt64BE.parse(parser._file, where)    


class Align(DelayedType):
    """
    0 or more unused bytes depending on file offset
    """
    def __init__(self, alignment=4, **kwargs):
        DelayedType.__init__(self, name="Padding", **kwargs)
        self.alignment = alignment
        
    def build(self, parser):
        align = parser.tell() % self.alignment
        if align:
            self.native = malcat.Unspecified(self.alignment - align, self.comment or "Padding on {}-bytes boundaries".format(self.alignment))
        else:
            self.native = None  # yield nothing

class StructAlign(DelayedType):
    """
    0 or more unused bytes depending on structure size
    """
    def __init__(self, struct, alignment=4, **kwargs):
        DelayedType.__init__(self, name="Padding", **kwargs)
        self.struct = struct
        self.alignment = alignment
        
    def build(self, parser):
        align = len(self.struct) % self.alignment
        if align:
            self.native = malcat.Unspecified(self.alignment - align, self.comment or "Padding on {}-bytes boundaries".format(self.alignment))
        else:
            self.native = None  # yield nothing            


class CString(DelayedType):
    """
    a zero-terminated ascii string
    """
    def __init__(self, max_size=512, **kwargs):
        DelayedType.__init__(self, **kwargs)
        self.max_size = max_size
        
    def build(self, parser):
        string = parser.read_cstring_ascii(max_bytes=self.max_size)
        zt = len(string) < self.max_size
        self.native = malcat.String(min(len(string) + 1, self.max_size), zt, self.comment)


class CStringUtf8(DelayedType):
    """
    a zero-terminated ascii string
    """
    def __init__(self, max_size=512, **kwargs):
        DelayedType.__init__(self, **kwargs)
        self.max_size = max_size
        
    def build(self, parser):
        string = parser.read_cstring_utf8(max_bytes=self.max_size)
        zt = len(string.encode("utf8")) < self.max_size
        self.native = malcat.U8String(min(len(string.encode("utf8")) + 1, self.max_size), zt, self.comment)


class CStringUtf16le(DelayedType):
    """
    a zero-terminated ascii string
    """
    def __init__(self, max_size=512, **kwargs):
        DelayedType.__init__(self, **kwargs)
        self.max_size = max_size
        
    def build(self, parser):
        string = parser.read_cstring_utf16le(max_bytes=self.max_size)
        zt = len(string) < self.max_size // 2
        self.native = malcat.U16String(min(len(string) + 1, self.max_size//2), zt, self.comment, True)


class CStringUtf16be(DelayedType):
    """
    a zero-terminated ascii string
    """
    def __init__(self, max_size=512, **kwargs):
        DelayedType.__init__(self, **kwargs)
        self.max_size = max_size
        
    def build(self, parser):
        string = parser.read_cstring_utf16be(max_bytes=self.max_size)
        zt = len(string) < self.max_size // 2
        self.native = malcat.U16String(min(len(string) + 1, self.max_size//2), zt, self.comment, False)
