/* Copyright 2019 Jens Georg.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

namespace Shotwell {
    class Profile : Object {
        public const string SYSTEM = "__shotwell_default_system";
        public Profile(string name, string id, string data_dir, bool active) {
            Object(name: name, id: id, data_dir: data_dir, active: active);
        }
        public string name {get; construct;}
        public string id {get; construct;}
        public string data_dir {get; construct;}
        public bool active {get; construct;}
    }

    class ProfileManager : Object, GLib.ListModel {
        // ListModel implementations
        Type get_item_type() {
            return typeof(Profile);
        }

        uint get_n_items() {
            // All that is in the ini file plus one for the default profile
            return profiles.get_groups().length + 1;
        }

        GLib.Object? get_item (uint position) {
            if (position == 0) {
                return new Profile(_("System Profile"), Profile.SYSTEM,
                            Path.build_path(Path.DIR_SEPARATOR_S, Environment.get_user_data_dir(), "shotwell"),
                            this.profile == null);
            }

            try {
                var group = profiles.get_groups()[position - 1];
                var id = profiles.get_value(group, "Id");
                var name = profiles.get_value(group, "Name");
                var active = this.profile == name;
                return new Profile(profiles.get_value(group, "Name"),
                                id,
                                get_data_dir_for_profile(id, group),
                                active);
            } catch (KeyFileError err) {
                if (err is KeyFileError.GROUP_NOT_FOUND) {
                    assert_not_reached();
                }

                warning("Profile configuration file corrupt: %s", err.message);
            }

            return null;
        }

        private static ProfileManager instance;
        public static ProfileManager get_instance() {
            if (instance == null)
                instance = new ProfileManager();

            return instance;
        }

        private ProfileManager() {
            Object();
        }

        private void write() {
            try {
                profiles.save_to_file(path);
            } catch (Error error) {
                critical("Failed to write profiles: %s", error.message);
            }
        }

        private KeyFile profiles;
        private string profile = null;
        private string path;
        private string group_name;

        public override void constructed() {
            base.constructed();

            profiles = new KeyFile();
            path = Path.build_filename(Environment.get_user_config_dir(), "shotwell");
            DirUtils.create_with_parents(path, 0700);
            path = Path.build_filename(path, "profiles.ini");

            try {
                profiles.load_from_file(path, KeyFileFlags.KEEP_COMMENTS);
            } catch (Error error) {
                debug("Could not read profiles: %s", error.message);
            }
        }

        public bool has_profile (string profile, out string group_name = null) {
            group_name = Base64.encode (profile.data);
            return profiles.has_group(group_name);
        }

        public void set_profile(string profile) {
            message("Using profile %s for this session", profile);
            assert(this.profile == null);

            this.profile = profile;

            add_profile(Uuid.string_random(), profile, null, null);
        }

        public void add_profile(string id, string name, string? library_folder, string? data_folder) {
            if (has_profile(name, out this.group_name)) {
                return;
            }

            try {
                profiles.set_string(group_name, "Name", name);
                profiles.set_string(group_name, "Id", id);
                if (data_folder != null) {
                    profiles.set_string(group_name, "DataDir", data_folder);
                }

                // Need to set comment after setting keys since it does not create the group
                profiles.set_comment(group_name, null, "Profile settings for \"%s\"".printf(name));

                write();
            } catch (Error err) {
                error("Failed to create profile: %s", err.message);                
            }

            if (library_folder != null) {
                var settings_path = "/org/gnome/shotwell/profiles/" + id + "/preferences/files/";
                print ("writing settings at path %s\n", settings_path);

    
                var settings = new Settings.with_path("org.gnome.shotwell.preferences.files", settings_path);
                settings.set_string("import-dir", library_folder);
            }
            
            items_changed(profiles.get_groups().length, 0, 1);
        }

        public string derive_data_dir(string? data_dir) {
            if (data_dir != null) {
                debug ("Using user-provided data dir %s", data_dir);

                try {
                    profiles.get_string(group_name, "DataDir");
                } catch (Error error) {
                    if (profile != null && profile != "") {
                        profiles.set_string(group_name ,"DataDir", data_dir);
                        debug("Using %s as data dir for profile %s", data_dir, profile);
                        write();
                    }
                }

                return data_dir;
            }

            return Path.build_filename(Environment.get_user_data_dir(), "shotwell", "profiles", id());
        }

        public string id() {
            // We are not running on any profile
            if (profile == null || profile == "")
                return "";

            try {
                return profiles.get_string(group_name, "Id");
            } catch (Error error) {
                assert_not_reached();
            }
        }

        private string get_data_dir_for_profile(string id, string group) throws KeyFileError {
            if ("DataDir" in profiles.get_keys(group)) {
                return profiles.get_value(group, "DataDir");
            } else {
                return Path.build_filename(Environment.get_user_data_dir(), "shotwell", "profiles", id);
            }
        }

        public void print_profiles() {
            print("Available profiles:\n");
            print("-------------------\n");
            try {
                foreach (var group in profiles.get_groups()) {
                    print("Profile name: %s\n", profiles.get_value(group, "Name"));
                    var id = profiles.get_value(group, "Id");
                    print("Profile Id: %s\n", id);
                    print("Data dir: %s\n", get_data_dir_for_profile(id, group));
                    print("\n");
                }
            } catch (Error error) {
                print("Failed to print profiles: %s", error.message);
            }
        }

        const string SCHEMAS[] = {
            "sharing",
            "video",
            "printing",
            "plugins.enable-state",
            "preferences.ui",
            "preferences.slideshow",
            "preferences.window",
            "preferences.files",
            "preferences.editing",
            "preferences.export",        
        };

        void reset_all_keys(Settings settings) {
            SettingsSchema schema;
            ((Object)settings).get("settings-schema", out schema, null);
        
            foreach (var key in schema.list_keys()) {
                debug("Resetting key %s", key);
                settings.reset(key);
            }
        
            foreach (var c in settings.list_children()) {
                debug("Checking children %s", c);
                var child = settings.get_child (c);
                reset_all_keys (child);
            }
        }
        
        private void remove_settings_recursively(string id) {
            var source = SettingsSchemaSource.get_default();
            foreach (var schema in SCHEMAS) {
                var path = "/org/gnome/shotwell/profiles/%s/%s/".printf(id, schema.replace(".", "/"));
                var schema_name = "org.gnome.shotwell.%s".printf(schema);
                debug("%s @ %s", schema_name, path);
                var schema_definition = source.lookup(schema_name, false);
                var settings = new Settings.full (schema_definition, null, path);
                settings.delay();
                reset_all_keys (settings);
                foreach (var key in schema_definition.list_keys()) {
                    debug("Resetting key %s", key);
                    settings.reset(key);
                }
                settings.apply();
                Settings.sync();
            }        
        }

        public void remove(string id, bool remove_all) {
            debug("Request to remove profile %s, with files? %s", id, remove_all.to_string());
            int index = 1;
            string group = null;

            foreach (var g in profiles.get_groups()) {
                try {
                    if (profiles.get_value(g, "Id") == id) {
                        group = g;
                        break;
                    }
                    index++;
                } catch (KeyFileError error) {
                    assert_not_reached();
                }
            }

            if (group != null) {
                string? data_dir = null;

                try {
                    data_dir = get_data_dir_for_profile(id, group);
                    // Remove profile
                    string? key = null;
                    profiles.remove_comment(group, key);
                    profiles.remove_group(group);
                } catch (KeyFileError err) {
                    // We checked the existence of the group above.
                    assert_not_reached();
                }

                remove_settings_recursively(id);

                if (remove_all) {
                    try {
                        var file = File.new_for_commandline_arg(data_dir);
                        file.trash();
                    } catch (Error error) {
                        warning("Failed to remove data folder: %s", error.message);
                    }
                }

                Idle.add(() => {
                    items_changed(index, 1, 0);

                    return false;
                });
                write();
            }
        }
    }
}
