/* exported init, cleanup, remove_maximized_background_color, remove_unmaximized_background_color, set_maximized_background_color, set_unmaximized_background_color, remove_background_color, register_text_shadow, add_text_shadow, register_icon_shadow, add_icon_shadow, has_text_shadow, has_icon_shadow, remove_text_shadow, remove_icon_shadow, register_text_color, set_text_color, remove_text_color, set_panel_color, set_corner_color, clear_corner_color, get_background_image_color, get_background_color, get_maximized_opacity, get_unmaximized_opacity, strip_panel_styling, reapply_panel_styling, strip_panel_background_image, reapply_panel_background_image, strip_panel_background, reapply_panel_background, set_background_alpha */

const St = imports.gi.St;

const Main = imports.ui.main;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const Params = imports.misc.params;

const Settings = Me.imports.settings;
const Util = Me.imports.util;

const GdkPixbuf = imports.gi.GdkPixbuf;
const GLib = imports.gi.GLib;

/* Convenience constant for the shell panel. */
const Panel = Main.panel;

/* Constants for theme opacity detection. */
const THEME_OPACITY_THRESHOLD = 50;

/* Constants for color averaging. */
const SATURATION_WEIGHT = 1.5;
const WEIGHT_THRESHOLD = 1.0;
const ALPHA_THRESHOLD = 24;

/* Scale factor for color conversion. */
const SCALE_FACTOR = 255.9999999;

/**
 * @typedef {Object} Color - Represents a standard color object
 * @property {number} red - Red value ranging from 0-255.
 * @property {number} green - Green value ranging from 0-255.
 * @property {number} blue - Blue value ranging from 0-255.
 * @property {number} [alpha=1.0] - Alpha value ranging from 0-1.0 with support for two decimal places.
 */

/**
 * Intialize.
 *
 */
function init() {
    this.stylesheets = [];
    this.styles = [];

    this.background_styles = [];

    update_transition_css();
}

/**
 * Used to release any held assets of theming.
 *
 */
function cleanup() {
    let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();

    for (let style of this.styles) {
        Panel.remove_style_class_name(style);
    }

    for (let style of this.background_styles) {
        Panel.remove_style_class_name(style);
    }

    for (let sheet of this.stylesheets) {
        theme.unload_stylesheet(Util.get_file(sheet));
        Util.remove_file(sheet);
    }

    this.background_styles = null;
    this.stylesheets = null;
    this.styles = null;
}

/**
 * Registers a shadow stylesheet for text in the panel.
 *
 * @param {Color} text_color - Object representing an RGBA color.
 * @param {Number[]} text_position - Integer array containing horizontal offset, vertical offset, radius. (in that order)
 */
function register_text_shadow(text_color, text_position) {
    let text_color_css = 'rgba(' + text_color.red + ', ' + text_color.green + ', ' + text_color.blue + ', ' + text_color.alpha.toFixed(2) + ')';
    let text_position_css = '' + text_position[0] + 'px ' + text_position[1] + 'px ' + text_position[2] + 'px';

    register_style('dpt-panel-text-shadow');

    return apply_stylesheet_css('.dpt-panel-text-shadow .panel-button { text-shadow: ' + text_position_css + ' ' + text_color_css + '; }', 'foreground/panel-text-shadow');
}

/**
 * Adds the currently registered shadow stylesheet to the text in the panel.
 *
 * @param {Color} text_color - Object representing an RGBA color.
 * @param {Number[]} text_position - Integer array containing horizontal offset, vertical offset, radius. (in that order)
 */
function add_text_shadow() {
    Panel.add_style_class_name('dpt-panel-text-shadow');
}

/**
 * Register a shadow stylesheet for icons in the panel.
 *
 * @param {Color} icon_color - Object representing an RGBA color.
 * @param {Number[]} icon_position - Integer array containing horizontal offset, vertical offset, radius. (in that order)
 */
function register_icon_shadow(icon_color, icon_position) {
    let icon_color_css = 'rgba(' + icon_color.red + ', ' + icon_color.green + ', ' + icon_color.blue + ', ' + icon_color.alpha.toFixed(2) + ')';
    let icon_position_css = '' + icon_position[0] + 'px ' + icon_position[1] + 'px ' + icon_position[2] + 'px';

    let stylesheet = apply_stylesheet_css('.dpt-panel-icon-shadow .system-status-icon { icon-shadow: ' + icon_position_css + ' ' + icon_color_css + '; }\n.dpt-panel-arrow-shadow .popup-menu-arrow { icon-shadow: ' + icon_position_css + ' ' + icon_color_css + '; }', 'foreground/panel-icon-shadow');

    register_style('dpt-panel-icon-shadow');
    register_style('dpt-panel-arrow-shadow');

    return stylesheet;
}

/**
 * Adds the currently register shadow stylesheet to icons in the panel.
 *
 */
function add_icon_shadow() {
    Panel.add_style_class_name('dpt-panel-icon-shadow');
    Panel.add_style_class_name('dpt-panel-arrow-shadow');
}

/**
 * Determines if the panel currently has text shadowing applied.
 *
 * @returns {Boolean} If the panel has text shadowing.
 */
function has_text_shadow() {
    return Panel.has_style_class_name('dpt-panel-text-shadow');
}

/**
 * Determines if the panel currently has icon shadowing applied.
 *
 * @returns {Boolean} If the panel has icon shadowing.
 */
function has_icon_shadow() {
    return (Panel.has_style_class_name('dpt-panel-icon-shadow') || Panel.has_style_class_name('dpt-panel-arrow-shadow'));
}

/**
 * Removes any text shadowing; deregistering the stylesheet and removing the css.
 *
 */
function remove_text_shadow() {
    Panel.remove_style_class_name('dpt-panel-text-shadow');
}

/**
 * Removes any icon shadowing; deregistering the stylesheet and removing the css.
 *
 */
function remove_icon_shadow() {
    Panel.remove_style_class_name('dpt-panel-icon-shadow');
    Panel.remove_style_class_name('dpt-panel-arrow-shadow');
}

/**
 * Registers text & icon coloring.
 *
 * @param {Color} color - Object containing an RGB color value.
 * @param {string} prefix - What prefix to apply to the stylesheet. '-' is the default.
 */
function register_text_color(color, prefix) {
    let color_css = 'color: rgb(' + color.red + ', ' + color.green + ', ' + color.blue + ');';

    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    let stylesheet = apply_stylesheet_css('.dpt-panel' + prefix + 'text-color .panel-button { ' + color_css + ' }\n.dpt-panel' + prefix + 'icon-color .system-status-icon { ' + color_css + ' }\n.dpt-panel' + prefix + 'arrow-color .popup-menu-arrow { ' + color_css + ' }', 'foreground/panel' + prefix + 'text-color');

    register_style('dpt-panel' + prefix + 'text-color');
    register_style('dpt-panel' + prefix + 'icon-color');
    register_style('dpt-panel' + prefix + 'arrow-color');

    return stylesheet;
}

/**
 * Sets which registered text color stylesheet to use for the text coloring. @see register_text_color
 *
 * @param {string} prefix - What stylesheet prefix to retrieve. '-' is the default.
 */
function set_text_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    Panel.add_style_class_name('dpt-panel' + prefix + 'text-color');
    Panel.add_style_class_name('dpt-panel' + prefix + 'icon-color');
    Panel.add_style_class_name('dpt-panel' + prefix + 'arrow-color');
}

/**
 * Remove a registered text color stylesheet from the panel. @see set_text_color
 *
 * @param {string} prefix - What stylesheet prefix to retrieve. '-' is the default.
 */
function remove_text_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    Panel.remove_style_class_name('dpt-panel' + prefix + 'text-color');
    Panel.remove_style_class_name('dpt-panel' + prefix + 'icon-color');
    Panel.remove_style_class_name('dpt-panel' + prefix + 'arrow-color');
}

/**
 * Registers any custom style so that it can be removed when the extension is disabled.
 *
 * @param {string} style - The name of a CSS styling.
 */
function register_style(style) {
    if (this.styles.indexOf(style) === -1) {
        this.styles.push(style);
    }
}

/**
 * Set's the panel corners' actors to a specific background color.
 *
 * @param {Color} color [color={}] - Object containing an RGBA color value.
 */
// TODO: Gnome needs CSS styling for the corners.
function set_corner_color(color) {
    let panel_color = get_background_color();

    color = Params.parse(color, {
        red: panel_color.red,
        green: panel_color.green,
        blue: panel_color.blue,
        alpha: 0
    });

    let opacity = Util.clamp(color.alpha / SCALE_FACTOR, 0, 1).toFixed(2);

    /* I strongly dislike using a deprecated method (set_style)
     * but this is a hold over from the older extension code and
     * the only way to keep per-app coloring working with corners. */
    let coloring = '-panel-corner-background-color: rgba(' + color.red + ', ' + color.green + ', ' + color.blue + ', ' + opacity + ');' +
        '' + '-panel-corner-border-color: transparent;';

    // TODO: Update this code. We're using @deprecated code.
    Panel._leftCorner.set_style(coloring);
    Panel._rightCorner.set_style(coloring);
}

/**
 * Removes any corner styling this extension has applied.
 *
 */
function clear_corner_color() {
    Panel._leftCorner.set_style(null);
    Panel._rightCorner.set_style(null);
}

/**
 * Returns the user's desired panel color from Settings. Handles theme detection again.
 * DEPENDENCY: Settings
 *
 * @returns {Object} Object containing an RGBA color value.
 */
function get_background_color() {
    if (!Settings.enable_custom_background_color()) {
        return {
            red: 0,
            blue: 0,
            green: 0
        };
    }

    return Settings.get_panel_color();
}

/**
 * Returns the user's desired maximized panel opacity from Settings or their theme.
 * DEPENDENCY: Settings
 * TODO: Needs better system to determine when default theme opacities are too low.
 *
 * @returns {Number} Alpha value from 0-255.
 */
function get_maximized_opacity() {
    let maximized_opacity = Settings.get_maximized_opacity();

    if (!Settings.enable_custom_opacity()) {
        return 255;
    } else {
        return maximized_opacity;
    }
}

/**
 * Returns the user's desired unmaximized panel opacity from Settings or their theme.
 * DEPENDENCY: Settings
 *
 * @returns {Number} Alpha value from 0-255.
 */
function get_unmaximized_opacity() {
    if (Settings.enable_custom_opacity()) {
        return Settings.get_unmaximized_opacity();
    } else {
        return 50;
    }
}

/**
 * Applies the style class 'panel-transparency' which makes the panel fully transparent.
 *
 */
function apply_panel_transparency() {
    Panel.add_style_class_name('panel-transparency');
}

/**
 * Applies the style class 'panel-transparency' which makes the panel fully transparent.
 *
 */
function remove_panel_transparency() {
    Panel.remove_style_class_name('panel-transparency');
}

/**
 * Applies the style class 'panel-effect-transparency' and removes the basic CSS preventing this extension's transitions.
 *
 */
function strip_panel_styling() {
    Panel.add_style_class_name('panel-effect-transparency');
}

/**
 * Removes the style class 'panel-effect-transparency' and enables the stock CSS preventing this extension's transitions.
 *
 */
function reapply_panel_styling() {
    Panel.remove_style_class_name('panel-effect-transparency');
}

/**
 * Applies the style class 'panel-background-image-transparency' and removes the basic CSS preventing this extension's transitions.
 *
 */
function strip_panel_background_image() {
    Panel.add_style_class_name('panel-background-image-transparency');
}

/**
 * Removes the style class 'panel-background-image-transparency' and enables the stock CSS preventing this extension's transitions.
 *
 */
function reapply_panel_background_image() {
    Panel.remove_style_class_name('panel-background-image-transparency');
}

/**
 * Writes CSS data to a file and loads the stylesheet into the Shell.
 *
 * @param {string} css - CSS data.
 * @param {string} name - Name of the intended CSS stylesheet.
 *
 * @returns {string} Filename of the stylesheet.
 */
function apply_stylesheet_css(css, name) {
    let file_name = GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell', 'extensions', Me.uuid, 'styles', name + '.dpt.css']);

    /* Write to the file. */
    if (!Util.write_to_file(file_name, css)) {
        log('[Dynamic Panel Transparency] Could not access: ' + file_name + '');
        log('[Dynamic Panel Transparency] The extension will not function until access is granted.');
        return null;
    }

    let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();

    if (theme.load_stylesheet(Util.get_file(file_name))) {
        this.stylesheets.push(file_name);
    } else {
        log('[Dynamic Panel Transparency] Error Loading Temporary Stylesheet: ' + name);
        return null;
    }

    return file_name;
}

/* Backend24 (3.24+) Specific Functions (Not backwards compatible) */

function initialize_background_styles() {
    register_background_color(Settings.get_panel_color(), 'custom');
    register_background_color({
        red: 0,
        green: 0,
        blue: 0
    });
}

function cleanup_background_styles() {
    remove_background_color();
}

function register_background_style(style) {
    if (this.background_styles.indexOf(style) === -1) {
        this.background_styles.push(style);
    }
}

function register_background_color(bg_color, prefix) {
    let suffix = (prefix ? '-' + prefix : '');

    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    let maximized_opacity = Util.clamp(get_maximized_opacity() / SCALE_FACTOR, 0, 1).toFixed(2);
    let unmaximized_opacity = Util.clamp(get_unmaximized_opacity() / SCALE_FACTOR, 0, 1).toFixed(2);

    let maximized_bg_color_css = 'rgba(' + bg_color.red + ', ' + bg_color.green + ', ' + bg_color.blue + ', ' + maximized_opacity + ')';
    let unmaximized_bg_color_css = 'rgba(' + bg_color.red + ', ' + bg_color.green + ', ' + bg_color.blue + ', ' + unmaximized_opacity + ')';

    register_background_style('dpt-panel' + prefix + 'maximized');
    register_background_style('dpt-panel' + prefix + 'unmaximized');

    let file_prefix = 'background/panel';

    let panel = apply_stylesheet_css('.dpt-panel' + prefix + 'unmaximized { background-color: ' + unmaximized_bg_color_css + '; }\n.dpt-panel' + prefix + 'maximized { background-color: ' + maximized_bg_color_css + '; }', file_prefix + suffix);

    return panel;
}

function set_unmaximized_background_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    let style = 'dpt-panel' + prefix + 'unmaximized';

    Panel.add_style_class_name(style);
}

function set_maximized_background_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    let style = 'dpt-panel' + prefix + 'maximized';

    Panel.add_style_class_name(style);
}

function remove_unmaximized_background_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    Panel.remove_style_class_name('dpt-panel' + prefix + 'unmaximized');
}

function remove_maximized_background_color(prefix) {
    if (prefix) {
        prefix = '-' + prefix + '-';
    } else {
        prefix = '-';
    }

    Panel.remove_style_class_name('dpt-panel' + prefix + 'maximized');
}

function remove_background_color() {
    remove_unmaximized_background_color('custom');
    remove_unmaximized_background_color();

    remove_maximized_background_color('custom');
    remove_maximized_background_color();
}

function update_transition_css() {
    let duration_css = Settings.get_transition_speed();

    let stylesheet = apply_stylesheet_css('.dpt-panel-transition-duration { transition-duration: ' + duration_css + 'ms; }', 'transitions/panel-transition-duration');

    Panel.add_style_class_name('dpt-panel-transition-duration');

    register_style('dpt-panel-transition-duration');

    return stylesheet;
}
