#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Generate flip-books from videos and animated GIFs
#
# Copyright © 2016-2020 Oliver Lau <ola@ct.de>, Heise Medien GmbH & Co. KG
# All rights reserved.

import os
import sys
import argparse
from PIL import Image, GifImagePlugin
from fpdf import FPDF
# from moviepy.editor import *


class Size:
    """ Class to store the size of a rectangle."""

    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

    def to_tuple(self):
        return self.width, self.height

    def __str__(self):
        return f'Size({self.width}x{self.height})'

    @staticmethod
    def from_tuple(sz):
        return Size(sz[0], sz[1])


class Point:
    """ Class to store a point on a 2D plane."""

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f'Point({self.x}, {self.y})'


class Margin:
    """ Class to store the margins of a rectangular boundary."""

    def __init__(self, top, right, bottom, left):
        self.top = top
        self.right = right
        self.bottom = bottom
        self.left = left

    def __str__(self):
        return f'Margin({self.top}, {self.right}, {self.bottom}, {self.left})'


class AnimatedGif:
    """ Generator for a sequence of Image objects from an animated GIF """

    def __init__(self, im):
        self.im = im

    def __getitem__(self, ix):
        try:
            if ix:
                self.im.seek(ix)
            return self.im
        except EOFError:
            raise IndexError

    def open(self, file_name):
        self.im = Image.open(file_name)


class FlipbookCreator:
    PAPER_SIZES = {
        'a5': Size(210, 148),
        'a4': Size(297, 210),
        'a3': Size(420, 297),
        'letter': Size(279.4, 215.9),
        'legal': Size(355.6, 215.9)
    }
    PAPER_CHOICES = PAPER_SIZES.keys()

    def __init__(self, verbosity=0, input_file_name=''):
        self.verbosity = verbosity
        self.input_file_name = input_file_name
        self.frames = None
        self.clip = None
        if self.input_file_name.endswith('.gif'):
            self.im = Image.open(self.input_file_name)
            self.palette = self.im.getpalette()
            self.frames = AnimatedGif(self.im)
            self.frame_count = len(list(AnimatedGif(self.im)))
            self.fps = 0
            self.last_im = Image.new('P', self.im.size)
            for i in range(0, 2):
                for f in AnimatedGif(self.im):
                    self.last_im.putpalette(self.palette)
                    #  im = Image.alpha_composite(self.last_im, f.convert('RGBA'))
                    im = self.last_im.copy()
                    im.paste(f)
                    self.last_im = im.copy()
        else:
            self.clip = VideoFileClip(self.input_file_name)
            self.fps = self.clip.fps
            self.frame_count = int(self.clip.duration * self.clip.fps)
        if self.verbosity > 0:
            print(f'Opening {self.input_file_name} ...')

    def process(self,
                output_file_name=None,
                dpi=150,
                offset=0,
                fps=10,
                height_mm=50,
                margins=Margin(10, 10, 10, 10),
                paper_format='a4'):

        def draw_raster():
            for ix in range(0, nx + 1):
                xx = x0 + ix * total.width
                pdf.line(xx, y0, xx, y1)
                if offset > 0 and ix != nx:
                    pdf.line(xx + offset, y0, xx + offset, y1)
            for iy in range(0, ny + 1):
                yy = y0 + iy * total.height
                pdf.line(x0, yy, x1, yy)

        height_mm = float(height_mm)
        tmp_files = []
        if self.clip:
            if fps != self.clip.fps:
                if self.verbosity > 0:
                    print(f'Transcoding from {self.clip.fps} fps to {fps} fps ...')
                self.clip.write_videofile('tmp.mp4', fps=fps, audio=False)
                tmp_files.append('tmp.mp4')
                self.clip = VideoFileClip('tmp.mp4')
                self.fps = self.clip.fps
                self.frame_count = int(self.clip.duration * self.fps)
            clip_size = Size.from_tuple(self.clip.size)
        elif self.frames:
            clip_size = Size.from_tuple(self.im.size)

        paper = self.PAPER_SIZES[paper_format.lower()]
        printable_area = Size(paper.width - margins.left - margins.right,
                              paper.height - margins.top - margins.bottom)
        frame_mm = Size(height_mm / clip_size.height * clip_size.width, height_mm)
        total = Size(offset + frame_mm.width, frame_mm.height)
        frame = Size(int(frame_mm.width / 25.4 * dpi),
                     int(frame_mm.height / 25.4 * dpi))
        nx = int(printable_area.width / total.width)
        ny = int(printable_area.height / total.height)
        if self.verbosity > 0:
            print('Input:  {} fps, {}x{}, {} frames'\
                '\n        from: {}'\
                .format(
                    self.fps,
                    clip_size.width,
                    clip_size.height,
                    self.frame_count,
                    self.input_file_name
                ))
            print('Output: {}dpi, {}x{}, {:.2f}mm x {:.2f}mm, {}x{} tiles'\
                '\n        to: {}'\
                .format(
                    dpi,
                    frame.width, frame.height,
                    frame_mm.width, frame_mm.height,
                    nx, ny,
                    output_file_name
                ))
        pdf = FPDF(unit='mm', format=paper_format.upper(), orientation='L')
        pdf.set_compression(True)
        pdf.set_title('Funny video')
        pdf.set_author('Oliver Lau <ola@ct.de> - Heise Medien GmbH & Co. KG')
        pdf.set_creator('flippy')
        pdf.set_keywords('flip-book, video, animated GIF')
        pdf.set_draw_color(128, 128, 128)
        pdf.set_line_width(0.1)
        pdf.set_font('Helvetica', '', 12)
        pdf.add_page()
        i = 0
        page = 0
        tx, ty = -1, 0
        x0, y0 = margins.left, margins.top
        x1, y1 = x0 + nx * total.width, y0 + ny * total.height

        if self.clip:
            all_frames = self.clip.iter_frames()
        elif self.frames:
            all_frames = AnimatedGif(self.im)
        else:
            all_frames = []
        for f in all_frames:
            ready = float(i + 1) / self.frame_count
            if self.verbosity:
                sys.stdout.write('\rProcessing frames |{:30}| {}%'
                                 .format('X' * int(30 * ready), int(100 * ready)))
                sys.stdout.flush()
            tx += 1
            if type(f) == GifImagePlugin.GifImageFile:
                f.putpalette(self.palette)
                self.last_im.paste(f)
                im = self.last_im.convert('RGBA')
            else:
                im = Image.fromarray(f)
                im.thumbnail(frame.to_tuple())
            if tx == nx:
                tx = 0
                ty += 1
                if ty == ny:
                    ty = 0
                    draw_raster()
                    pdf.add_page()
                    page += 1
            temp_file = 'tmp-{}-{}-{}.png'.format(page, tx, ty)
            im.save(temp_file)
            tmp_files.append(temp_file)
            x = x0 + tx * total.width
            y = y0 + ty * total.height
            pdf.image(temp_file,
                      x=x + offset,
                      y=y,
                      w=frame_mm.width,
                      h=frame_mm.height)
            text = Point(x, y + frame_mm.height - 2)
            if offset > 0:
                pdf.rotate(90, text.x, text.y)
                pdf.text(text.x, text.y + 5, '{}'.format(i))
                pdf.rotate(0)
            i += 1

        if y != 0 and x != 0:
            draw_raster()

        if self.verbosity > 0:
            print('\nGenerating PDF ...')
        pdf.output(name=output_file_name)
        if self.verbosity > 0:
            print('Removing temporary files ...')
        for temp_file in tmp_files:
            os.remove(temp_file)


def main():
    parser = argparse.ArgumentParser(description='Generate flip-books from videos.')
    parser.add_argument('video', type=str, help='File name of video/GIF to process')
    parser.add_argument('--out', type=str, help='Name of PDF file to write to', default='flip-book.pdf')
    parser.add_argument('--height', type=float, help='Height of flip-book [mm]', default=30)
    parser.add_argument('--paper', type=str, choices=FlipbookCreator.PAPER_CHOICES, help='paper size.', default='a4')
    parser.add_argument('--offset', type=float, help='Margin left to each frame [mm]', default=15.0)
    parser.add_argument('--phena', action='store_true', help='Create PDF to use in Phenakistoscope')
    parser.add_argument('--dpi', type=int, help='DPI', default=200)
    parser.add_argument('--fps', type=int, help='Frames per second', default=10)
    parser.add_argument('-v', type=int, nargs='?', help='verbosity level', default=1)
    args = parser.parse_args()

    if args.phena:
        print('Phenakistoscope not supported yet.')
        sys.exit(1)

    flippy = FlipbookCreator(
        input_file_name=args.video,
        verbosity=args.v)
    flippy.process(
        paper_format=args.paper,
        output_file_name=args.out,
        height_mm=args.height,
        dpi=args.dpi,
        offset=args.offset
    )

if __name__ == '__main__':
    main()