#!/usr/bin/env python3


import sys

import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
gi.require_version('GimpUi', '3.0')
from gi.repository import GimpUi
from gi.repository import GLib

"""
import sys
import gi
import datetime


# I use this when debugging. Any 'print()' statements get sent to 'file'.

# Trace errors by recording them to a file.
# If the file doesn't exist, create it.
import time
file = sys.stderr = sys.stdout = open("D:\\error.txt", 'a')
_start = f"\nPhotoBoothPro, {time.ctime()}"
_end = "_" * (80 - len(_start))
print(f"{_start}{_end}")
"""

gi.require_version('Gimp', '3.0')
gi.require_version('Gegl', '0.4')
from gi.repository import Gegl, Gimp, GLib   # noqa

STRIP_W, STRIP_H = 600, 3200
MARGIN, FRAME_H = 40, 400


def run_filter(layer, filter_, q, mode, opacity):
    """
    layer: Gimp.Layer
        Receive filter output.
    filter_: Gimp.DrawableFilter
    q: iterable
        [(property str, property value)]
    mode: Gimp.LayerMode
        Identify the layer blend mode with an enum (int).
    opacity: float
        .0 to 100.
    """
    config = filter_.get_config()

    for q1 in q:
        config.set_property(*q1)

    filter_.set_blend_mode(mode)
    filter_.set_opacity(opacity)
    filter_.update()
    layer.append_filter(filter_)
    return filter_


def make_filter(layer, s):
    """
    Create a "Gimp.DrawableFilter" by string.

    layer: Gimp.Layer
        The filter name, e.g. 'oil-paint'.
    """
    print(f"Making filter: {s}")
    return Gimp.DrawableFilter.new(layer, f'gegl:{s}', "")


def do_color_balance(
    drawable,
    transfer_mode,
    preserve_lum,
    cyan_red,
    magenta_green,
    yellow_blue
):
    """Adjust color balance of a drawable."""
    procedure = Gimp.get_pdb().lookup_procedure('gimp-drawable-color-balance')
    config = procedure.create_config()
    config.set_property('drawable', drawable)
    config.set_property('transfer-mode', transfer_mode)
    config.set_property('preserve-lum', preserve_lum)
    config.set_property('cyan-red', cyan_red)
    config.set_property('magenta-green', magenta_green)
    config.set_property('yellow-blue', yellow_blue)
    result = procedure.run(config)
    success = result.index(0)
    return success


def apply_schneider_look(image, drawable):
    """Applies optical and chemical effects."""
    # 1. Mirror
    drawable.transform_flip_simple(Gimp.OrientationType.HORIZONTAL, True, 0)

    # Prepare arguments for Lens Distortion.
    filter_ = make_filter(drawable, 'lens-distortion')

    # 2. Schneider Optical Effects
    # Use the default filter settings, '[]', for Lens Distortion.
    run_filter(
        drawable,
        filter_,
        [],
        Gimp.LayerMode.NORMAL,
        100.
    )

    filter_ = make_filter(drawable, 'bloom')

    # Use the default filter settings, '[]', for Bloom.
    run_filter(
        drawable,
        filter_,
        [],
        Gimp.LayerMode.NORMAL,
        100.
    )

    # 3. Chemical Color Balance
    preserve_lum = True
    cyan_red = -0.15
    magenta_green = 0.10
    yellow_blue = -0.25

    do_color_balance(
        drawable,
        Gimp.TransferMode.SHADOWS,
        preserve_lum,
        cyan_red, magenta_green, yellow_blue
    )
    do_color_balance(
        drawable,
        Gimp.TransferMode.HIGHLIGHTS,
        preserve_lum,
        cyan_red, magenta_green, yellow_blue
    )
    do_color_balance(
        drawable,
        Gimp.TransferMode.MIDTONES,
        preserve_lum,
        cyan_red, magenta_green, yellow_blue
    )

    # 4. Silver Halide Grain
    filter_ = make_filter(drawable, 'noise-rgb')
    run_filter(
        drawable,
        filter_,
        [],
        Gimp.LayerMode.NORMAL,
        100.
    )


def photobooth_main_proc(
    procedure,
    run_mode,
    image,
    drawables,
    args,
    data,
):
    Gimp.context_push()
    image.undo_group_start()

    new_image = Gimp.Image.new(STRIP_W, STRIP_H, Gimp.ImageBaseType.RGB)

    Gimp.Display.new(new_image)     #

    bg_color = Gegl.Color.new('#F9F7F2')
    bg_layer = Gimp.Layer.new(
        new_image,
        'Paper Base',
        STRIP_W,
        STRIP_H,
        Gimp.ImageType.RGBA_IMAGE,
        100.,
        Gimp.LayerMode.NORMAL,
    )
    new_image.insert_layer(bg_layer, None, 0)
    Gimp.context_set_background(bg_color)
    bg_layer.fill(Gimp.FillType.BACKGROUND)

    y_offset = MARGIN

    for i in range(min(len(drawables), 4)):
        source_layer = drawables[i]
        new_frame = Gimp.Layer.new_from_drawable(source_layer, new_image)

        new_image.insert_layer(new_frame, None, -1)
        apply_schneider_look(new_image, new_frame)

        target_w = STRIP_W - MARGIN * 2
        scale_ratio = target_w / new_frame.get_width()

        new_frame.scale(
            target_w, int(new_frame.get_height() * scale_ratio), True
        )
        new_frame.set_offsets(MARGIN, y_offset)
        y_offset += FRAME_H + 15

    # Mechanical Date Stamp
    stamp_color = Gegl.Color.new('#B22222')
    date_str = datetime.datetime.now().strftime('PHOTO-ME - %d %b %Y').upper()
    Gimp.context_set_foreground(stamp_color)

    # This should work but doesn't.
    font = Gimp.Font.get_by_name('Sans-serif')

    # Text Layer logic for GIMP 3
    stamp_layer = Gimp.TextLayer.new(
        new_image,
        date_str,
        font,
        18.0,
        Gimp.Unit.pixel()
    )
    new_image.insert_layer(stamp_layer, None, 0)

    if stamp_layer:
        stamp_layer.transform_rotate_simple(
            Gimp.OrientationType.VERTICAL, True, 0, 0
        )
        # stamp_layer.set_offsets(STRIP_W - 35, STRIP_H - 450)  Needs work.
        stamp_layer.set_opacity(70.)

    Gimp.Display.new(new_image)
    image.undo_group_end()
    Gimp.context_pop()
    Gimp.displays_flush()
    return procedure.new_return_values(
        Gimp.PDBStatusType.SUCCESS, GLib.Error()
    )


class PhotoboothPro(Gimp.PlugIn):

    def do_query_procedures(self):
        return ['python-fu-photobooth-pro']

    def do_create_procedure(self, name):
        procedure = Gimp.ImageProcedure.new(
            self, name, Gimp.PDBProcType.PLUGIN, photobooth_main_proc, None
        )
        procedure.set_image_types('RGB*')
        procedure.set_documentation(
            'Schneider Photobooth Pro',
            'Full analog strip recreation',
            name
        )
        procedure.set_menu_label('Schneider Photobooth Pro...')
        procedure.add_menu_path('<Image>/Filters/Artistic')
        procedure.set_attribution('Gemini', 'Gemini', '2026')
        return procedure


Gimp.main(PhotoboothPro.__gtype__, sys.argv)
