// Olympus Wi-Fi Camera Viewer
// GTK4 + Libadwaita application for browsing and importing images
// from Olympus cameras using the OPC protocol

public class ImageItem : Gtk.FlowBoxChild {
    private Gtk.Image thumbnail_image;
    private Gtk.Label name_label;
    private Gtk.Label date_label;
    private Gtk.Box main_box;
    private Gtk.Overlay overlay;
    private Gtk.CheckButton check_button;

    public ImageInfo info { get; construct; }
    public bool selected {
        get { return check_button.active; }
        set { check_button.active = value; }
    }

    public signal void selection_changed ();

    public ImageItem (ImageInfo info) {
        Object (info: info);
    }

    construct {
        hexpand = true;
        halign = Gtk.Align.CENTER;

        overlay = new Gtk.Overlay ();

        main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 4);
        main_box.margin_start = 4;
        main_box.margin_end = 4;
        main_box.margin_top = 4;
        main_box.margin_bottom = 4;

        // Thumbnail image
        thumbnail_image = new Gtk.Image(   );
        thumbnail_image.pixel_size = 160;
        thumbnail_image.icon_name = "image-x-generic";
        thumbnail_image.halign = Gtk.Align.CENTER;
        thumbnail_image.valign = Gtk.Align.CENTER;

        var image_frame = new Gtk.Frame (null);
        image_frame.child = thumbnail_image;
        image_frame.width_request = 160;
        image_frame.height_request = 120;
        main_box.append (image_frame);

        // File name
        name_label = new Gtk.Label(   info.filename);
        name_label.ellipsize = Pango.EllipsizeMode.END;
        name_label.max_width_chars = 20;
        name_label.halign = Gtk.Align.CENTER;
        var attr_list = new Pango.AttrList ();
        attr_list.insert (Pango.attr_weight_new (Pango.Weight.BOLD));
        name_label.attributes = attr_list;
        main_box.append (name_label);

        // Date
        date_label = new Gtk.Label(   info.display_date);
        date_label.ellipsize = Pango.EllipsizeMode.END;
        date_label.max_width_chars = 20;
        date_label.halign = Gtk.Align.CENTER;
        date_label.opacity = 0.7;
        main_box.append (date_label);

        overlay.child = main_box;

        // Check button overlay for selection (top-left corner)
        check_button = new Gtk.CheckButton(   );
        check_button.halign = Gtk.Align.START;
        check_button.valign = Gtk.Align.START;
        check_button.margin_start = 8;
        check_button.margin_top = 8;
        check_button.add_css_class ("selection-mode");
        overlay.add_overlay (check_button);

        child = overlay;

        check_button.toggled.connect (() => {
            update_selection_style ();
            selection_changed ();
        });

        // Click toggles selection
        var gesture = new Gtk.GestureClick(   );
        gesture.pressed.connect ((n_press, x, y) => {
            if (n_press == 1) {
                check_button.active = !check_button.active;
            }
        });
        add_controller (gesture);

        add_css_class ("image-item");
    }

    private void update_selection_style () {
        if (check_button.active) {
            add_css_class ("selected-image");
        } else {
            remove_css_class ("selected-image");
        }
    }

    public void set_thumbnail_from_bytes (Bytes bytes) {
        try {
            var texture = Gdk.Texture.from_bytes (bytes);
            if (texture != null) {
                thumbnail_image.set_from_paintable (texture);
            } else {
                thumbnail_image.icon_name = "image-x-generic";
            }
        } catch (Error e) {
            warning ("Failed to load thumbnail: %s", e.message);
            thumbnail_image.icon_name = "image-x-generic";
        }
    }
}

public class ConnectPage : Gtk.Box {
    private Gtk.Button connect_button;
    private Gtk.Label status_label;
    private Gtk.Spinner spinner;
    private Gtk.Box status_box;

    public signal void connection_succeeded ();

    public ConnectPage () {
        Object (
            orientation: Gtk.Orientation.VERTICAL,
            spacing: 16,
            halign: Gtk.Align.CENTER,
            valign: Gtk.Align.CENTER
        );
    }

    construct {
        var title_label = new Gtk.Label ("Olympus Wi-Fi Camera");
        var title_attrs = new Pango.AttrList ();
        title_attrs.insert (Pango.attr_scale_new (Pango.Scale.XX_LARGE));
        title_attrs.insert (Pango.attr_weight_new (Pango.Weight.BOLD));
        title_label.attributes = title_attrs;
        title_label.add_css_class ("title-label");
        append (title_label);

        var subtitle_label = new Gtk.Label ("Browse and import photos from your Olympus camera");
        subtitle_label.opacity = 0.7;
        append (subtitle_label);

        append (new Gtk.Label (""));

        // Connect button
        connect_button = new Gtk.Button.with_label(   "Connect to Camera");
        connect_button.add_css_class ("suggested-action");
        connect_button.add_css_class ("circular");
        connect_button.hexpand = false;
        connect_button.width_request = 250;
        connect_button.height_request = 60;
        connect_button.valign = Gtk.Align.CENTER;

        var button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
        button_box.halign = Gtk.Align.CENTER;
        button_box.append (connect_button);
        append (button_box);

        // Status box (spinner + label)
        status_box = new Gtk.Box(   Gtk.Orientation.HORIZONTAL, 8);
        status_box.halign = Gtk.Align.CENTER;
        status_box.visible = false;

        spinner = new Gtk.Spinner ();
        status_box.append (spinner);

        status_label = new Gtk.Label ("");
        status_box.append (status_label);

        append (status_box);

        connect_button.clicked.connect (on_connect_clicked);
    }

    private void on_connect_clicked () {
        set_connecting_state ("Connecting to camera…");
        connect_button.sensitive = false;
        new Thread<void*> ("connect", connect_thread);
    }

    private void* connect_thread () {
        var camera = new CameraClient ();

        try {
            // Step 1: Check/establish OPC connection
            Idle.add(   () => {
                status_label.label = "Establishing OPC connection…";
                return false;
            });
            Thread.usleep (100000);

            if (!camera.check_connection ()) {
                string msg = "Failed: Camera is not responding with OPC protocol.";
                Idle.add (() => {
                    set_idle_state (msg);
                    connect_button.sensitive = true;
                    return false;
                });
                return null;
            }

            // Step 2: Activate session (some cameras require this)
            Idle.add(   () => {
                status_label.label = "Activating session…";
                return false;
            });
            Thread.usleep (100000);
            camera.activate ();

            // Step 3: Switch to playback mode
            Idle.add(   () => {
                status_label.label = "Switching to playback mode…";
                return false;
            });
            Thread.usleep (100000);

            if (!camera.switch_mode ("play")) {
                string msg = "Failed to switch camera to playback mode.";
                Idle.add (() => {
                    set_idle_state (msg);
                    connect_button.sensitive = true;
                    return false;
                });
                return null;
            }

            Idle.add (() => {
                set_idle_state ("Connected! Loading gallery…");
                connect_button.sensitive = true;
                connection_succeeded ();
                return false;
            });

        } catch (Error connect_err) {
            string msg = "Connection failed: %s".printf(   connect_err.message);
            Idle.add (() => {
                set_idle_state (msg);
                connect_button.sensitive = true;
                return false;
            });
        }

        return null;
    }

    private void set_connecting_state (string message) {
        status_box.visible = true;
        spinner.start ();
        status_label.label = message;
        status_label.remove_css_class ("error");
        status_label.remove_css_class ("success");
    }

    private void set_idle_state (string message) {
        spinner.stop ();
        status_label.label = message;
        if (message.has_prefix ("Failed") || message.has_prefix(   "Connection failed")) {
            status_label.add_css_class ("error");
            status_label.remove_css_class ("success");
        } else if (message.has_prefix ("Connected")) {
            status_label.add_css_class ("success");
            status_label.remove_css_class ("error");
        }
    }
}

public class GalleryPage : Gtk.Box {
    private Gtk.FlowBox flowbox;
    private Gtk.Label status_label;
    private Gtk.Button import_button;
    private Gtk.Button disconnect_button;
    private Gtk.ProgressBar progress_bar;
    private Gtk.Box progress_box;
    private Gtk.Label progress_label;

    private ImageInfo[] all_images;
    private ImageItem[] image_widgets;

    public signal void disconnect_requested ();

    public GalleryPage () {
        Object (
            orientation: Gtk.Orientation.VERTICAL,
            spacing: 0
        );
        this.all_images = new ImageInfo[0];
        this.image_widgets = new ImageItem[0];
    }

    construct {
        // Toolbar
        var toolbar_box = new Gtk.Box(   Gtk.Orientation.HORIZONTAL, 8);
        toolbar_box.margin_start = 12;
        toolbar_box.margin_end = 12;
        toolbar_box.margin_top = 8;
        toolbar_box.margin_bottom = 8;

        disconnect_button = new Gtk.Button.with_label ("Disconnect");
        disconnect_button.add_css_class ("flat");
        toolbar_box.append (disconnect_button);

        var spacer = new Gtk.Label ("");
        spacer.hexpand = true;
        toolbar_box.append (spacer);

        status_label = new Gtk.Label ("Loading images…");
        status_label.halign = Gtk.Align.CENTER;
        status_label.hexpand = true;
        toolbar_box.append (status_label);

        var spacer2 = new Gtk.Label ("");
        spacer2.hexpand = true;
        toolbar_box.append (spacer2);

        import_button = new Gtk.Button.with_label ("Import Selected");
        import_button.add_css_class ("suggested-action");
        import_button.sensitive = false;
        toolbar_box.append (import_button);

        append (toolbar_box);
        append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL));

        // Scrolled window with FlowBox
        var scrolled = new Gtk.ScrolledWindow(   );
        scrolled.vexpand = true;
        scrolled.hexpand = true;

        flowbox = new Gtk.FlowBox ();
        flowbox.max_children_per_line = 6;
        flowbox.selection_mode = Gtk.SelectionMode.NONE;
        flowbox.homogeneous = true;
        flowbox.column_spacing = 8;
        flowbox.row_spacing = 8;
        flowbox.margin_start = 12;
        flowbox.margin_end = 12;
        flowbox.margin_top = 8;
        flowbox.margin_bottom = 8;
        flowbox.activate_on_single_click = false;

        scrolled.child = flowbox;
        append (scrolled);

        // Progress box
        progress_box = new Gtk.Box(   Gtk.Orientation.VERTICAL, 4);
        progress_box.margin_start = 12;
        progress_box.margin_end = 12;
        progress_box.margin_top = 8;
        progress_box.margin_bottom = 8;
        progress_box.visible = false;

        progress_label = new Gtk.Label ("");
        progress_box.append (progress_label);

        progress_bar = new Gtk.ProgressBar ();
        progress_bar.show_text = false;
        progress_box.append (progress_bar);

        append (progress_box);

        disconnect_button.clicked.connect (() => { disconnect_requested (); });
        import_button.clicked.connect (on_import_clicked);

        load_images ();
    }

    private void load_images () {
        status_label.label = "Loading images…";
        import_button.sensitive = false;
        new Thread<void*> ("load", load_images_thread);
    }

    private void* load_images_thread () {
        // Create dedicated CameraClient in this thread so Soup.Session
        // runs on a GMainContext owned by this thread (avoids segfault).
        var load_camera = new CameraClient ();
        try {
            var images = load_camera.get_all_images ();
            Idle.add (() => {
                all_images = images;
                populate_gallery ();
                status_label.label = "%d images found".printf(   all_images.length);
                update_import_button_state ();
                return false;
            });
        } catch (Error err) {
            string msg = "Error: %s".printf(   err.message);
            Idle.add (() => {
                status_label.label = msg;
                return false;
            });
        }
        return null;
    }

    private void populate_gallery () {
        var child = flowbox.get_first_child ();
        while (child != null) {
            var next = child.get_next_sibling ();
            flowbox.remove (child);
            child = next;
        }
        image_widgets = new ImageItem[all_images.length];

        for (int i = 0; i < all_images.length; i++) {
            var info = all_images[i];
            var item = new ImageItem (info);
            item.selection_changed.connect (update_import_button_state);
            flowbox.append (item);
            image_widgets[i] = item;
            load_thumbnail_async (item, info.full_path);
        }
    }

    private void load_thumbnail_async (ImageItem item, string path) {
        new Thread<void*> ("thumb", () => {
            // Create a separate CameraClient per thread for thread safety
            var thumb_camera = new CameraClient(   );
            try {
                var bytes = thumb_camera.get_thumbnail (path);
                Idle.add (() => {
                    item.set_thumbnail_from_bytes (bytes);
                    return false;
                });
            } catch (Error e) {
                warning ("Thumbnail failed for %s: %s", path, e.message);
            }
            return null;
        });
    }

    private void update_import_button_state () {
        int count = 0;
        for (int i = 0; i < image_widgets.length; i++) {
            if (image_widgets[i].selected) count++;
        }
        import_button.sensitive = count > 0;
        import_button.label = count > 0
            ? "Import Selected (%d)".printf(   count)
            : "Import Selected";
    }

    private void on_import_clicked () {
        int count = 0;
        for (int i = 0; i < image_widgets.length; i++) {
            if (image_widgets[i].selected) count++;
        }
        if (count == 0) return;

        var selected = new ImageInfo[count];
        int idx = 0;
        for (int i = 0; i < image_widgets.length; i++) {
            if (image_widgets[i].selected) {
                selected[idx] = image_widgets[i].info;
                idx++;
            }
        }

        var file_dialog = new Gtk.FileDialog ();
        file_dialog.title = "Select Destination Directory";
        file_dialog.select_folder.begin ((Gtk.Window?)this.get_root (), null, (obj, res) => {
            try {
                var dest = file_dialog.select_folder.end (res);
                if (dest != null) {
                    start_import (selected, dest);
                }
            } catch (Error e) {
                warning ("Folder selection failed: %s", e.message);
            }
        });
    }

    private void start_import (ImageInfo[] files, GLib.File dest_dir) {
        import_button.sensitive = false;
        disconnect_button.sensitive = false;
        progress_box.visible = true;
        progress_bar.fraction = 0.0;

        int total = files.length;
        progress_label.label = "Importing 0 of %d…".printf(   total);

        // Use a dedicated thread with its own CameraClient instance.
        // Each thread gets a fresh Soup.Session so there's no sharing.
        new Thread<void*>(   "import", () => {
            int completed = 0;
            int failed = 0;
            var import_camera = new CameraClient ();

            for (int f = 0; f < total; f++) {
                var file_info = files[f];
                try {
                    var data = import_camera.download_original (file_info.full_path);
                    var dest_path = dest_dir.get_child (file_info.filename);
                    dest_path.replace_contents (
                        data.get_data (), null, false,
                        GLib.FileCreateFlags.NONE, null, null
                    );
                    completed++;
                } catch (Error e) {
                    failed++;
                    warning ("Failed to import %s: %s", file_info.filename, e.message);
                }

                // Snapshot progress locals so the idle callback does not
                // race with the worker thread updating completed/failed.
                int snap_completed = completed;
                int snap_failed = failed;
                var snap_info = file_info;
                Idle.add (() => {
                    progress_bar.fraction = (double)(snap_completed + snap_failed) / total;
                    progress_label.label = "Importing %d of %d (%s)".printf( 
                        snap_completed, total, snap_info.filename);
                    return false;
                });
            }

            int final_completed = completed;
            int final_failed = failed;
            Idle.add (() => {
                if (final_failed == 0) {
                    progress_label.label = "Successfully imported %d of %d images!".printf(   final_completed, total);
                    progress_label.add_css_class ("success");
                } else {
                    progress_label.label = "Imported %d of %d (%d failed)".printf(   final_completed, total, final_failed);
                    progress_label.add_css_class ("error");
                }
                progress_bar.fraction = 1.0;

                Timeout.add (3000, () => {
                    progress_box.visible = false;
                    progress_bar.fraction = 0.0;
                    progress_label.remove_css_class ("success");
                    progress_label.remove_css_class ("error");
                    import_button.sensitive = true;
                    disconnect_button.sensitive = true;
                    for (int i = 0; i < image_widgets.length; i++) {
                        image_widgets[i].selected = false;
                    }
                    update_import_button_state ();
                    return false;
                });
                return false;
            });
            return null;
        });
    }

    public void refresh () {
        load_images ();
    }
}

public class OlympusApp : Adw.Application {
    public OlympusApp () {
        Object (
            application_id: "com.olympuswifi.viewer",
            flags: ApplicationFlags.DEFAULT_FLAGS
        );
    }

    protected override void activate () {
        load_css ();
        var window = new MainWindow (this);
        window.present ();
    }

    private void load_css () {
        var display = Gdk.Display.get_default ();
        if (display == null) return;
        var css_provider = new Gtk.CssProvider ();
        css_provider.load_from_string (CSS);
        Gtk.StyleContext.add_provider_for_display (
            display,
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        );
    }

    private const string CSS = """
        .title-label { font-weight: bold; }
        .selected-image {
            background-color: alpha(@accent_bg_color, 0.2);
            border: 2px solid @accent_bg_color;
            border-radius: 8px;
        }
        .image-item {
            border-radius: 8px;
            border: 2px solid transparent;
            transition: all 200ms ease;
        }
        .image-item:hover {
            background-color: alpha(@accent_bg_color, 0.08);
        }
        .error { color: @error_bg_color; font-weight: bold; }
        .success { color: @success_bg_color; font-weight: bold; }
        button.circular {
            border-radius: 30px;
            font-size: 1.2em;
            font-weight: bold;
        }
    """;
}

public class MainWindow : Adw.ApplicationWindow {
    private Gtk.Stack stack;
    private ConnectPage connect_page;
    private GalleryPage? gallery_page;

    public MainWindow (Adw.Application app) {
        Object (
            application: app,
            title: "Olympus Wi-Fi Camera Viewer",
            default_width: 900,
            default_height: 700
        );
    }

    construct {
        stack = new Gtk.Stack ();
        stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;

        connect_page = new ConnectPage ();
        connect_page.connection_succeeded.connect (on_connected);
        stack.add_named (connect_page, "connect");

        set_content (stack);
        stack.visible_child = connect_page;
    }

    private void on_connected () {
        if (gallery_page == null) {
            gallery_page = new GalleryPage ();
            gallery_page.disconnect_requested.connect (on_disconnect);
            stack.add_named (gallery_page, "gallery");
        } else {
            gallery_page.refresh ();
        }
        stack.visible_child = gallery_page;
    }

    private void on_disconnect () {
        stack.visible_child = connect_page;
    }
}

public static int main (string[] args) {
    // Parse and strip debug flag before GTK processes args
    var filtered = new string[args.length];
    int out = 0;
    for (int i = 0; i < args.length; i++) {
        if (args[i] == "-d" || args[i] == "--debug") {
            CameraClient.debug = true;
            stdout.printf ("[debug] Enabled verbose HTTP logging\n");
        } else {
            filtered[out] = args[i];
            out++;
        }
    }
    // Trim to actual count
    var clean_args = new string[out];
    for (int i = 0; i < out; i++) {
        clean_args[i] = filtered[i];
    }

    var app = new OlympusApp ();
    return app.run (clean_args);
}
