#
# The Python Imaging Library.
# $Id$
#
# IFUNC IM file handling for PIL
#
# history:
# 1995-09-01 fl   Created.
# 1997-01-03 fl   Save palette images
# 1997-01-08 fl   Added sequence support
# 1997-01-23 fl   Added P and RGB save support
# 1997-05-31 fl   Read floating point images
# 1997-06-22 fl   Save floating point images
# 1997-08-27 fl   Read and save 1-bit images
# 1998-06-25 fl   Added support for RGB+LUT images
# 1998-07-02 fl   Added support for YCC images
# 1998-07-15 fl   Renamed offset attribute to avoid name clash
# 1998-12-29 fl   Added I;16 support
# 2001-02-17 fl   Use 're' instead of 'regex' (Python 2.1) (0.7)
# 2003-09-26 fl   Added LA/PA support
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2001 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#


import re
from PIL import Image, ImageFile, ImagePalette
from PIL._binary import i8

__version__ = "0.7"


# --------------------------------------------------------------------
# Standard tags

COMMENT = "Comment"
DATE = "Date"
EQUIPMENT = "Digitalization equipment"
FRAMES = "File size (no of images)"
LUT = "Lut"
NAME = "Name"
SCALE = "Scale (x,y)"
SIZE = "Image size (x*y)"
MODE = "Image type"

TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
        SCALE: 0, SIZE: 0, MODE: 0}

OPEN = {
    # ifunc93/p3cfunc formats
    "0 1 image": ("1", "1"),
    "L 1 image": ("1", "1"),
    "Greyscale image": ("L", "L"),
    "Grayscale image": ("L", "L"),
    "RGB image": ("RGB", "RGB;L"),
    "RLB image": ("RGB", "RLB"),
    "RYB image": ("RGB", "RLB"),
    "B1 image": ("1", "1"),
    "B2 image": ("P", "P;2"),
    "B4 image": ("P", "P;4"),
    "X 24 image": ("RGB", "RGB"),
    "L 32 S image": ("I", "I;32"),
    "L 32 F image": ("F", "F;32"),
    # old p3cfunc formats
    "RGB3 image": ("RGB", "RGB;T"),
    "RYB3 image": ("RGB", "RYB;T"),
    # extensions
    "LA image": ("LA", "LA;L"),
    "RGBA image": ("RGBA", "RGBA;L"),
    "RGBX image": ("RGBX", "RGBX;L"),
    "CMYK image": ("CMYK", "CMYK;L"),
    "YCC image": ("YCbCr", "YCbCr;L"),
}

# ifunc95 extensions
for i in ["8", "8S", "16", "16S", "32", "32F"]:
    OPEN["L %s image" % i] = ("F", "F;%s" % i)
    OPEN["L*%s image" % i] = ("F", "F;%s" % i)
for i in ["16", "16L", "16B"]:
    OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i)
    OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i)
for i in ["32S"]:
    OPEN["L %s image" % i] = ("I", "I;%s" % i)
    OPEN["L*%s image" % i] = ("I", "I;%s" % i)
for i in range(2, 33):
    OPEN["L*%s image" % i] = ("F", "F;%s" % i)


# --------------------------------------------------------------------
# Read IM directory

split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")


def number(s):
    try:
        return int(s)
    except ValueError:
        return float(s)


##
# Image plugin for the IFUNC IM file format.

class ImImageFile(ImageFile.ImageFile):

    format = "IM"
    format_description = "IFUNC Image Memory"

    def _open(self):

        # Quick rejection: if there's not an LF among the first
        # 100 bytes, this is (probably) not a text header.

        if b"\n" not in self.fp.read(100):
            raise SyntaxError("not an IM file")
        self.fp.seek(0)

        n = 0

        # Default values
        self.info[MODE] = "L"
        self.info[SIZE] = (512, 512)
        self.info[FRAMES] = 1

        self.rawmode = "L"

        while True:

            s = self.fp.read(1)

            # Some versions of IFUNC uses \n\r instead of \r\n...
            if s == b"\r":
                continue

            if not s or s == b'\0' or s == b'\x1A':
                break

            # FIXME: this may read whole file if not a text file
            s = s + self.fp.readline()

            if len(s) > 100:
                raise SyntaxError("not an IM file")

            if s[-2:] == b'\r\n':
                s = s[:-2]
            elif s[-1:] == b'\n':
                s = s[:-1]

            try:
                m = split.match(s)
            except re.error as v:
                raise SyntaxError("not an IM file")

            if m:

                k, v = m.group(1, 2)

                # Don't know if this is the correct encoding,
                # but a decent guess (I guess)
                k = k.decode('latin-1', 'replace')
                v = v.decode('latin-1', 'replace')

                # Convert value as appropriate
                if k in [FRAMES, SCALE, SIZE]:
                    v = v.replace("*", ",")
                    v = tuple(map(number, v.split(",")))
                    if len(v) == 1:
                        v = v[0]
                elif k == MODE and v in OPEN:
                    v, self.rawmode = OPEN[v]

                # Add to dictionary. Note that COMMENT tags are
                # combined into a list of strings.
                if k == COMMENT:
                    if k in self.info:
                        self.info[k].append(v)
                    else:
                        self.info[k] = [v]
                else:
                    self.info[k] = v

                if k in TAGS:
                    n += 1

            else:

                raise SyntaxError("Syntax error in IM header: " +
                                  s.decode('ascii', 'replace'))

        if not n:
            raise SyntaxError("Not an IM file")

        # Basic attributes
        self.size = self.info[SIZE]
        self.mode = self.info[MODE]

        # Skip forward to start of image data
        while s and s[0:1] != b'\x1A':
            s = self.fp.read(1)
        if not s:
            raise SyntaxError("File truncated")

        if LUT in self.info:
            # convert lookup table to palette or lut attribute
            palette = self.fp.read(768)
            greyscale = 1  # greyscale palette
            linear = 1  # linear greyscale palette
            for i in range(256):
                if palette[i] == palette[i+256] == palette[i+512]:
                    if i8(palette[i]) != i:
                        linear = 0
                else:
                    greyscale = 0
            if self.mode == "L" or self.mode == "LA":
                if greyscale:
                    if not linear:
                        self.lut = [i8(c) for c in palette[:256]]
                else:
                    if self.mode == "L":
                        self.mode = self.rawmode = "P"
                    elif self.mode == "LA":
                        self.mode = self.rawmode = "PA"
                    self.palette = ImagePalette.raw("RGB;L", palette)
            elif self.mode == "RGB":
                if not greyscale or not linear:
                    self.lut = [i8(c) for c in palette]

        self.frame = 0

        self.__offset = offs = self.fp.tell()

        self.__fp = self.fp  # FIXME: hack

        if self.rawmode[:2] == "F;":

            # ifunc95 formats
            try:
                # use bit decoder (if necessary)
                bits = int(self.rawmode[2:])
                if bits not in [8, 16, 32]:
                    self.tile = [("bit", (0, 0)+self.size, offs,
                                 (bits, 8, 3, 0, -1))]
                    return
            except ValueError:
                pass

        if self.rawmode in ["RGB;T", "RYB;T"]:
            # Old LabEye/3PC files.  Would be very surprised if anyone
            # ever stumbled upon such a file ;-)
            size = self.size[0] * self.size[1]
            self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
                         ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
                         ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
        else:
            # LabEye/IFUNC files
            self.tile = [("raw", (0, 0)+self.size, offs,
                         (self.rawmode, 0, -1))]

    @property
    def n_frames(self):
        return self.info[FRAMES]

    @property
    def is_animated(self):
        return self.info[FRAMES] > 1

    def seek(self, frame):

        if frame < 0 or frame >= self.info[FRAMES]:
            raise EOFError("seek outside sequence")

        if self.frame == frame:
            return

        self.frame = frame

        if self.mode == "1":
            bits = 1
        else:
            bits = 8 * len(self.mode)

        size = ((self.size[0] * bits + 7) // 8) * self.size[1]
        offs = self.__offset + frame * size

        self.fp = self.__fp

        self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]

    def tell(self):

        return self.frame

#
# --------------------------------------------------------------------
# Save IM files

SAVE = {
    # mode: (im type, raw mode)
    "1": ("0 1", "1"),
    "L": ("Greyscale", "L"),
    "LA": ("LA", "LA;L"),
    "P": ("Greyscale", "P"),
    "PA": ("LA", "PA;L"),
    "I": ("L 32S", "I;32S"),
    "I;16": ("L 16", "I;16"),
    "I;16L": ("L 16L", "I;16L"),
    "I;16B": ("L 16B", "I;16B"),
    "F": ("L 32F", "F;32F"),
    "RGB": ("RGB", "RGB;L"),
    "RGBA": ("RGBA", "RGBA;L"),
    "RGBX": ("RGBX", "RGBX;L"),
    "CMYK": ("CMYK", "CMYK;L"),
    "YCbCr": ("YCC", "YCbCr;L")
}


def _save(im, fp, filename, check=0):

    try:
        image_type, rawmode = SAVE[im.mode]
    except KeyError:
        raise ValueError("Cannot save %s images as IM" % im.mode)

    try:
        frames = im.encoderinfo["frames"]
    except KeyError:
        frames = 1

    if check:
        return check

    fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
    if filename:
        fp.write(("Name: %s\r\n" % filename).encode('ascii'))
    fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
    fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
    if im.mode == "P":
        fp.write(b"Lut: 1\r\n")
    fp.write(b"\000" * (511-fp.tell()) + b"\032")
    if im.mode == "P":
        fp.write(im.im.getpalette("RGB", "RGB;L"))  # 768 bytes
    ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])

#
# --------------------------------------------------------------------
# Registry

Image.register_open(ImImageFile.format, ImImageFile)
Image.register_save(ImImageFile.format, _save)

Image.register_extension(ImImageFile.format, ".im")
