/* Copyright 2016 Software Freedom Conservancy Inc.
 * Copyright 2017 Jens Georg <mail@jensge.org>
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

[GtkTemplate (ui = "/org/gnome/Shotwell/ui/preferences_dialog.ui")]
public class PreferencesDialog : Gtk.Dialog {
    private class PathFormat {
        public PathFormat(string name, string? pattern) {
            this.name = name;
            this.pattern = pattern;
        }
        public string name;
        public string? pattern;
    }

    public string library_folder {get; set; }

    private static PreferencesDialog preferences_dialog;

    [GtkChild]
    private unowned Gtk.Switch switch_dark;

    [GtkChild]
    private unowned Gtk.ComboBox photo_editor_combo;
    [GtkChild]
    private unowned Gtk.ComboBox raw_editor_combo;
    private SortedList<AppInfo> external_raw_apps;
    private SortedList<AppInfo> external_photo_apps;
    [GtkChild]
    private unowned Shotwell.FolderButton library_dir_button;
    [GtkChild]
    private unowned Gtk.Label library_dir_text;
    [GtkChild]
    private unowned Gtk.DropDown dir_pattern_combo;
    [GtkChild]
    private unowned Gtk.Entry dir_pattern_entry;
    [GtkChild]
    private unowned Gtk.Label dir_pattern_example;
    private bool allow_closing = false;
    private Gee.ArrayList<PathFormat> path_formats = new Gee.ArrayList<PathFormat>();
    private GLib.DateTime example_date = new GLib.DateTime.local(2009, 3, 10, 18, 16, 11);
    [GtkChild]
    private unowned Gtk.CheckButton lowercase;
    private Plugins.ManifestWidgetMediator plugins_mediator = new Plugins.ManifestWidgetMediator();

    [GtkChild]
    private unowned Gtk.DropDown default_raw_developer_combo;

    [GtkChild]
    private unowned Gtk.CheckButton autoimport;
    [GtkChild]
    private unowned Gtk.CheckButton write_metadata;
    [GtkChild]
    private unowned Gtk.Label pattern_help;
    [GtkChild]
    private unowned Gtk.Stack preferences_stack;

    [GtkChild]
    private unowned Gtk.CheckButton transparent_checker_radio;
    [GtkChild]
    private unowned Gtk.CheckButton transparent_solid_radio;
    [GtkChild]
    private unowned Gtk.ColorDialogButton transparent_solid_color;
    [GtkChild]
    private unowned Gtk.CheckButton transparent_none_radio;

    private PreferencesDialog() {
        Object (use_header_bar: Resources.use_header_bar());

        //set_parent_window(AppWindow.get_instance().get_parent_window());
        set_transient_for(AppWindow.get_instance());
        close_request.connect(on_delete);
        response.connect(on_close);
        set_hide_on_close(true);

        transparent_checker_radio.toggled.connect(on_radio_changed);
        transparent_solid_radio.toggled.connect(on_radio_changed);
        transparent_none_radio.toggled.connect(on_radio_changed);

        transparent_solid_radio.bind_property("active",
                                              transparent_solid_color,
                                              "sensitive");

        Gdk.RGBA color = Gdk.RGBA();
        color.parse(Config.Facade.get_instance().get_transparent_background_color());
        transparent_solid_color.rgba = color;
        transparent_solid_color.notify["rgba"].connect(on_color_changed);

        switch (Config.Facade.get_instance().get_transparent_background_type()) {
            case "checkered":
                transparent_checker_radio.active = true;
            break;
            case "solid":
                transparent_solid_radio.active = true;
            break;
            default:
                transparent_none_radio.active = true;
            break;
        }

        library_dir_button.bind_property("folder", library_dir_text, "label", GLib.BindingFlags.DEFAULT | GLib.BindingFlags.SYNC_CREATE, bind_file_to_path, null);

        // First set the initial folder, then connect the property
        library_dir_button.folder = AppDirs.get_import_dir();

        // Ticket #3162 - Move dir pattern blurb into Gnome help.
        // Because specifying a particular snippet of the help requires
        // us to know where its located, we can't hardcode a URL anymore;
        // instead, we ask for the help path, and if we find it, we tell
        // yelp to read from there, otherwise, we read from system-wide.
        string help_path = Resources.get_help_path();

        if (help_path == null) {
            // We're installed system-wide, so use the system help.
	    pattern_help.set_markup("<a href=\"%s\">%s</a>".printf(Resources.DIR_PATTERN_URI_SYSWIDE,  _("(Help)")));
        } else {
            // We're being run from the build directory; we'll have to handle clicks to this
            // link manually ourselves, due to a limitation of help: URIs.
	    pattern_help.set_markup("<a href=\"dummy:\">%s</a>".printf(_("(Help)")));
            pattern_help.activate_link.connect(on_local_pattern_help);
        }

        add_to_dir_formats(_("Year%sMonth%sDay").printf(Path.DIR_SEPARATOR_S, Path.DIR_SEPARATOR_S),
            "%Y" + Path.DIR_SEPARATOR_S + "%m" + Path.DIR_SEPARATOR_S + "%d");
        add_to_dir_formats(_("Year%sMonth").printf(Path.DIR_SEPARATOR_S), "%Y" +
            Path.DIR_SEPARATOR_S + "%m");
        add_to_dir_formats(_("Year%sMonth-Day").printf(Path.DIR_SEPARATOR_S),
            "%Y" + Path.DIR_SEPARATOR_S + "%m-%d");
        add_to_dir_formats(_("Year-Month-Day"), "%Y-%m-%d");
        add_to_dir_formats(_("Custom"), null); // Custom must always be last.
        dir_pattern_combo.notify["selected-item"].connect(on_dir_pattern_combo_changed);
        dir_pattern_entry.changed.connect(on_dir_pattern_entry_changed);

        lowercase.toggled.connect(on_lowercase_toggled);

        ((Gtk.Box)preferences_stack.get_child_by_name("plugins")).append(plugins_mediator);
        ((Gtk.Box)preferences_stack.get_child_by_name("profiles")).append(new Shotwell.ProfileBrowser());


        populate_preference_options();

        photo_editor_combo.changed.connect(on_photo_editor_changed);
        raw_editor_combo.changed.connect(on_raw_editor_changed);

        autoimport.set_active(Config.Facade.get_instance().get_auto_import_from_library());

        write_metadata.set_active(Config.Facade.get_instance().get_commit_metadata_to_masters());

        var model = (Gtk.StringList)default_raw_developer_combo.model;
        model.append(RawDeveloper.CAMERA.get_label());
        model.append(RawDeveloper.SHOTWELL.get_label());
        set_raw_developer_combo(Config.Facade.get_instance().get_default_raw_developer());
        default_raw_developer_combo.notify["selected-item"].connect(on_default_raw_developer_changed);
        switch_dark.active = Gtk.Settings.get_default().gtk_application_prefer_dark_theme;
        switch_dark.notify["active"].connect(on_theme_variant_changed);
    }

    public void populate_preference_options() {
        populate_app_combo_box(photo_editor_combo, PhotoFileFormat.get_editable_mime_types(),
            Config.Facade.get_instance().get_external_photo_app(), out external_photo_apps);

        populate_app_combo_box(raw_editor_combo, PhotoFileFormat.RAW.get_mime_types(),
            Config.Facade.get_instance().get_external_raw_app(), out external_raw_apps);

        library_dir_text.set_label(AppDirs.get_import_dir().get_path());

        setup_dir_pattern(dir_pattern_combo, dir_pattern_entry);

        lowercase.set_active(Config.Facade.get_instance().get_use_lowercase_filenames());
    }

    private void on_theme_variant_changed(GLib.Object o, GLib.ParamSpec ps) {
        var config = Config.Facade.get_instance();
        config.set_gtk_theme_variant(switch_dark.active);

        Gtk.Settings.get_default().gtk_application_prefer_dark_theme = switch_dark.active;
    }

    private void on_radio_changed() {
        var config = Config.Facade.get_instance();

        if (transparent_checker_radio.active) {
            config.set_transparent_background_type("checkered");
        } else if (transparent_solid_radio.active) {
            config.set_transparent_background_type("solid");
        } else {
            config.set_transparent_background_type("none");
        }
    }

    private void on_color_changed() {
        var color = transparent_solid_color.get_rgba();
        Config.Facade.get_instance().set_transparent_background_color(color.to_string());
    }

    // Ticket #3162, part II - if we're not yet installed, then we have to manually launch
    // the help viewer and specify the full path to the subsection we want...
    private bool on_local_pattern_help(string ignore) {
        try {
            Resources.launch_help(AppWindow.get_instance(), "other-files.page");
        } catch (Error e) {
            message("Unable to launch help: %s", e.message);
        }
        return true;
    }

    private void populate_app_combo_box(Gtk.ComboBox combo_box, string[] mime_types,
        string current_app_executable, out SortedList<AppInfo> external_apps) {
        // get list of all applications for the given mime types
        assert(mime_types.length != 0);
        external_apps = DesktopIntegration.get_apps_for_mime_types(mime_types);

        if (external_apps.size == 0)
            return;

        // populate application ComboBox with app names and icons
        Gtk.CellRendererPixbuf pixbuf_renderer = new Gtk.CellRendererPixbuf();
        Gtk.CellRendererText text_renderer = new Gtk.CellRendererText();
        pixbuf_renderer.xpad = 6;
        combo_box.clear();
        combo_box.pack_start(pixbuf_renderer, false);
        combo_box.pack_start(text_renderer, false);
        combo_box.add_attribute(pixbuf_renderer, "gicon", 0);
        combo_box.add_attribute(text_renderer, "text", 1);

        Gtk.ListStore combo_store = new Gtk.ListStore(2, typeof(GLib.Object), typeof(string));
        Gtk.TreeIter iter;

        int current_app = -1;

        foreach (AppInfo app in external_apps) {
            combo_store.append(out iter);

            Icon? app_icon = app.get_icon();
            if (app_icon != null)
                combo_store.set (iter, 0, app_icon, 1, app.get_name());
            else
                combo_store.set (iter, 1, app.get_name());

            if (app.get_commandline() == current_app_executable)
                current_app = external_apps.index_of(app);
        }

        // TODO: allow users to choose unlisted applications like Nautilus's "Open with -> Other Application..."

        combo_box.set_model(combo_store);

        if (current_app != -1)
            combo_box.set_active(current_app);
    }

    private void setup_dir_pattern(Gtk.DropDown combo_box, Gtk.Entry entry) {
        string? pattern = Config.Facade.get_instance().get_directory_pattern();
        bool found = false;
        if (null != pattern) {
            // Locate pre-built text.
            int i = 0;
            foreach (PathFormat pf in path_formats) {
                if (pf.pattern == pattern) {
                    combo_box.set_selected(i);
                    found = true;
                    break;
                }
                i++;
            }
        } else {
            // Custom path.
            string? s = Config.Facade.get_instance().get_directory_pattern_custom();
            if (!is_string_empty(s)) {
                combo_box.set_selected(path_formats.size - 1); // Assume "custom" is last.
                found = true;
            }
        }

        if (!found) {
            combo_box.set_selected(0);
        }

        on_dir_pattern_combo_changed();
    }

    public static void show_preferences() {
        if (preferences_dialog == null)
            preferences_dialog = new PreferencesDialog();

        preferences_dialog.populate_preference_options();
        preferences_dialog.present();

        // Ticket #3001: Cause the dialog to become active if the user chooses 'Preferences'
        // from the menus a second time.
        preferences_dialog.present();
    }

    // For items that should only be committed when the dialog is closed, not as soon as the change
    // is made.
    private void commit_on_close() {
        Config.Facade.get_instance().set_auto_import_from_library(autoimport.active);
        Config.Facade.get_instance().set_commit_metadata_to_masters(write_metadata.active);

        if (library_dir_button.folder != null)
            AppDirs.set_import_dir(library_dir_button.folder);

        PathFormat pf = path_formats.get((int)dir_pattern_combo.get_selected());
        if (null == pf.pattern) {
            Config.Facade.get_instance().set_directory_pattern_custom(dir_pattern_entry.text);
            Config.Facade.get_instance().set_directory_pattern(null);
        } else {
            Config.Facade.get_instance().set_directory_pattern(pf.pattern);
        }
    }

    private bool on_delete() {
        if (!get_allow_closing())
            return true;

        commit_on_close();
        return false; //hide_on_delete(); //prevent widgets from getting destroyed
    }

    private void on_close() {
        if (!get_allow_closing())
            return;

        set_visible(false);
        commit_on_close();
    }

    private void on_dir_pattern_combo_changed() {
        PathFormat pf = path_formats.get((int)dir_pattern_combo.get_selected());
        if (null == pf.pattern) {
            // Custom format.
            string? dir_pattern = Config.Facade.get_instance().get_directory_pattern_custom();
            if (is_string_empty(dir_pattern))
                dir_pattern = "";
            dir_pattern_entry.set_text(dir_pattern);
            dir_pattern_entry.editable = true;
            dir_pattern_entry.sensitive = true;
        } else {
            dir_pattern_entry.set_text(pf.pattern);
            dir_pattern_entry.editable = false;
            dir_pattern_entry.sensitive = false;
        }
    }

    private void on_dir_pattern_entry_changed() {
         string example = example_date.format(dir_pattern_entry.text);
         if (is_string_empty(example) && !is_string_empty(dir_pattern_entry.text)) {
            // Invalid pattern.
            dir_pattern_example.set_text(_("Invalid pattern"));
            dir_pattern_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "dialog-error");
            dir_pattern_entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, false);
            set_allow_closing(false);
         } else {
            // Valid pattern.
            dir_pattern_example.set_text(example);
            dir_pattern_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, null);
            set_allow_closing(true);
         }
    }

    private void set_allow_closing(bool allow) {
        set_deletable(allow);
        allow_closing = allow;
    }

    private bool get_allow_closing() {
        return allow_closing;
    }

    private void on_photo_editor_changed() {
        int photo_app_choice_index = (photo_editor_combo.get_active() < external_photo_apps.size) ?
            photo_editor_combo.get_active() : external_photo_apps.size;

        AppInfo app = external_photo_apps.get_at(photo_app_choice_index);

        Config.Facade.get_instance().set_external_photo_app(DesktopIntegration.get_app_open_command(app));

        debug("setting external photo editor to: %s", DesktopIntegration.get_app_open_command(app));
    }

    private void on_raw_editor_changed() {
        int raw_app_choice_index = (raw_editor_combo.get_active() < external_raw_apps.size) ?
            raw_editor_combo.get_active() : external_raw_apps.size;

        AppInfo app = external_raw_apps.get_at(raw_app_choice_index);

        Config.Facade.get_instance().set_external_raw_app(app.get_commandline());

        debug("setting external raw editor to: %s", app.get_commandline());
    }

    private RawDeveloper raw_developer_from_combo() {
        if (default_raw_developer_combo.get_selected() == 0)
            return RawDeveloper.CAMERA;
        return RawDeveloper.SHOTWELL;
    }

    private void set_raw_developer_combo(RawDeveloper d) {
        if (d == RawDeveloper.CAMERA)
            default_raw_developer_combo.set_selected(0);
        else
            default_raw_developer_combo.set_selected(1);
    }

    private void on_default_raw_developer_changed() {
        Config.Facade.get_instance().set_default_raw_developer(raw_developer_from_combo());
    }

    private void add_to_dir_formats(string name, string? pattern) {
        PathFormat pf = new PathFormat(name, pattern);
        path_formats.add(pf);
        ((Gtk.StringList)dir_pattern_combo.model).append(name);
    }

    private void on_lowercase_toggled() {
        Config.Facade.get_instance().set_use_lowercase_filenames(lowercase.get_active());
    }
}
