import GLib from 'gi://GLib';

const Signals = imports.signals;

/// An enum non-extensively describing the type of gsettings key.
export const Type = {
    B: 'Boolean',
    I: 'Integer',
    D: 'Double',
    S: 'String',
    C: 'Color',
    AS: 'StringArray'
};

/// An object to get and manage the gsettings preferences.
///
/// Should be initialized with an array of keys, for example:
///
/// let settings = new Settings([
///     { type: Type.I, name: "panel-corner-radius" },
///     { type: Type.B, name: "debug" }
/// ]);
///
/// Each {type, name} object represents a gsettings key, which must be created
/// in the gschemas.xml file of the extension.
export const Settings = class Settings {
    constructor(keys, settings) {
        this.settings = settings;
        this.keys = keys;

        this.keys.forEach(bundle => {
            let component = this;
            let component_settings = settings;
            if (bundle.component !== "general") {
                let bundle_component = bundle.component.replaceAll('-', '_');
                this[bundle_component] = {
                    settings: this.settings.get_child(bundle.component)
                };
                component = this[bundle_component];
                component_settings = settings.get_child(bundle.component);
            }


            bundle.schemas.forEach(key => {
                let property_name = this.get_property_name(key.name);

                switch (key.type) {
                    case Type.B:
                        Object.defineProperty(component, property_name, {
                            get() {
                                return component_settings.get_boolean(key.name);
                            },
                            set(v) {
                                component_settings.set_boolean(key.name, v);
                            }
                        });
                        break;

                    case Type.I:
                        Object.defineProperty(component, property_name, {
                            get() {
                                return component_settings.get_int(key.name);
                            },
                            set(v) {
                                component_settings.set_int(key.name, v);
                            }
                        });
                        break;

                    case Type.D:
                        Object.defineProperty(component, property_name, {
                            get() {
                                return component_settings.get_double(key.name);
                            },
                            set(v) {
                                component_settings.set_double(key.name, v);
                            }
                        });
                        break;

                    case Type.S:
                        Object.defineProperty(component, property_name, {
                            get() {
                                return component_settings.get_string(key.name);
                            },
                            set(v) {
                                component_settings.set_string(key.name, v);
                            }
                        });
                        break;

                    case Type.C:
                        Object.defineProperty(component, property_name, {
                            // returns the array [red, blue, green, alpha] with
                            // values between 0 and 1
                            get() {
                                let val = component_settings.get_value(key.name);
                                return val.deep_unpack();
                            },
                            // takes an array [red, blue, green, alpha] with
                            // values between 0 and 1
                            set(v) {
                                let val = new GLib.Variant("(dddd)", v);
                                component_settings.set_value(key.name, val);
                            }
                        });
                        break;

                    case Type.AS:
                        Object.defineProperty(component, property_name, {
                            get() {
                                let val = component_settings.get_value(key.name);
                                return val.deep_unpack();
                            },
                            set(v) {
                                let val = new GLib.Variant("as", v);
                                component_settings.set_value(key.name, val);
                            }
                        });
                        break;
                }

                component[property_name + '_reset'] = function () {
                    return component_settings.reset(key.name);
                };

                component[property_name + '_changed'] = function (cb) {
                    return component_settings.connect('changed::' + key.name, cb);
                };

                component[property_name + '_disconnect'] = function () {
                    return component_settings.disconnect.apply(
                        component_settings, arguments
                    );
                };
            });
        });
    };

    /// Reset the preferences.
    reset() {
        this.keys.forEach(bundle => {
            let component = this;
            if (bundle.component !== "general") {
                let bundle_component = bundle.component.replaceAll('-', '_');
                component = this[bundle_component];
            }

            bundle.schemas.forEach(key => {
                let property_name = this.get_property_name(key.name);
                component[property_name + '_reset']();
            });
        });

        this.emit('reset', true);
    }

    /// From the gschema name, returns the name of the associated property on
    /// the Settings object.
    get_property_name(name) {
        return name.replaceAll('-', '_').toUpperCase();
    }

    /// Remove all connections managed by the Settings object, i.e. created with
    /// `settings.PROPERTY_changed(callback)`.
    disconnect_all_settings() {
        this.keys.forEach(bundle => {
            let component = this;
            if (bundle.component !== "general") {
                let bundle_component = bundle.component.replaceAll('-', '_');
                component = this[bundle_component];
            }

            bundle.schemas.forEach(key => {
                let property_name = this.get_property_name(key.name);
                component[property_name + '_disconnect']();
            });
        });
    }
};

Signals.addSignalMethods(Settings.prototype);