import malcat 
import struct
from filetypes.base import *

TIFF_TAGS_FOR_META = {
        "Copyright": "Copyright",
        "Make": "Camera maker",
        "Model": "Camera model", 
        "Software": "Camera software", 
        "DateTime": "Shot time",
        "DateTimeOriginal": "Shot time original",
        "CreateDate": "Creation time",
        "ModifyDate": "Modification time",
        "CreatorTool": "Creator tool", 
        "DocumentID": "Document ID", 
        "PixelXDimension": "Width", 
        "PixelYDimension": "Height", 
        "GPSDateStamp": "GPS time", 
        "ImageUniqueID": "Image unique ID", 
        }

RECORD_INFOS = {
        1: ("BYTE", 1, lambda n, sz: UInt8(name=n), lambda n, sz: UInt8(name=n), "B"),
        2: ("ASCII", 0, lambda n, sz: String(sz, zero_terminated=True, name=n), lambda n, sz: String(sz, zero_terminated=True, name=n), "s"),
        3: ("WORD", 2, lambda n, sz: UInt16(name=n), lambda n, sz: UInt16BE(name=n), "H"),
        4: ("DWORD", 4, lambda n, sz: UInt32(name=n), lambda n, sz: UInt32BE(name=n), "I"),
        5: ("RATIONAL", 8, lambda n, sz: Array(2, UInt32(), name=n), lambda n, sz: Array(2, UInt32BE(), name=n), "II"),
        6: ("SBYTE", 1, lambda n, sz: Int8(name=n), lambda n, sz: Int8(name=n), "b"),
        7: ("UNDEFINED", 0,  lambda n, sz: Unused(sz, name=n), lambda n, sz: Unused(sz, name=n), None),
        8: ("SWORD", 2, lambda n, sz: Int16(name=n), lambda n, sz: Int16BE(name=n), "h"),
        9: ("SLONG", 4, lambda n, sz: Int32(name=n), lambda n, sz: Int32BE(name=n), "i"),
        10: ("SRATIONAL", 8, lambda n, sz: Array(2, Int32(), name=n), lambda n, sz: Array(2, Int32BE(), name=n), "q"),
        11: ("FLOAT", 4, lambda n, sz: UInt32(name=n), lambda n, sz: UInt32BE(name=n), "f"),
        12: ("DOUBLE", 8, lambda n, sz: UInt64(name=n), lambda n, sz: UInt64BE(name=n), "d"),
}

class TiffRecord(Struct):

    def __init__(self, little_endian, tags, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.little_endian = little_endian
        self.tags = tags

    def parse(self):
        if self.little_endian:
            u16 = UInt16BE
            u32 = UInt32BE
        else:
            u16 = UInt16
            u32 = UInt32
        yield u16(name="Tag", comment="record tag", values=self.tags)
        yield u16(name="Type", comment="record type", values=[ (x[1][0], x[0]) for x in RECORD_INFOS.items()])
        yield u32(name="Length", comment="length of the record in units of the data type")
        yield u32(name="OffsetOrData", comment="offset of the field or value if length of field <= 4")



class TiffDirectory(Struct):

    tags = [
        ("NewSubfileType", 0x00FE), ("SubfileType", 0x00FF), ("ImageWidth", 0x0100), ("ImageLength", 0x0101), ("BitsPerSample", 0x0102), ("Compression", 0x0103), ("PhotometricInterpretation", 0x0106), ("Threshholding", 0x0107), ("CellWidth", 0x0108), ("CellLength", 0x0109), ("FillOrder", 0x010A), ("ImageDescription", 0x010E), ("Make", 0x010F), ("Model", 0x0110), ("StripOffsets", 0x0111), ("Orientation", 0x0112),
("SamplesPerPixel", 0x0115), ("RowsPerStrip", 0x0116), ("StripByteCounts", 0x0117), ("MinSampleValue", 0x0118), ("MaxSampleValue", 0x0119), ("XResolution", 0x011A), ("YResolution", 0x011B), ("PlanarConfiguration", 0x011C), ("FreeOffsets", 0x0120), ("FreeByteCounts", 0x0121), ("GrayResponseUnit", 0x0122), ("GrayResponseCurve", 0x0123), ("ResolutionUnit", 0x0128), ("Software", 0x0131), ("DateTime", 0x0132), ("Artist", 0x013B),
("HostComputer", 0x013C), ("ColorMap", 0x0140), ("ExtraSamples", 0x0152), ("Copyright", 0x8298), ("DocumentName", 0x010D), ("PageName", 0x011D), ("XPosition", 0x011E), ("YPosition", 0x011F), ("T4Options", 0x0124), ("T6Options", 0x0125), ("PageNumber", 0x0129), ("TransferFunction", 0x012D), ("Predictor", 0x013D), ("WhitePoint", 0x013E), ("PrimaryChromaticities", 0x013F), ("HalftoneHints", 0x0141),
("TileWidth", 0x0142), ("TileLength", 0x0143), ("TileOffsets", 0x0144), ("TileByteCounts", 0x0145), ("BadFaxLines", 0x0146), ("CleanFaxData", 0x0147), ("ConsecutiveBadFaxLines", 0x0148), ("SubIFDs", 0x014A), ("InkSet", 0x014C), ("InkNames", 0x014D), ("NumberOfInks", 0x014E), ("DotRange", 0x0150), ("TargetPrinter", 0x0151), ("SampleFormat", 0x0153), ("SMinSampleValue", 0x0154), ("SMaxSampleValue", 0x0155),
("TransferRange", 0x0156), ("ClipPath", 0x0157), ("XClipPathUnits", 0x0158), ("YClipPathUnits", 0x0159), ("Indexed", 0x015A), ("JPEGTables", 0x015B), ("OPIProxy", 0x015F), ("GlobalParametersIFD", 0x0190), ("ProfileType", 0x0191), ("FaxProfile", 0x0192), ("CodingMethods", 0x0193), ("VersionYear", 0x0194), ("ModeNumber", 0x0195), ("Decode", 0x01B1), ("DefaultImageColor", 0x01B2), ("JPEGProc", 0x0200),
("JPEGInterchangeFormat", 0x0201), ("JPEGInterchangeFormatLength", 0x0202), ("JPEGRestartInterval", 0x0203), ("JPEGLosslessPredictors", 0x0205), ("JPEGPointTransforms", 0x0206), ("JPEGQTables", 0x0207), ("JPEGDCTables", 0x0208), ("JPEGACTables", 0x0209), ("YCbCrCoefficients", 0x0211), ("YCbCrSubSampling", 0x0212), ("YCbCrPositioning", 0x0213), ("ReferenceBlackWhite", 0x0214), ("StripRowCounts", 0x022F), ("XMP", 0x02BC), ("ImageID", 0x800D), ("ImageLayer", 0x87AC),
("Wang", 0x80A4), ("MD", 0x82A5), ("MD", 0x82A6), ("MD", 0x82A7), ("MD", 0x82A8), ("MD", 0x82A9), ("MD", 0x82AA), ("MD", 0x82AB), ("MD", 0x82AC), ("ModelPixelScaleTag", 0x830E), ("IPTC", 0x83BB), ("INGR", 0x847E), ("INGR", 0x847F), ("IrasB", 0x8480), ("ModelTiepointTag", 0x8482), ("ModelTransformationTag", 0x85D8),
("Photoshop", 0x8649), ("Exif", 0x8769), ("ICC", 0x8773), ("GeoKeyDirectoryTag", 0x87AF), ("GeoDoubleParamsTag", 0x87B0), ("GeoAsciiParamsTag", 0x87B1), ("GPS", 0x8825), ("HylaFAX", 0x885C), ("HylaFAX", 0x885D), ("HylaFAX", 0x885E), ("ImageSourceData", 0x935C), ("Interoperability", 0xA005), ("LensInfo", 0xA432), ("GDAL_METADATA", 0xA480), ("GDAL_NODATA", 0xA481), ("Oce", 0xC427), ("Oce", 0xC428),
("Oce", 0xC429), ("Oce", 0xC42A), ("DNGVersion", 0xC612), ("DNGBackwardVersion", 0xC613), ("UniqueCameraModel", 0xC614), ("LocalizedCameraModel", 0xC615), ("CFAPlaneColor", 0xC616), ("CFALayout", 0xC617), ("LinearizationTable", 0xC618), ("BlackLevelRepeatDim", 0xC619), ("BlackLevel", 0xC61A), ("BlackLevelDeltaH", 0xC61B), ("BlackLevelDeltaV", 0xC61C), ("WhiteLevel", 0xC61D), ("DefaultScale", 0xC61E), ("DefaultCropOrigin", 0xC61F),
("DefaultCropSize", 0xC620), ("ColorMatrix1", 0xC621), ("ColorMatrix2", 0xC622), ("CameraCalibration1", 0xC623), ("CameraCalibration2", 0xC624), ("ReductionMatrix1", 0xC625), ("ReductionMatrix2", 0xC626), ("AnalogBalance", 0xC627), ("AsShotNeutral", 0xC628), ("AsShotWhiteXY", 0xC629), ("BaselineExposure", 0xC62A), ("BaselineNoise", 0xC62B), ("BaselineSharpness", 0xC62C), ("BayerGreenSplit", 0xC62D), ("LinearResponseLimit", 0xC62E), ("CameraSerialNumber", 0xC62F),
("DngLensInfo", 0xC630), ("ChromaBlurRadius", 0xC631), ("AntiAliasStrength", 0xC632), ("DNGPrivateData", 0xC634), ("MakerNoteSafety", 0xC635), ("CalibrationIlluminant1", 0xC65A), ("CalibrationIlluminant2", 0xC65B), ("BestQualityScale", 0xC65C), ("Alias", 0xC660), ("TIFF_RSID", 0xC6DC), ("GEO_METADATA", 0xC6DD),
]

    def __init__(self, little_endian, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.little_endian = little_endian

    def parse(self):
        if self.little_endian:
            num = yield UInt16BE(name="NumberOfEntries", comment="number of entries in directory")
        else:
            num = yield UInt16(name="NumberOfEntries", comment="number of entries in directory")
        if num:
            yield Array(num, TiffRecord(self.little_endian, self.tags), name="Records")
            if self.little_endian:
                yield UInt32BE(name="NextDirectory", comment="offset to next directory")
            else:
                yield UInt32(name="NextDirectory", comment="offset to next directory")


    
class ExifDirectory(TiffDirectory):

    tags = [ 
            ("ExposureTime", 0x829A), ("FNumber", 0x829D), ("ExposureProgram", 0x8822), ("SpectralSensitivity", 0x8824), ("ISOSpeedRatings", 0x8827), ("OECF", 0x8828), ("SensitivityType", 0x8830), ("RecommendedExposureIndex", 0x8832), ("ExifVersion", 0x9000), ("DateTimeOriginal", 0x9003),
("DateTimeDigitized", 0x9004), ("ComponentsConfiguration", 0x9101), ("CompressedBitsPerPixel", 0x9102), ("ShutterSpeedValue", 0x9201), ("ApertureValue", 0x9202), ("BrightnessValue", 0x9203), ("ExposureBiasValue", 0x9204), ("MaxApertureValue", 0x9205),
("SubjectDistance", 0x9206), ("MeteringMode", 0x9207), ("LightSource", 0x9208), ("Flash", 0x9209), ("FocalLength", 0x920A), ("SubjectArea", 0x9214), ("MakerNote", 0x927C), ("UserComment", 0x9286),
("SubsecTime", 0x9290), ("SubsecTimeOriginal", 0x9291), ("SubsecTimeDigitized", 0x9292), ("FlashpixVersion", 0xA000), ("ColorSpace", 0xA001), ("PixelXDimension", 0xA002), ("PixelYDimension", 0xA003), ("RelatedSoundFile", 0xA004),
("FlashEnergy", 0xA20B), ("SpatialFrequencyResponse", 0xA20C), ("FocalPlaneXResolution", 0xA20E), ("FocalPlaneYResolution", 0xA20F), ("FocalPlaneResolutionUnit", 0xA210), ("SubjectLocation", 0xA214), ("ExposureIndex", 0xA215), ("SensingMethod", 0xA217),
("FileSource", 0xA300), ("SceneType", 0xA301), ("CFAPattern", 0xA302), ("CustomRendered", 0xA401), ("ExposureMode", 0xA402), ("WhiteBalance", 0xA403), ("DigitalZoomRatio", 0xA404), ("FocalLengthIn35mmFilm", 0xA405),
("SceneCaptureType", 0xA406), ("GainControl", 0xA407), ("Contrast", 0xA408), ("Saturation", 0xA409), ("Sharpness", 0xA40A), ("DeviceSettingDescription", 0xA40B), ("SubjectDistanceRange", 0xA40C), ("ImageUniqueID", 0xA420),
] + TiffDirectory.tags


class GpsDirectory(TiffDirectory):

    tags = [
                ("GPSVersionID", 0x0000), ("GPSLatitudeRef", 0x0001), ("GPSLatitude", 0x0002), ("GPSLongitudeRef", 0x0003), ("GPSLongitude", 0x0004), ("GPSAltitudeRef", 0x0005), ("GPSAltitude", 0x0006), ("GPSTimeStamp", 0x0007),
("GPSSatellites", 0x0008), ("GPSStatus", 0x0009), ("GPSMeasureMode", 0x000A), ("GPSDOP", 0x000B), ("GPSSpeedRef", 0x000C), ("GPSSpeed", 0x000D), ("GPSTrackRef", 0x000E), ("GPSTrack", 0x000F),
("GPSImgDirectionRef", 0x0010), ("GPSImgDirection", 0x0011), ("GPSMapDatum", 0x0012), ("GPSDestLatitudeRef", 0x0013), ("GPSDestLatitude", 0x0014), ("GPSDestLongitudeRef", 0x0015), ("GPSDestLongitude", 0x0016), ("GPSDestBearingRef", 0x0017),
("GPSDestBearing", 0x0018), ("GPSDestDistanceRef", 0x0019), ("GPSDestDistance", 0x001A), ("GPSProcessingMethod", 0x001B), ("GPSAreaInformation", 0x001C), ("GPSDateStamp", 0x001D), ("GPSDifferential", 0x001E),
]


class TiffHeader(Struct):
    # http://www.fileformat.info/format/tiff/corion.htm

    def parse(self):
        order = yield String(2, name="ByteOrder", zero_terminated=False, comment="should be II or MM")
        little_endian = order == "MM"
        if little_endian:
            version = yield UInt16BE(name="TiffVersion", comment="should be 42")
            root = yield UInt32BE(name="FirstDirectory", comment="offset to first directory")
        else:
            version = yield UInt16(name="TiffVersion", comment="should be 42")
            root = yield UInt32(name="FirstDirectory", comment="offset to first directory")
        if version != 42:
            raise FatalError("Invalid tiff version")
        if root != 8:
            raise FatalError("Invalid tiff root")



class TiffAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.DOCUMENT
    name = "TIFF"


    def __init__(self):
        FileTypeAnalyzer.__init__(self)
        self.infos = {}


    def parse(self, hint):
        tiff = yield TiffHeader(name="Tiff", category=Type.HEADER)
        little_endian = tiff["ByteOrder"] == "MM"
        todo = [(tiff["FirstDirectory"], TiffDirectory)]
        while todo:
            dir_offset, dir_class = todo[-1]
            todo.pop()
            self.jump(dir_offset)
            d = yield dir_class(little_endian, parent=tiff, category=Type.HEADER)
            parsed =  {}
            if not "Records" in d:
                continue
            for r in d["Records"]:
                _, sz, cons, cons_le, sf = RECORD_INFOS[r["Type"]]
                if little_endian:
                    cons = cons_le
                    direction = ">"
                else:
                    direction = "<"
                eff_size = r["Length"] * (sz or 1)
                if eff_size > 4:
                    offset = r["OffsetOrData"]
                    embedded = False
                else:
                    offset = r.OffsetOrData.offset
                    embedded = True
                tag = r["Tag"]
                tagname = r.Tag.enum
                if not tagname:
                    continue
                if not embedded:
                    obj = cons(tagname, r["Length"])
                    if sz and r["Length"] > 1:
                        obj = Array(r["Length"], obj, name=tagname)
                    obj.category = Type.META
                    obj.parent = d
                    self.jump(offset)
                    value = yield obj
                else: 
                    if sz == 0:
                        sf = "{}s".format(r["Length"])
                    else:
                        if r["Length"] > 1:
                            sf = sf * r["Length"]
                        sf = direction + sf
                    value = struct.unpack(sf, self.read(offset, eff_size))
                    if len(value) == 1:
                        value = value[0]
                if value is not None and tagname in TIFF_TAGS_FOR_META:
                    self.infos[TIFF_TAGS_FOR_META[tagname]] = str(value)
                    self.add_metadata(TIFF_TAGS_FOR_META[tagname], str(value))
                # parse private tags
                if tag == 0x8769 and r["OffsetOrData"]:
                    todo.append((r["OffsetOrData"], ExifDirectory))
                elif tag == 0x8825 and r["OffsetOrData"]:
                    todo.append((r["OffsetOrData"], GpsDirectory))
            if d["NextDirectory"]:
                todo.append((d["NextDirectory"], TiffDirectory))
