from hachoir.metadata.metadata import (registerExtractor, Metadata,
                                       RootMetadata, MultipleMetadata)
from hachoir.parser.image import (
    BmpFile, IcoFile, PcxFile, GifFile, PngFile, TiffFile,
    XcfFile, TargaFile, WMF_File, PsdFile)
from hachoir.parser.image.png import getBitsPerPixel as pngBitsPerPixel
from hachoir.parser.image.xcf import XcfProperty
from hachoir.metadata.safe import fault_tolerant


def computeComprRate(meta, compr_size):
    """
    Compute image compression rate. Skip size of color palette, focus on
    image pixels. Original size is width x height x bpp. Compressed size
    is an argument (in bits).

    Set "compr_data" with a string like "1.52x".
    """
    if (not meta.has("width")
        or not meta.has("height")
            or not meta.has("bits_per_pixel")):
        return
    if not compr_size:
        return
    orig_size = meta.get('width') * meta.get('height') * \
        meta.get('bits_per_pixel')
    meta.compr_rate = float(orig_size) / compr_size


class BmpMetadata(RootMetadata):

    def extract(self, image):
        if "header" not in image:
            return
        hdr = image["header"]
        self.width = hdr["width"].value
        self.height = hdr["height"].value
        bpp = hdr["bpp"].value
        if bpp:
            if bpp <= 8 and "used_colors" in hdr:
                self.nb_colors = hdr["used_colors"].value
            self.bits_per_pixel = bpp
        self.compression = hdr["compression"].display
        self.format_version = ("Microsoft Bitmap version %s"
                               % hdr.getFormatVersion())

        self.width_dpi = hdr["horizontal_dpi"].value
        self.height_dpi = hdr["vertical_dpi"].value

        if "pixels" in image:
            computeComprRate(self, image["pixels"].size)


class TiffMetadata(RootMetadata):
    key_to_attr = {
        "ImageWidth": "width",
        "ImageLength": "height",
        "Software": "producer",
        "ImageDescription": "comment",
        "DocumentName": "title",
        "XResolution": "width_dpi",
        "YResolution": "height_dpi",
        "DateTime": "creation_date",
    }

    def extract(self, tiff):
        if "ifd[0]" in tiff:
            self.useIFD(tiff["ifd[0]"])

    def useIFD(self, ifd):
        attr = {}
        for entry in ifd.array("entry"):
            self.processIfdEntry(ifd, entry, attr)
        if 'BitsPerSample' in attr and 'SamplesPerPixel' in attr:
            self.bits_per_pixel = attr[
                'BitsPerSample'] * attr['SamplesPerPixel']

    @fault_tolerant
    def processIfdEntry(self, ifd, entry, attr):
        tag = entry["tag"].display
        if tag in {"BitsPerSample", "SamplesPerPixel"}:
            value = ifd.getEntryValues(entry)[0].value
            attr[tag] = value
            return

        try:
            attrname = self.key_to_attr[tag]
        except KeyError:
            return
        value = ifd.getEntryValues(entry)[0].value
        if tag in {"XResolution", "YResolution"}:
            value = round(value)
        setattr(self, attrname, value)


class IcoMetadata(MultipleMetadata):
    color_to_bpp = {
        2: 1,
        16: 4,
        256: 8
    }

    def extract(self, icon):
        for index, header in enumerate(icon.array("icon_header")):
            image = Metadata(self)

            # Read size and colors from header
            image.width = header["width"].value
            image.height = header["height"].value
            bpp = header["bpp"].value
            nb_colors = header["nb_color"].value
            if nb_colors != 0:
                image.nb_colors = nb_colors
                if bpp == 0 and nb_colors in self.color_to_bpp:
                    bpp = self.color_to_bpp[nb_colors]
            elif bpp == 0:
                bpp = 8
            image.bits_per_pixel = bpp
            image.setHeader("Icon #%u (%sx%s)"
                            % (1 + index,
                               image.get("width", "?"),
                               image.get("height", "?")))

            # Read compression from data (if available)
            key = "icon_data[%u]/header/codec" % index
            if key in icon:
                image.compression = icon[key].display
            key = "icon_data[%u]/pixels" % index
            if key in icon:
                computeComprRate(image, icon[key].size)

            # Store new image
            self.addGroup("image[%u]" % index, image)


class PcxMetadata(RootMetadata):

    @fault_tolerant
    def extract(self, pcx):
        self.width = 1 + pcx["xmax"].value
        self.height = 1 + pcx["ymax"].value
        self.width_dpi = pcx["horiz_dpi"].value
        self.height_dpi = pcx["vert_dpi"].value
        self.bits_per_pixel = pcx["bpp"].value
        if 1 <= pcx["bpp"].value <= 8:
            self.nb_colors = 2 ** pcx["bpp"].value
        self.compression = "Run-length encoding (RLE)"
        self.format_version = "PCX: %s" % pcx["version"].display
        if "image_data" in pcx:
            computeComprRate(self, pcx["image_data"].size)


class XcfMetadata(RootMetadata):
    # Map image type to bits/pixel
    TYPE_TO_BPP = {0: 24, 1: 8, 2: 8}

    def extract(self, xcf):
        self.width = xcf["width"].value
        self.height = xcf["height"].value
        try:
            self.bits_per_pixel = self.TYPE_TO_BPP[xcf["type"].value]
        except KeyError:
            pass
        self.format_version = xcf["type"].display
        self.readProperties(xcf)

    @fault_tolerant
    def processProperty(self, prop):
        type = prop["type"].value
        if type == XcfProperty.PROP_PARASITES:
            for field in prop["data"]:
                if "name" not in field or "data" not in field:
                    continue
                if field["name"].value == "gimp-comment":
                    self.comment = field["data"].value
        elif type == XcfProperty.PROP_COMPRESSION:
            self.compression = prop["data/compression"].display
        elif type == XcfProperty.PROP_RESOLUTION:
            self.width_dpi = int(prop["data/xres"].value)
            self.height_dpi = int(prop["data/yres"].value)

    def readProperties(self, xcf):
        for prop in xcf.array("property"):
            self.processProperty(prop)


class PngMetadata(RootMetadata):
    TEXT_TO_ATTR = {
        "software": "producer",
    }

    def extract(self, png):
        if "header" in png:
            self.useHeader(png["header"])
        if "time" in png:
            self.useTime(png["time"])
        if "physical" in png:
            self.usePhysical(png["physical"])
        for comment in png.array("text"):
            if "text" not in comment:
                continue
            keyword = comment["keyword"].value
            text = comment["text"].value
            try:
                key = self.TEXT_TO_ATTR[keyword.lower()]
                setattr(self, key, text)
            except KeyError:
                if keyword.lower() != "comment":
                    self.comment = "%s=%s" % (keyword, text)
                else:
                    self.comment = text
        compr_size = sum(data.size for data in png.array("data"))
        computeComprRate(self, compr_size)

    @fault_tolerant
    def useTime(self, field):
        self.creation_date = field.value

    @fault_tolerant
    def usePhysical(self, field):
        self.width_dpi = field["pixel_per_unit_x"].value
        self.height_dpi = field["pixel_per_unit_y"].value

    @fault_tolerant
    def useHeader(self, header):
        self.width = header["width"].value
        self.height = header["height"].value

        # Read number of colors and pixel format
        if "/palette/size" in header:
            nb_colors = header["/palette/size"].value // 3
        else:
            nb_colors = None
        if not header["has_palette"].value:
            if header["has_alpha"].value:
                self.pixel_format = "RGBA"
            else:
                self.pixel_format = "RGB"
        elif "/transparency" in header:
            self.pixel_format = "Color index with transparency"
            if nb_colors:
                nb_colors -= 1
        else:
            self.pixel_format = "Color index"
        self.bits_per_pixel = pngBitsPerPixel(header)
        if nb_colors:
            self.nb_colors = nb_colors

        # Read compression, timestamp, etc.
        self.compression = header["compression"].display


class GifMetadata(RootMetadata):

    def extract(self, gif):
        self.useScreen(gif["/screen"])
        if self.has("bits_per_pixel"):
            self.nb_colors = (1 << self.get('bits_per_pixel'))
        self.compression = "LZW"
        self.format_version = "GIF version %s" % gif["version"].value
        for comments in gif.array("comments"):
            for comment in gif.array(comments.name + "/comment"):
                self.comment = comment.value
        if ("graphic_ctl/has_transp" in gif
                and gif["graphic_ctl/has_transp"].value):
            self.pixel_format = "Color index with transparency"
        else:
            self.pixel_format = "Color index"

    @fault_tolerant
    def useScreen(self, screen):
        self.width = screen["width"].value
        self.height = screen["height"].value
        self.bits_per_pixel = (1 + screen["size_global_map"].value)


class TargaMetadata(RootMetadata):

    def extract(self, tga):
        self.width = tga["width"].value
        self.height = tga["height"].value
        self.bits_per_pixel = tga["bpp"].value
        if tga["nb_color"].value:
            self.nb_colors = tga["nb_color"].value
        self.compression = tga["codec"].display
        if "pixels" in tga:
            computeComprRate(self, tga["pixels"].size)


class WmfMetadata(RootMetadata):

    def extract(self, wmf):
        if wmf.isAPM():
            if "amf_header/rect" in wmf:
                rect = wmf["amf_header/rect"]
                self.width = (rect["right"].value - rect["left"].value)
                self.height = (rect["bottom"].value - rect["top"].value)
            self.bits_per_pixel = 24
        elif wmf.isEMF():
            emf = wmf["emf_header"]
            if "description" in emf:
                desc = emf["description"].value
                if "\0" in desc:
                    self.producer, self.title = desc.split("\0", 1)
                else:
                    self.producer = desc
            if emf["nb_colors"].value:
                self.nb_colors = emf["nb_colors"].value
                self.bits_per_pixel = 8
            else:
                self.bits_per_pixel = 24
            self.width = emf["width_px"].value
            self.height = emf["height_px"].value


class PsdMetadata(RootMetadata):

    @fault_tolerant
    def extract(self, psd):
        self.width = psd["width"].value
        self.height = psd["height"].value
        self.bits_per_pixel = psd["depth"].value * psd["nb_channels"].value
        self.pixel_format = psd["color_mode"].display
        self.compression = psd["compression"].display


registerExtractor(IcoFile, IcoMetadata)
registerExtractor(GifFile, GifMetadata)
registerExtractor(XcfFile, XcfMetadata)
registerExtractor(TargaFile, TargaMetadata)
registerExtractor(PcxFile, PcxMetadata)
registerExtractor(BmpFile, BmpMetadata)
registerExtractor(PngFile, PngMetadata)
registerExtractor(TiffFile, TiffMetadata)
registerExtractor(WMF_File, WmfMetadata)
registerExtractor(PsdFile, PsdMetadata)
