#!/usr/bin/python3

import argparse
import os
import gi

gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import GLib, Gio, GdkPixbuf, Gdk, Gtk  # type: ignore

try:
    import argcomplete
except ModuleNotFoundError:
    pass

NAME = "org.gnome.Shell.Screenshot"
OBJECT_PATH = "/org/gnome/Shell/Screenshot"
INTERFACE = "org.gnome.Shell.Screenshot"

class Screenshot:
    def __init__(self):
        self._proxy = Gio.DBusProxy.new_for_bus_sync(
            bus_type = Gio.BusType.SESSION,
            flags = Gio.DBusProxyFlags.NONE,
            info = None,
            name = NAME,
            object_path = OBJECT_PATH,
            interface_name = INTERFACE,
            cancellable = None,
        )

    def screenshot(self, include_cursor) -> GdkPixbuf.Pixbuf:
        variant = self._proxy.call_sync(
            method_name = "Screenshot",
            parameters = GLib.Variant(
                "(bbs)",
                (
                    include_cursor,
                    True,
                    self._get_tmp_filename(),
                ),
            ),
            flags = Gio.DBusCallFlags.NO_AUTO_START,
            timeout_msec = -1,
            cancellable = None,
        )
        return self._get_screenshot_from_result(variant)

    def screenshot_window(self, include_cursor) -> GdkPixbuf.Pixbuf:
        variant = self._proxy.call_sync(
            method_name = "ScreenshotWindow",
            parameters = GLib.Variant(
                "(bbbs)",
                (
                    True,
                    include_cursor,
                    True,
                    self._get_tmp_filename(),
                ),
            ),
            flags = Gio.DBusCallFlags.NO_AUTO_START,
            timeout_msec = -1,
            cancellable = None,
        )
        return self._get_screenshot_from_result(variant)

    def screenshot_area(self, x, y, width, height) -> GdkPixbuf.Pixbuf:
        variant = self._proxy.call_sync(
            method_name = "ScreenshotArea",
            parameters = GLib.Variant(
                "(iiiibs)",
                (
                    x,
                    y,
                    width,
                    height,
                    True,
                    self._get_tmp_filename(),
                ),
            ),
            flags = Gio.DBusCallFlags.NO_AUTO_START,
            timeout_msec = -1,
            cancellable = None,
        )
        return self._get_screenshot_from_result(variant)

    def interactive_screenshot(self) -> GdkPixbuf.Pixbuf:
        variant = self._proxy.call_sync(
            method_name = "InteractiveScreenshot",
            parameters = None,
            flags = Gio.DBusCallFlags.NO_AUTO_START,
            timeout_msec = GLib.MAXINT,
            cancellable = None,
        )
        [success, uri] = variant
        file = Gio.File.new_for_uri(uri)
        return self._get_screenshot_from_result((success, file.get_path()))

    def select_area(self) -> tuple[int, int, int, int]:
        variant = self._proxy.call_sync(
            method_name = "SelectArea",
            parameters = None,
            flags = Gio.DBusCallFlags.NO_AUTO_START,
            timeout_msec = -1,
            cancellable = None,
        )
        [x, y, width, height] = variant
        return (x, y, width, height)

    def _get_screenshot_from_result(self, variant):
        [success, filename] = variant
        assert success
        screenshot = GdkPixbuf.Pixbuf.new_from_file(filename)
        GLib.unlink(filename)
        return screenshot

    def _get_tmp_filename(self):
        path = GLib.build_filenamev([
            GLib.get_user_cache_dir(),
            "gnome-screenshot-tool",
        ])
        GLib.mkdir_with_parents(path, 0o700)
        return GLib.build_filenamev([path, f'scr-{GLib.random_int()}'])

class ScreenshotApp(Gtk.Application):
    def __init__(self, config):
        super().__init__(application_id="org.gnome.Screenshot")

        self._config = config

    def do_startup(self):
        Gtk.Application.do_startup(self)
        self._proxy = Screenshot()

    def do_activate(self):
        self.hold()
        if self._config.area:
            self._selected_area = self._proxy.select_area()
        self._start_screenshot_timeout()

    def _start_screenshot_timeout(self):
        GLib.timeout_add_seconds(self._config.delay, self._take_screenshot)

    def _take_screenshot(self):
        include_pointer = self._config.include_pointer

        try:
            if self._config.interactive:
                screenshot = self._proxy.interactive_screenshot()
            elif self._config.area:
                [x, y, width, height] = self._selected_area
                screenshot = self._proxy.screenshot_area(x, y, width, height)
            elif self._config.window:
                screenshot = self._proxy.screenshot_window(include_pointer)
            else:
                screenshot = self._proxy.screenshot(include_pointer)

            # if self._config.clipboard:
                # self._save_screenshot_to_clipboard(screenshot)

            if self._config.file:
                target_file = Gio.File.new_for_commandline_arg(self._config.file)
                override = True
            else:
                target_file = self._get_default_target_file()
                override = False

            self._save_screenshot_to_file(screenshot, target_file, override)
        except Exception as e:
            print(f'Failed to take screenshot: {e}')
        finally:
            self.release()

        return GLib.SOURCE_REMOVE

    def _save_screenshot_to_clipboard(self, screenshot):
        dpy = Gdk.Display.get_default()
        clipboard = dpy.get_clipboard()
        clipboard.set(screenshot)

    def _save_screenshot_to_file(self, screenshot, file, override):
        if override:
            ostream = file.replace(None, False, Gio.FileCreateFlags.NONE, None)
        else:
            ostream = file.create(Gio.FileCreateFlags.NONE, None)

        [_, ext] = os.path.splitext(file.get_basename())
        if not ext:
            ext = 'png'
        else:
            ext = ext[1:]

        def find_writable_format(formats, ext):
            for f in formats:
                for e in f.get_extensions():
                    if e == ext and f.is_writable():
                        return f.get_name()
            return None

        formats = GdkPixbuf.Pixbuf.get_formats()
        format = find_writable_format(formats, ext)

        if format == 'png':
            keys = ["tEXt::Software"]
            values = ["gnome-screenshot-tool"]
        else:
            keys = None
            values = None

        screenshot.save_to_streamv(ostream, format, keys, values, None)

    def _get_default_target_file(self):
        path = GLib.build_filenamev([
            GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) or GLib.get_home_dir(),
            "Screenshots",
        ])
        dir = Gio.File.new_for_path(path)
        try:
            dir.make_directory_with_parents(None)
        except GLib.Error as e:
            if not e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.EXISTS):
                raise e

        timestamp = GLib.DateTime.new_now_local().format('%Y-%m-%d %H-%M-%S')
        name = "Screenshot From %s" % (timestamp)

        def suffixes() -> str:
            yield ''

            i = 1
            while True:
                yield f'-{i}'
                i = i + 1

        for suffix in suffixes():
            file = dir.get_child(f'{name}{suffix}.png')
            if not file.query_exists(None):
                return file

def main():
    parser = argparse.ArgumentParser()

    group = parser.add_mutually_exclusive_group()
    group.add_argument("-w", "--window", action="store_true", help="Grab a window instead of the entire screen")
    group.add_argument("-a", "--area", action="store_true", help="Grab an area of the screen instead of the entire screen")
    group.add_argument("-i", "--interactive", action="store_true", help="Interactively set options")

    # parser.add_argument("-c", "--clipboard", action="store_true", help="Send the grab directly to the clipboard")
    parser.add_argument("-p", "--include-pointer", action="store_true", help="Include the pointer with the screenshot")
    parser.add_argument("-d", "--delay", type=int, default=0, help="Take a screenshot after the specified delay (in seconds)")
    parser.add_argument("-f", "--file", type=str, help="Save screenshot directly to this file")

    if argcomplete:
        argcomplete.autocomplete(parser)
    args = parser.parse_args()

    if args.interactive:
        if args.include_pointer:
            print("Option --include-pointer is ignored in interactive mode.")

    app = ScreenshotApp(args)
    app.run(None)

if __name__ == "__main__":
    main()
