/* window.vala
 *
 * Copyright 2020 James Westman
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


[GtkTemplate (ui = "/net/flyingpimonster/Camera/ui/window.ui")]
public class Camera.Window : Hdy.ApplicationWindow {
    [GtkChild] Aperture.Viewfinder viewfinder;
    [GtkChild] Gallery gallery;
    [GtkChild] ControlsOverlay overlay;


    public Settings settings;


    private string? last_output_time;
    private int output_file_suffix;

    private bool countdown_active;
    private uint countdown_timer_id;

    private bool recording_active = false;
    private SimpleAction action_take_picture;


    public Window (Gtk.Application app) {
        Object (application: app);
    }

    construct {
        this.settings = new Settings("net.flyingpimonster.Camera");

        overlay.group.setup(gallery);

        this.create_about_dialog();
        this.create_countdown_action();
        this.create_misc_actions();

        set_capture_mode(this.settings.get_string("capture-mode"));

        viewfinder.notify["state"].connect(on_viewfinder_state_changed);
        on_viewfinder_state_changed();

        this.on_gallery_progress_changed();
    }

    private void create_about_dialog() {
        string[] authors = {
            "James Westman <james@flyingpimonster.net>"
        };
        string[] artists = {
            "Tobias Bernard"
        };

        var action = new SimpleAction("about", null);

        action.activate.connect(() => {
            Gtk.show_about_dialog(this,
                "comments", _("A simple camera app"),
                "program-name", _("Camera"),
                "version", BUILD_VERSION + "-" + BUILD_COMMIT,
                "authors", authors,
                "artists", artists,
                "license-type", Gtk.License.GPL_3_0,
                "logo-icon-name", APPLICATION_ID,
                "website", "https://gitlab.gnome.org/jwestman/camera"
            );
        });

        this.add_action(action);
    }

    private void create_countdown_action() {
        var action = this.settings.create_action("countdown");

        this.settings.changed["countdown"].connect((key) => {
            overlay.group.popdown_countdown_button();
            countdown_cancel();
        });

        this.add_action(action);
    }

    private void create_misc_actions() {
        var close_action = new SimpleAction("close", null);
        close_action.activate.connect(() => {
            close();
        });
        add_action(close_action);

        var escape = new SimpleAction("escape", null);
        escape.activate.connect(() => {
            if (this.countdown_active) {
                this.countdown_cancel();
            } else {
                gallery.close();
            }
        });
        this.add_action(escape);

        var left = new SimpleAction("go-left", null);
        left.activate.connect(() => {
            this.gallery.scroll_forward();
        });
        this.add_action(left);

        var right = new SimpleAction("go-right", null);
        right.activate.connect(() => {
            this.gallery.scroll_backward();
        });
        this.add_action(right);

        this.action_take_picture = new SimpleAction("take-picture", null);
        this.action_take_picture.activate.connect(this.on_take_picture_activated);
        this.add_action(this.action_take_picture);

        var next_camera = new SimpleAction("next-camera", null);
        next_camera.activate.connect(on_next_camera);
        this.add_action(next_camera);

        var picture_mode = new SimpleAction.stateful("picture-mode", null, false);
        picture_mode.change_state.connect(this.on_picture_mode);
        this.add_action(picture_mode);

        var video_mode = new SimpleAction.stateful("video-mode", null, false);
        video_mode.change_state.connect(this.on_video_mode);
        this.add_action(video_mode);

        var toggle_gallery = new SimpleAction("toggle-gallery", null);
        toggle_gallery.activate.connect(this.toggle_gallery);
        this.add_action(toggle_gallery);
    }

    private string get_filename(UserDirectory user_dir, string extension) {
        var pictures = File.new_for_path(Environment.get_user_special_dir(user_dir));
        var time = new DateTime.now_local().format("%F_%T");
        File file;

        if (last_output_time == time) {
            output_file_suffix ++;
            file = pictures.get_child("%s_%d.%s".printf(time, output_file_suffix, extension));
        } else {
            output_file_suffix = 0;
            last_output_time = time;
            file = pictures.get_child(time + "." + extension);
        }

        return file.get_path();
    }

    private string get_image_filename() {
        return get_filename(PICTURES, "jpg");
    }

    private string get_video_filename() {
        return get_filename(VIDEOS, "mkv");
    }

    [GtkCallback]
    private void on_gallery_progress_changed() {
        overlay.group.gallery_progress = gallery.progress;
    }


    private void toggle_gallery() {
        if (!this.gallery.is_viewfinder_visible) {
            this.gallery.close();
        } else {
            this.gallery.open();
        }
    }


    private async void take_picture() {
        try {
            // disable action while taking the picture
            this.action_take_picture.set_enabled(false);

            Gdk.Pixbuf pixbuf = yield this.viewfinder.take_picture_async(null);
            this.gallery.add_image(pixbuf);

            File file = File.new_for_path(get_image_filename());
            var stream = yield file.create_async(NONE);

            yield pixbuf.save_to_stream_async(stream, "jpeg");
        } catch (Error e) {
            this.handle_io_error(e);
        }

        this.action_take_picture.set_enabled(true);
    }


    private void shutter_action() {
        string mode = this.settings.get_string("capture-mode");

        if (mode == "video") {
            if (this.recording_active) {
                /* disable the button while the video is ending */
                this.action_take_picture.set_enabled(false);
                viewfinder.stop_recording_async.begin(null, (obj, res) => {
                    this.recording_active = false;
                    this.action_take_picture.set_enabled(true);
                });
                set_shutter_mode(VIDEO);
            } else {
                this.recording_active = true;
                viewfinder.start_recording_to_file(get_video_filename());
                set_shutter_mode(RECORDING);
            }
        } else {
            this.take_picture.begin();
        }
    }


    private void on_take_picture_activated() {
        if (this.recording_active) {
            shutter_action();
        } else if (settings.get_int("countdown") > 0) {
            if (countdown_active) {
                countdown_cancel();
            } else {
                countdown_start();
            }
        } else {
            shutter_action();
        }
    }


    /**
     * Sets the capture mode ("picture" or "video").
     *
     * Updates the user interface and the capture-mode application setting.
     */
    private void set_capture_mode(string mode) {
        this.settings.set_string("capture-mode", mode);
        countdown_cancel();

        if (mode == "picture") {
            set_shutter_mode(PICTURE);
            ((SimpleAction) lookup_action("picture-mode")).change_state(true);
            ((SimpleAction) lookup_action("video-mode")).change_state(false);
        } else {
            set_shutter_mode(VIDEO);
            ((SimpleAction) lookup_action("picture-mode")).change_state(false);
            ((SimpleAction) lookup_action("video-mode")).change_state(true);
        }
    }


    /**
     * Handler for the picture-mode action
     */
    private void on_picture_mode(SimpleAction action, Variant? parameter) {
        if (action.get_state().get_boolean() == parameter.get_boolean()) {
            return;
        }

        action.set_state(parameter);

        if (parameter.get_boolean()) {
            set_capture_mode("picture");
        }
    }


    /**
     * Handler for the video-mode action
     */
    private void on_video_mode(SimpleAction action, Variant? parameter) {
        if (action.get_state().get_boolean() == parameter.get_boolean()) {
            return;
        }

        action.set_state(parameter);

        if (parameter.get_boolean()) {
            set_capture_mode("video");
        }
    }


    private void set_shutter_mode(ShutterButtonMode mode) {
        overlay.group.shutter_mode = mode;
    }


    private void on_viewfinder_state_changed() {
        bool ready = viewfinder.state == READY;
        overlay.group.switcher_sensitive = ready;
    }


    private void countdown_start() {
        if (countdown_active) {
            return;
        }

        int duration = this.settings.get_int("countdown");

        overlay.group.shutter_countdown = duration;
        overlay.group.start_countdown();

        countdown_timer_id = Timeout.add(duration * 1000, on_countdown_timeout);
        countdown_active = true;
    }


    private bool on_countdown_timeout() {
        shutter_action();
        countdown_cleanup();
        return Source.REMOVE;
    }


    private void countdown_cancel() {
        if (countdown_active) {
            countdown_cleanup();
            Source.remove(this.countdown_timer_id);
        }
    }


    private void countdown_cleanup() {
        this.countdown_active = false;
        overlay.group.stop_countdown();
    }


    private void on_next_camera() {
        var devices = Aperture.DeviceManager.get_instance();
        this.viewfinder.camera = devices.next_camera(this.viewfinder.camera);
    }


    private void handle_io_error(Error e) {
        critical("Error saving image: %s", e.message);

        var dialog = new Gtk.MessageDialog.with_markup(this, MODAL, ERROR, OK, "<b>Could not save image!</b>");

        if (e is FileError || e is IOError) {
            dialog.format_secondary_text("%s", e.message);
        } else if (e is Gdk.PixbufError) {
            dialog.format_secondary_text("Could not encode image: %s", e.message);
        } else {
            dialog.format_secondary_text("Unknown error: %s", e.message);
        }

        dialog.run();
        dialog.close();
    }
}

