/* exported init, cleanup, asyncCheck, syncCheck, forceAsyncCheck, forceSyncCheck, get_current_maximized_window */

const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;

const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;

const Main = imports.ui.main;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const Settings = Me.imports.settings;
const Util = Me.imports.util;

const Transitions = Me.imports.transitions;
const Theming = Me.imports.theming;

/* Determines whether to continue the async loop checks. */
let continueCheck = false;
/* Variable for current detected maximized window... */
let maximized_window = null;
/* Run the next change regardless of the whether it is the same as the current status. */
// TODO: Find a nicer way to override optimization code.
let override_optimization = false;
/* Current ID of the async loop (0 if no loop is running) */
let timeoutId = 0;

/* How often the asynchronous loop should run in milliseconds... */
const ASYNC_UPDATE_FREQUENCY = 200; // ms

function init() {
    this._wm_tracker = Shell.WindowTracker.get_default();

    _updateBounds();
}

function cleanup() {
    this._wm_tracker = null;

    if (timeoutId > 0) {
        Mainloop.source_remove(timeoutId);
    }
}

function forceSyncCheck() {
    override_optimization = true;
    syncCheck();
}

function syncCheck() {
    /* Prevent any asynchronous checks from occuring in the loop. */
    continueCheck = false;
    /* Stop the asynchronous loop... */
    if (timeoutId > 0)
        Mainloop.source_remove(timeoutId);
    /* Remove the old loop ID */
    timeoutId = 0;
    /* Update bounds when a check is done in sync. */
    _updateBounds();
    /* Run a check. */
    _check();
}

function forceAsyncCheck() {
    override_optimization = true;
    asyncCheck();
}

function asyncCheck() {
    if (timeoutId <= 0) {
        _check();

        timeoutId = Mainloop.timeout_add(ASYNC_UPDATE_FREQUENCY, (function() {
            _check();

            if (continueCheck) {
                continueCheck = false;
                return GLib.SOURCE_CONTINUE;
            } else {
                timeoutId = 0;
                return GLib.SOURCE_REMOVE;
            }
        }).bind(this));
    } else {
        continueCheck = true;
    }

}


function _updateBounds() {
    const panel = Main.panel;

    this.panel_bounds = {
        x: panel.get_x(),
        y: panel.get_y(),
        height: panel.get_height(),
        width: panel.get_width(),
        is_top: true
    };

    this.scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;

    let [, pivot_y] = Main.layoutManager.panelBox.get_pivot_point();

    // Adjust for bottom panel.
    if (pivot_y < 0) {
        this.panel_bounds.y = -pivot_y;
        this.panel_bounds.is_top = false;
    }
}

/* Main extension logic. Modified to fit Gnome Shell 3.26 design patterns. */

// TODO: Cleanup use of variable flags.
function _check() {
    if (Main.overview._shown) {
        return;
    }

    let workspace = null;

    const manager = global.screen || global.workspace_manager;

    if (manager) {
        workspace = manager.get_active_workspace();
    } else {
        log('[Dynamic Panel Transparency] Error could not get active workspace.');
    }

    let windows = workspace.list_windows();
    windows = global.display.sort_windows_by_stacking(windows);

    let focused_window = global.display.get_focus_window();

    maximized_window = null;

    let add_transparency = true;
    let force_transparency = false;

    /* Handle desktop icons (they're a window too) */
    if (focused_window && focused_window.get_window_type() === Meta.WindowType.DESKTOP) {
        add_transparency = true;
        maximized_window = focused_window;
    } else {
        // TODO: Always negative? Is pivot negative?
        for (let i = windows.length - 1; i >= 0; i--) {

            let current_window = windows[i];

            if (!current_window.showing_on_its_workspace() || !current_window.is_on_primary_monitor()) {
                continue;
            }

            /* Make sure the window is on the correct monitor, isn't minimized, isn't supposed to be excluded, and is actually maximized. */
            if (!Util.is_valid(current_window)) {
                continue;
            }

            if (current_window.maximized_vertically) {
                /* Make sure the top-most window is selected */
                if (maximized_window === null && !force_transparency) {
                    maximized_window = current_window;
                }

                add_transparency = false;

                break;
            }

            let frame = current_window.get_frame_rect();

            if (Main.layoutManager._rightPanelBarrier) {
                let overlap = this.panel_bounds.x < frame.x + frame.width &&
                    this.panel_bounds.x + this.panel_bounds.width > frame.x &&
                    this.panel_bounds.y < frame.y + frame.height &&
                    this.panel_bounds.height + this.panel_bounds.y > frame.y;

                if (overlap) {
                    force_transparency = true;
                    maximized_window = null;

                    break;
                }
            }

            if (Settings.transition_when_windows_touch_panel()) {
                let touching_panel = false;

                if (this.panel_bounds.is_top) {
                    touching_panel = frame.y >= (this.panel_bounds.y + this.panel_bounds.height) &&
                        frame.y <= (this.panel_bounds.y + this.panel_bounds.height);
                } else {
                    touching_panel = (frame.y + frame.height) >= (this.panel_bounds.y) &&
                        (frame.y + frame.height) <= (this.panel_bounds.y);
                }

                if (!force_transparency && touching_panel) {
                    add_transparency = false;

                    if (maximized_window === null && !force_transparency) {
                        maximized_window = current_window;
                    }

                    break;
                }
            }
        }
    }

    if (force_transparency) {
        Transitions.fade_out();
        force_transparency = false;
        /* Only change if the transparency isn't already correct or if override_optimization has been called */
    } else if (Transitions.get_transparency_status().is_blank()) {
        if (add_transparency) {
            Transitions.minimum_fade_in();
        } else {
            Transitions.fade_in();
        }
    } else if (override_optimization || (Transitions.get_transparency_status().is_transparent() !== add_transparency)) {
        override_optimization = false;

        if (add_transparency) {
            Transitions.fade_out();
        } else {
            Transitions.fade_in();
        }
    }

    /* Reset text coloring. */
    if (Settings.get_enable_text_color() && (Settings.get_enable_maximized_text_color() || Settings.get_enable_overview_text_color())) {
        if (!add_transparency && Settings.get_enable_maximized_text_color()) {
            Theming.remove_text_color();
            Theming.set_text_color('maximized');
        } else {
            Theming.remove_text_color('maximized');
            Theming.set_text_color();
        }
    }
}

/**
 * Returns the current visible maximized window as understood by the events' logic.
 * The maximized window is not necessarily the highest window in the z-order.
 *
 * @returns {Object} The current visible maximized window.
 */
function get_current_maximized_window() {
    return maximized_window;
}