#! /usr/bin/env python
r"""modul imagediff - compares images.
This modul is identified for python 2.5, because PIL library doesn't yet
available for python3.

This modul contains class ImageDiff.
"""

import optparse, sys, difflib, Image, ImageFilter, ImageEnhance


class ImageDiff():
    r"""Class, which provides methods for image comparation.

    It can show differences between two images in PBM format or can
    show similarity in per cents. Important is, that ImageDiff can rotate and
    flip image to find the best similarity. For faster results, images can be resized.
    """
    def __init__(self,file1_name,file2_name, options):
        r"""Constructs ConfigDiff instance.

        parameters:
        file1_name - name of the first file
        file2_name - name of the second file
        param - options from optparse

        instance variables:
        self.file1_name - name of the first file
        self.file2_name - name of the second file
        self.image1 - the first image
        self.image2 - the second image
        """
        self.options = options
        self.file1_name = file1_name
        self.file2_name = file2_name


        try:
            self.image1 = Image.open(self.file1_name)
            self.image2 = Image.open(self.file2_name)
        except IOError, msg :
            sys.stderr.write("I/O error: " + str(msg) + '\n')
            sys.exit(1)

        if self.options.fast:
            self.options.size = (64,64)

    def compare(self):
        r"""Method for images comparing.

        It contains some useful functions:
        ratio - gets two parameters - images and returns number - similarity in
        per cents without images transformating
        best_ratio - gets two parameters - images and returns number - similarity in
        per cents, but it transformates images to find the best ratio.
        pbm_otput - gets two parameters - images and returns difference in PBM format
        """
        image1, image2  = self.image1, self.image2

        def ratio(image1,image2,toleration=0):
            """Function, which goes through pixels in both files and compares
            them to be same or not. It can also resize images, if we will.

            We can compare each pixel with toleration <0-1>. But it is slower.
            """
            if self.options.size[0] > 0 and self.options.size[1] > 0:
                image1 = image1.resize(self.options.size)
                image2 = image2.resize(self.options.size)

            if image1.size != image2.size:
                return 0

            diff_pixels = 0
            if toleration == 0:
                for a,b in zip(image1.getdata(),image2.getdata()):
                    if a != b:
                        diff_pixels += 1
            else:
                for a,b in zip(image1.getdata(),image2.getdata()):
                    if a != b:
                        if (b[0]*float(1-toleration))<= a[0] <=(b[0]*float(1+toleration)) and \
                           (b[1]*float(1-toleration))<= a[1] <=(b[1]*float(1+toleration)) and \
                           (b[2]*float(1-toleration))<= a[2] <=(b[2]*float(1+toleration)):
                            continue
                        else:
                            diff_pixels += 1

            pixels = image1.size[0]*image1.size[1]
            same_pixels = pixels - diff_pixels

            return float(same_pixels) / pixels * 100


        def best_ratio(image1,image2):
            """Function, which returns the best ratio. According to parameters,
            it rotates images or flips images, calculates the ratio and returns the best
            ratio, rotates angle and flip types.
            """
            max_ratio = None
            rotate_angle = None
            flip_type = None

            if self.options.enable_rotation and not self.options.enable_flip:
                #~ print "R"#debug
                rotate_ratios = ratio(image1,image2            ,self.options.toleration),\
                                ratio(image1,image2.rotate(90) ,self.options.toleration),\
                                ratio(image1,image2.rotate(180),self.options.toleration),\
                                ratio(image1,image2.rotate(270),self.options.toleration)

                max_ratio = max_rotate_ratio = max(rotate_ratios)
                rotate_angle = (list(rotate_ratios)).index(max_rotate_ratio)*90

                #~ print "Rotate ratios: " + str(rotate_ratios) + "Rotate angle: " + str(rotate_angle)#debug

            elif self.options.enable_flip and not self.options.enable_rotation:
                #~ print "F"#debug
                flip_types = [None,'Flip left right', 'Flip top bottom']
                flip_ratios = ratio(image1,image2            ,self.options.toleration),\
                              ratio(image1,image2.transpose(Image.FLIP_LEFT_RIGHT) ,self.options.toleration),\
                              ratio(image1,image2.transpose(Image.FLIP_TOP_BOTTOM) ,self.options.toleration)

                max_ratio = max_flip_ratio = max(flip_ratios)
                flip_type = flip_types[list(flip_ratios).index(max_flip_ratio)]

                #~ print "Flip ratios: " + str(flip_ratios) + "Flip type: " + str(flip_type)#debug

            elif self.options.enable_rotation and self.options.enable_flip:
                #~ print "RF"#debug
                rotate_ratios = ratio(image1,image2            ,self.options.toleration),\
                                ratio(image1,image2.rotate(90) ,self.options.toleration),\
                                ratio(image1,image2.rotate(180),self.options.toleration),\
                                ratio(image1,image2.rotate(270),self.options.toleration)

                flip_rotate_ratios = ratio(image1,image2.transpose(Image.FLIP_LEFT_RIGHT)            ,self.options.toleration),\
                                     ratio(image1,image2.transpose(Image.FLIP_LEFT_RIGHT).rotate(90) ,self.options.toleration),\
                                     ratio(image1,image2.transpose(Image.FLIP_LEFT_RIGHT).rotate(180),self.options.toleration),\
                                     ratio(image1,image2.transpose(Image.FLIP_LEFT_RIGHT).rotate(270),self.options.toleration)

                max_rotate_ratio = max(rotate_ratios)
                max_flip_rotate_ratio = max(flip_rotate_ratios)

                if max_rotate_ratio > max_flip_rotate_ratio:
                    max_ratio = max_rotate_ratio
                    rotate_angle = (list(rotate_ratios)).index(max_rotate_ratio)*90
                    flip_type = None
                else:
                    max_ratio = max_flip_rotate_ratio
                    rotate_angle = (list(flip_rotate_ratios)).index(max_flip_rotate_ratio)*90
                    flip_type = 'Flip left right'

                #~ print "Rotate ratios: " + str(rotate_ratios) + "Rotate angle: " + str(rotate_angle)#debug
                #~ print "Flip ratios: " + str(flip_rotate_ratios) + "Flip type: " + str(flip_type)#debug

            else:
                max_ratio = ratio(image1, image2, self.options.toleration)

            return (max_ratio, rotate_angle, flip_type)

        def pbm_otput(image1,image2):
            r"""Function, which returns the difference between two images in PBM
            format.
            """
            max_ratio, rotate_angle, flip_type = best_ratio(image1,image2)
            toleration = self.options.toleration

            yield 'P1\n'
            yield '# difference between ' + str(self.file1_name) + ' and ' + str(self.file2_name) + '\n'
            yield '# ratio: ' + str(max_ratio) + '%\n'

            if flip_type != None:
                if flip_type == 'Flip left right':
                    image2 = image2.transpose(Image.FLIP_LEFT_RIGHT)
                elif flip_type == 'Flip top bottom':
                    image2 = image2.transpose(Image.FLIP_TOP_BOTTOM)
                yield '# Flip type: ' + str(flip_type) + '\n'

            if rotate_angle != None:
                image2 = image2.rotate(rotate_angle)
                yield '# Rotate angle: ' + str(rotate_angle) + '\n'

            if self.options.size[0] > 0 and self.options.size[1] > 0:
                image1 = image1.resize(self.options.size)
                image2 = image2.resize(self.options.size)

            yield str(image1.size[0]) + ' ' + str(image1.size[0])

            if max_ratio == 0:
                for r in image1.size[1]:
                    yield '\n'
                    for s in image1.size[0]:
                        yield '1'
            elif max_ratio == 1:
                for r in image1.size[1]:
                    yield '\n'
                    for s in image1.size[0]:
                        yield '0'


            if toleration == 0:
                for a,b,c in zip(image1.getdata(),image2.getdata(),range(image1.size[0]*image1.size[1])):
                    if c%image1.size[0] == 0:
                        yield '\n'

                    if a != b:
                        yield '1'
                    else:
                        yield '0'

            else:
                for a,b,c in zip(image1.getdata(),image2.getdata(),range(image1.size[0]*image1.size[1])):
                    if c%image1.size[0] == 0:
                        yield '\n'
                    if a != b:
                        if (b[0]*float(1-toleration))<= a[0] <=(b[0]*float(1+toleration)) and \
                           (b[1]*float(1-toleration))<= a[1] <=(b[1]*float(1+toleration)) and \
                           (b[2]*float(1-toleration))<= a[2] <=(b[2]*float(1+toleration)):
                            yield '0'
                        else:
                            yield '1'
                    else:
                        yield '0'

            yield '\n'


        if self.options.ratio:
            max_ratio, rotate_angle, flip_type = best_ratio(image1,image2)
            print ("Ratio: " + str(max_ratio)+'%')
            if flip_type != None:
                print ("Flip: " + str(flip_type))
            if rotate_angle != None:
                print ("Angle: " + str(rotate_angle))

        elif self.options.text_otput:
            sys.stdout.writelines(pbm_otput(image1,image2))

        else:
            sys.stdout.writelines(pbm_otput(image1,image2))


def main():
    r"""Main function. This function is executed as the first when the program starts.
    It parses arguments and compares images.
    """
    #usage for help
    usage = "usage: %prog [options] from_file to_file"
    version="%prog 0.1"
    description="""Image Diff compares images and shows their differences."""

    #standard diffs options
    parser = optparse.OptionParser(usage=usage, version=version, description=description)

    parser.add_option("-t","--text-otput", action="store_true", default=False, help='Shows text output in PBM format.')
    parser.add_option("-r","--ratio", action="store_true", default=False, help='Shows image similarity percentage.')
    parser.add_option("-f","--fast", action="store_true", default=False, help='Resizes images to 64x64 pixels and faster shows the ratio.')

    parser.add_option("-R","--enable-rotation", action="store_true", default=False, help='Rotates images to find the best ratio.')
    parser.add_option("-F","--enable-flip",     action="store_true", default=False, help='Flips images to find the best ratio.')
    parser.add_option("-T","--toleration", action="store",metavar="toleration", type="float", default=0, help='Toleration (0-1) between each pixel colors.')
    parser.add_option("-S","--size", action="store",metavar="x y", type="int",nargs = 2, default=(0,0), help='Resizes images for faster comparation.')

     #parse arguments
    (options, args) = parser.parse_args()

    if len(args) != 2:
        parser.print_help()
        sys.exit(1)

    file1_name, file2_name = args

    ImageDiff(file1_name, file2_name, options).compare()


if __name__ == "__main__":
    main()
