/**
 * Vertical Workspaces
 * workspacesView.js
 * 
 * @author     GdH <G-dH@github.com>
 * @copyright  2022 - 2023
 * @license    GPL-3.0
 *
 */

'use strict';

const { GObject, Clutter, Meta, St } = imports.gi;

const Main = imports.ui.main;
const Util = imports.misc.util;
const WorkspacesView = imports.ui.workspacesView;
//const SecondaryMonitorDisplay = WorkspacesView.SecondaryMonitorDisplay;
// first call of item defined using const in other module returns undefined
WorkspacesView.SecondaryMonitorDisplay;
const ControlsState = imports.ui.overviewControls.ControlsState;
const FitMode = imports.ui.workspacesView.FitMode;

const SIDE_CONTROLS_ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const SEARCH_WINDOWS_PREFIX = Me.imports.windowSearchProvider.prefix;
const SEARCH_RECENT_FILES_PREFIX = Me.imports.recentFilesSearchProvider.prefix;

const _Util = Me.imports.util;
let _overrides;

let opt;



function update(reset = false) {
    if (_overrides) {
        _overrides.removeAll();
        global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1);
    }

    if (reset) {
        _overrides = null;
        opt = null;
        return;
    }

    opt = Me.imports.settings.opt;

    _overrides = new _Util.Overrides();

    _overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon);
    _overrides.addOverride('WorkspacesDisplay', WorkspacesView.WorkspacesDisplay.prototype, WorkspacesDisplay);

    if (opt.ORIENTATION) {
        // switch internal workspace orientation in GS
        global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, -1, 1);
        _overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayVertical);
    } else {
        _overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayHorizontal);
    }
}

var WorkspacesViewCommon = {
    _getFirstFitSingleWorkspaceBox: function(box, spacing, vertical) {
        let [width, height] = box.get_size();
        const [workspace] = this._workspaces;

        const rtl = this.text_direction === Clutter.TextDirection.RTL;
        const adj = this._scrollAdjustment;
        const currentWorkspace = vertical || !rtl
            ? adj.value : adj.upper - adj.value - 1;

        // Single fit mode implies centered too
        let [x1, y1] = box.get_origin();
        const [, workspaceWidth] = workspace ? workspace.get_preferred_width(Math.floor(height)) : [,width];
        const [, workspaceHeight] = workspace ? workspace.get_preferred_height(workspaceWidth) : [,height];

        if (vertical) {
            x1 += (width - workspaceWidth) / 2;
            y1 -= currentWorkspace * (workspaceHeight + spacing);
        } else {
            x1 += (width - workspaceWidth) / 2;
            x1 -= currentWorkspace * (workspaceWidth + spacing);
        }

        const fitSingleBox = new Clutter.ActorBox({x1, y1});

        fitSingleBox.set_size(workspaceWidth, workspaceHeight);

        return fitSingleBox;
    },

    // set spacing between ws previews
    _getSpacing: function(box, fitMode, vertical) {
        const [width, height] = box.get_size();
        const [workspace] = this._workspaces;

        if (!workspace) return;

        let availableSpace;
        let workspaceSize;
        if (vertical) {
            [, workspaceSize] = workspace.get_preferred_height(width);
            availableSpace = height;
        } else {
            [, workspaceSize] = workspace.get_preferred_width(height);
            availableSpace = width;
        }

        const spacing = (availableSpace - workspaceSize * 0.4) * (1 - fitMode);
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        return Math.clamp(spacing,
            opt.WORKSPACE_MIN_SPACING * scaleFactor,
            opt.WORKSPACE_MAX_SPACING * scaleFactor);
    },

    // this function has duplicate in OverviewControls so we use one function for both to avoid issues with syncing them
    _getFitModeForState: function(state) {
        return _getFitModeForState(state);
    },

    // normal view 0, spread windows 1
    _getWorkspaceModeForOverviewState: function(state) {

        switch (state) {
        case ControlsState.HIDDEN:
            return 0;
        case ControlsState.WINDOW_PICKER:
            return opt.WORKSPACE_MODE;
        case ControlsState.APP_GRID:
            return ((this._monitorIndex !== global.display.get_primary_monitor() || !opt.WS_ANIMATION) && !opt.OVERVIEW_MODE) ? 1 : 0;
        }

        return 0;
    },

    _updateVisibility: function() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();

        const fitMode = this._fitModeAdjustment.value;
        const singleFitMode = fitMode === FitMode.SINGLE;

        for (let w = 0; w < this._workspaces.length; w++) {
            let workspace = this._workspaces[w];

            if (this._animating || this._gestureActive || !singleFitMode) {
                //workspace.show();
            } else {
                workspace.visible = Math.abs(w - active) <= opt.NUMBER_OF_VISIBLE_NEIGHBORS;
            }

        }
    },

    // disable scaling and hide inactive workspaces
    _updateWorkspacesState: function() {
        const adj = this._scrollAdjustment;
        const fitMode = this._fitModeAdjustment.value;

        let { initialState, finalState, progress, currentState } =
            this._overviewAdjustment.getStateTransitionParams();

        const workspaceMode = (1 - fitMode) * Util.lerp(
            this._getWorkspaceModeForOverviewState(initialState),
            this._getWorkspaceModeForOverviewState(finalState),
            progress);


        const currentMonitor = Main.layoutManager.primaryMonitor.index;

        // define the transition values here to save time in each ws
        let scaleX, scaleY;
        if (opt.ORIENTATION) { //vertical 1 / horizontal 0
            scaleX = 1;
            scaleY = 0.1;
        } else {
            scaleX = 0.1;
            scaleY = 1;
        }

        const secondaryMonitor = this._monitorIndex !== global.display.get_primary_monitor();
        const blockSecondaryAppGrid = opt.OVERVIEW_MODE && currentState >= 1;
        // Hide inactive workspaces
        this._workspaces.forEach((w, index) => {
            if (!(blockSecondaryAppGrid && secondaryMonitor))
                w.stateAdjustment.value = workspaceMode;
            //w.stateAdjustment.value = workspaceMode;

            const distanceToCurrentWorkspace = Math.abs(adj.value - index);

            const scaleProgress = 1 - Math.clamp(distanceToCurrentWorkspace, 0, 1);

            // if we disable workspaces that we can't or don't need to see, transition animations will be noticeably smoother

            // only the current ws needs to be visible during overview transition animations
            //                        and only current and adjacent ws when switching ws
            if (opt.WORKSPACE_MAX_SPACING > 340) { // large spacing - only one workspace needs to be visible at once in the overview
                w.visible = scaleProgress || ((currentState % 1) && !distanceToCurrentWorkspace);

            // horizontal orientation - 2 adjacent workspaces can be visible on the screen with the current one
            // in order to keep animations as smooth as possible, hide all ws that cannot/shouldn't be visible at the given time
            } else {
                //
                w.visible = w.monitorIndex !== currentMonitor || scaleProgress || (!opt.WS_ANIMATION && distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS)
                    || (distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState <= ControlsState.WINDOW_PICKER
                        && ((initialState < ControlsState.APP_GRID && finalState < ControlsState.APP_GRID))
                );

                // after transition from APP_GRID to WINDOW_PICKER state,
                // adjacent workspaces are hidden and we need them to show up
                // make them visible during animation can impact smoothness of the animation
                // so we show them after the animation finished, scaling animation will make impression that they move in from outside the monitor
                if (!w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) {
                    w.scale_x = scaleX;
                    w.scale_y = scaleY;
                    w.visible = true;
                    w.ease({
                        duration: 100,
                        scale_x: 1,
                        scale_y: 1,
                        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    });
                }
            }

            // force ws preview bg corner radiuses where GS doesn't do it
            if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1 && distanceToCurrentWorkspace < 2) {
                w._background._updateBorderRadius(Math.min(1, w._overviewAdjustment.value));
            }

            // hide workspace background
            if (!opt.SHOW_WS_PREVIEW_BG && w._background.opacity) {
                w._background.opacity = 0;
            }
        });
    }
}

//  SecondaryMonitorDisplay Vertical
var SecondaryMonitorDisplayVertical = {
    _getThumbnailParamsForState: function(state) {

        let opacity, scale, translation_x;
        switch (state) {
        case ControlsState.HIDDEN:
            opacity = 255;
            scale = 1;
            translation_x = 0;
            if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) {
                translation_x = this._thumbnails.width * (opt.SEC_WS_TMB_LEFT ? -1 : 1);
            }
            break;
        case ControlsState.WINDOW_PICKER:
        case ControlsState.APP_GRID:
            opacity = 255;
            scale = 1;
            translation_x = 0;
            break;
        default:
            opacity = 255;
            scale = 1;
            translation_x = 0;
            break;
        }

        return { opacity, scale, translation_x };
    },

    _getThumbnailsWidth: function(box, spacing) {
        if (!this._thumbnails.visible)
            return 0;

        const [width, height] = box.get_size();
        const { expandFraction } = this._thumbnails;
        const [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(height - 2 * spacing);
        return Math.min(
            thumbnailsWidth * expandFraction,
            width * opt.MAX_THUMBNAIL_SCALE);
    },

    _getWorkspacesBoxForState: function(state, box, padding, thumbnailsWidth, spacing) {
        //const { ControlsState } = OverviewControls;
        const workspaceBox = box.copy();
        const [width, height] = workspaceBox.get_size();

        switch (state) {
        case ControlsState.HIDDEN:
            break;
        case ControlsState.WINDOW_PICKER:
        case ControlsState.APP_GRID:
            if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) {
                break;
            }

            let wWidth = Math.round(width - thumbnailsWidth - 5 * spacing);
            let wHeight = Math.round(Math.min(wWidth / (width / height), height - 1.7 * padding));
            wWidth *= opt.WS_PREVIEW_SCALE;
            wHeight *= opt.WS_PREVIEW_SCALE;

            let wsbX;
            let offset = Math.round(width - thumbnailsWidth - wWidth) / 2;
            if (opt.SEC_WS_TMB_LEFT) {
                wsbX = thumbnailsWidth + offset;
            } else {
                wsbX = offset;
            }

            const wsbY = Math.round((height - wHeight) / 2);

            workspaceBox.set_origin(wsbX, wsbY);
            workspaceBox.set_size(wWidth, wHeight);
            break;
        }

        return workspaceBox;
    },

    vfunc_allocate: function(box) {
        this.set_allocation(box);

        const themeNode = this.get_theme_node();
        const contentBox = themeNode.get_content_box(box);
        const [width, height] = contentBox.get_size();
        const { expandFraction } = this._thumbnails;
        const spacing = themeNode.get_length('spacing') * expandFraction;
        const padding = Math.round(0.1 * height);

        let thumbnailsWidth = this._getThumbnailsWidth(contentBox, spacing);
        let [, thumbnailsHeight] = this._thumbnails.get_preferred_custom_height(thumbnailsWidth);
        thumbnailsHeight = Math.min(thumbnailsHeight, height - 2 * spacing);

        this._thumbnails.visible = opt.SHOW_WS_TMB;
        if (this._thumbnails.visible) {
            let wsTmbX;
            if (opt.SEC_WS_TMB_LEFT) { // left
                wsTmbX = Math.round(spacing / 4);
                this._thumbnails._positionLeft = true;
            } else {
                wsTmbX = Math.round(width - spacing / 4 - thumbnailsWidth);
                this._thumbnails._positionLeft = false;
            }

            const childBox = new Clutter.ActorBox();
            const availSpace = height - thumbnailsHeight - 2 * spacing;

            let wsTmbY =  availSpace / 2;
            wsTmbY -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbY - spacing;

            childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY));
            childBox.set_size(thumbnailsWidth, thumbnailsHeight);
            this._thumbnails.allocate(childBox);
        }

        const {
            currentState, initialState, finalState, transitioning, progress,
        } = this._overviewAdjustment.getStateTransitionParams();

        let workspacesBox;
        const workspaceParams = [contentBox, padding, thumbnailsWidth, spacing];
        if (!transitioning) {
            workspacesBox =
                this._getWorkspacesBoxForState(currentState, ...workspaceParams);
        } else {
            const initialBox =
                this._getWorkspacesBoxForState(initialState, ...workspaceParams);
            const finalBox =
                this._getWorkspacesBoxForState(finalState, ...workspaceParams);
            workspacesBox = initialBox.interpolate(finalBox, progress);
        }
        this._workspacesView.allocate(workspacesBox);
    },

    _updateThumbnailVisibility: function() {
        if (opt.OVERVIEW_MODE2) {
            this.set_child_above_sibling(this._thumbnails, null);
        }

        const visible = !(this._settings.get_boolean('workspaces-only-on-primary') ||
                            opt.SEC_WS_TMB_POSITION === 3); // 3 - disabled

        if (this._thumbnails.visible === visible)
            return;

        this._thumbnails.show();
        this._updateThumbnailParams();
        this._thumbnails.ease_property('expand-fraction', visible ? 1 : 0, {
            duration: SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._thumbnails.visible = visible;
                this._thumbnails._indicator.visible = visible;
            },
        });
    },

    _updateThumbnailParams: function() {
        // workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails
        // the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid
        if (!this._thumbnails._thumbnails.length) {
            this._thumbnails._createThumbnails();
        }

        if (!this._thumbnails.visible)
            return;

        const { initialState, finalState, progress } =
            this._overviewAdjustment.getStateTransitionParams();

        const initialParams = this._getThumbnailParamsForState(initialState);
        const finalParams = this._getThumbnailParamsForState(finalState);

        /*const opacity =
            Util.lerp(initialParams.opacity, finalParams.opacity, progress);
        const scale =
            Util.lerp(initialParams.scale, finalParams.scale, progress);*/

        // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread)
        const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE);
        const translation_x = (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !(opt.OVERVIEW_MODE2)) || animateOverviewMode2))
            ? Util.lerp(initialParams.translation_x, finalParams.translation_x, progress)
            : 0;

        this._thumbnails.set({
            opacity: 255,
            //scale_x: scale,
            //scale_y: scale,
            translation_x,
        });
    },

    _updateWorkspacesView: function() {
        if (this._workspacesView)
            this._workspacesView.destroy();

        if (this._settings.get_boolean('workspaces-only-on-primary')) {
            this._workspacesView = new WorkspacesView.ExtraWorkspaceView(
                this._monitorIndex,
                this._overviewAdjustment);
        } else {
            this._workspacesView = new WorkspacesView.WorkspacesView(
                this._monitorIndex,
                this._controls,
                this._scrollAdjustment,
                // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
                //this._fitModeAdjustment,
                new St.Adjustment({
                    actor: this,
                    value: 0,//FitMode.SINGLE,
                    lower: 0,//FitMode.SINGLE,
                    upper: 0,//FitMode.SINGLE,
                }),
                //secondaryOverviewAdjustment);
                this._overviewAdjustment);
        }
        this.add_child(this._workspacesView);
        this._thumbnails.opacity = 0;
    }
}

//  SecondaryMonitorDisplay Horizontal
var SecondaryMonitorDisplayHorizontal = {
    _getThumbnailParamsForState: function(state) {
        //const { ControlsState } = OverviewControls;

        let opacity, scale, translation_y;
        switch (state) {
        case ControlsState.HIDDEN:
            opacity = 255;
            scale = 1;
            translation_y = 0;
            if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)) {
                translation_y = this._thumbnails.height * (opt.SEC_WS_TMB_TOP ? -1 : 1);
            }
            break;
        case ControlsState.WINDOW_PICKER:
        case ControlsState.APP_GRID:
            opacity = 255;
            scale = 1;
            translation_y = 0;
            break;
        default:
            opacity = 255;
            scale = 1;
            translation_y = 0;
            break;
        }

        return { opacity, scale, translation_y };
    },

    _getWorkspacesBoxForState: function(state, box, padding, thumbnailsHeight, spacing) {
        //const { ControlsState } = OverviewControls;
        const workspaceBox = box.copy();
        const [width, height] = workspaceBox.get_size();

        switch (state) {
        case ControlsState.HIDDEN:
            break;
        case ControlsState.WINDOW_PICKER:
        case ControlsState.APP_GRID:
            if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) {
                break;
            }

            let wHeight = Math.round(Math.min(height - thumbnailsHeight - 5 * spacing));
            let wWidth = Math.round(Math.min(wHeight * (width / height), width - 1.7 * padding));
            wWidth *= opt.WS_PREVIEW_SCALE;
            wHeight *= opt.WS_PREVIEW_SCALE;

            let wsbY;
            let offset = Math.round((height - thumbnailsHeight - wHeight) / 2);
            if (opt.WS_TMB_TOP) {
                wsbY = thumbnailsHeight + offset;
            } else {
                wsbY = offset;
            }

            const wsbX = Math.round((width - wWidth) / 2);

            workspaceBox.set_origin(wsbX, wsbY);
            workspaceBox.set_size(wWidth, wHeight);
            break;
        }

        return workspaceBox;
    },

    _getThumbnailsHeight: function(box) {
        if (!this._thumbnails.visible)
            return 0;

        const [width, height] = box.get_size();
        const { expandFraction } = this._thumbnails;
        const [thumbnailsHeight] = this._thumbnails.get_preferred_height(width);
        return Math.min(
            thumbnailsHeight * expandFraction,
            height * opt.MAX_THUMBNAIL_SCALE);
    },

    vfunc_allocate: function(box) {
        this.set_allocation(box);

        const themeNode = this.get_theme_node();
        const contentBox = themeNode.get_content_box(box);
        const [width, height] = contentBox.get_size();
        const { expandFraction } = this._thumbnails;
        const spacing = themeNode.get_length('spacing') * expandFraction;
        const padding = Math.round(0.1 * height);

        let thumbnailsHeight = this._getThumbnailsHeight(contentBox);
        let [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(thumbnailsHeight);
        thumbnailsWidth = Math.min(thumbnailsWidth, width - 2 * spacing);

        this._thumbnails.visible = opt.SHOW_WS_TMB;
        if (this._thumbnails.visible) {
            let wsTmbY;
            if (opt.SEC_WS_TMB_TOP) {
                wsTmbY = Math.round(spacing / 4);
            } else {
                wsTmbY = Math.round(height - spacing / 4 - thumbnailsHeight);
            }

            const childBox = new Clutter.ActorBox();
            const availSpace = width - thumbnailsWidth - 2 * spacing;

            let wsTmbX = availSpace / 2;
            wsTmbX -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbX - spacing;

            childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY));
            childBox.set_size(thumbnailsWidth, thumbnailsHeight);
            this._thumbnails.allocate(childBox);
        }

        const {
            currentState, initialState, finalState, transitioning, progress,
        } = this._overviewAdjustment.getStateTransitionParams();

        let workspacesBox;
        const workspaceParams = [contentBox, padding, thumbnailsHeight, spacing];
        if (!transitioning) {
            workspacesBox =
                this._getWorkspacesBoxForState(currentState, ...workspaceParams);
        } else {
            const initialBox =
                this._getWorkspacesBoxForState(initialState, ...workspaceParams);
            const finalBox =
                this._getWorkspacesBoxForState(finalState, ...workspaceParams);
            workspacesBox = initialBox.interpolate(finalBox, progress);
        }
        this._workspacesView.allocate(workspacesBox);
    },

    _updateThumbnailVisibility: SecondaryMonitorDisplayVertical._updateThumbnailVisibility,

    _updateThumbnailParams: function() {
        // workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails
        // the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid
        if (!this._thumbnails._thumbnails.length) {
            this._thumbnails._createThumbnails();
        }

        if (!this._thumbnails.visible)
            return;

        const { initialState, finalState, progress } =
            this._overviewAdjustment.getStateTransitionParams();

        const initialParams = this._getThumbnailParamsForState(initialState);
        const finalParams = this._getThumbnailParamsForState(finalState);

        /*const opacity =
            Util.lerp(initialParams.opacity, finalParams.opacity, progress);
        const scale =
            Util.lerp(initialParams.scale, finalParams.scale, progress);*/

        // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread)
        const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE);
        const translation_y = (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !(opt.OVERVIEW_MODE2)) || animateOverviewMode2))
            ? Util.lerp(initialParams.translation_y, finalParams.translation_y, progress)
            : 0;

        this._thumbnails.set({
            opacity: 255,
            //scale_x: scale,
            //scale_y: scale,
            translation_y,
        });
    },

    _updateWorkspacesView: function() {
        if (this._workspacesView)
            this._workspacesView.destroy();

        if (this._settings.get_boolean('workspaces-only-on-primary')) {
            this._workspacesView = new WorkspacesView.ExtraWorkspaceView(
                this._monitorIndex,
                this._overviewAdjustment);
        } else {
            this._workspacesView = new WorkspacesView.WorkspacesView(
                this._monitorIndex,
                this._controls,
                this._scrollAdjustment,
                // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
                //this._fitModeAdjustment,
                new St.Adjustment({
                    actor: this,
                    value: 0,//FitMode.SINGLE,
                    lower: 0,//FitMode.SINGLE,
                    upper: 0,//FitMode.SINGLE,
                }),
                //secondaryOverviewAdjustment);
                this._overviewAdjustment);
        }
        this.add_child(this._workspacesView);
        this._thumbnails.opacity = 0;
    }
}

var WorkspacesDisplay = {
    _updateWorkspacesViews: function() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].destroy();

        this._primaryIndex = Main.layoutManager.primaryIndex;
        this._workspacesViews = [];
        let monitors = Main.layoutManager.monitors;
        for (let i = 0; i < monitors.length; i++) {
            let view;
            if (i === this._primaryIndex) {
                view = new WorkspacesView.WorkspacesView(i,
                    this._controls,
                    this._scrollAdjustment,
                    this._fitModeAdjustment,
                    this._overviewAdjustment);

                view.visible = this._primaryVisible;
                this.bind_property('opacity', view, 'opacity', GObject.BindingFlags.SYNC_CREATE);
                this.add_child(view);
            } else {
                view = new WorkspacesView.SecondaryMonitorDisplay(i,
                    this._controls,
                    this._scrollAdjustment,
                    // Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
                    //this._fitModeAdjustment,
                    new St.Adjustment({
                        actor: this,
                        value: 0,//FitMode.SINGLE,
                        lower: 0,//FitMode.SINGLE,
                        upper: 0,//FitMode.SINGLE,
                    }),
                    this._overviewAdjustment);
                Main.layoutManager.overviewGroup.add_actor(view);
            }

            this._workspacesViews.push(view);
        }
    },

    _onScrollEvent: function(actor, event) {
        if (this._swipeTracker.canHandleScrollEvent(event))
            return Clutter.EVENT_PROPAGATE;

        if (!this.mapped)
            return Clutter.EVENT_PROPAGATE;

        if (this._workspacesOnlyOnPrimary &&
            this._getMonitorIndexForEvent(event) != this._primaryIndex)
            return Clutter.EVENT_PROPAGATE;

        const isShiftPressed = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0;
        /*const isCtrlPressed = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
        const isAltPressed = (event.get_state() & Clutter.ModifierType.MOD1_MASK) != 0;
        const noModifiersPressed = !(isCtrlPressed && isShiftPressed && isAltPressed);

        if (OVERVIEW_MODE2 && noModifiersPressed) {
            Main.overview.hide();
            return Clutter.EVENT_STOP;
        }*/

        let direction = event.get_scroll_direction();

        if (/*SHIFT_REORDERS_WS && */isShiftPressed) {
            if (direction === Clutter.ScrollDirection.UP) {
                direction = -1;
            }
            else if (direction === Clutter.ScrollDirection.DOWN) {
                direction = 1;
            } else {
                direction = 0;
            }

            if (direction) {
                _reorderWorkspace(direction);
                // make all workspaces on primary monitor visible for case the new position is hidden
                Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => w.visible = true);
                return Clutter.EVENT_STOP;
            }
        }

        return Main.wm.handleWorkspaceScroll(event);
    },

    _onKeyPressEvent: function(actor, event) {
        const symbol = event.get_key_symbol();
        /*const { ControlsState } = OverviewControls;
        if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER && symbol !== Clutter.KEY_space)
            return Clutter.EVENT_PROPAGATE;*/

        /*if (!this.reactive)
            return Clutter.EVENT_PROPAGATE;**/
        const isCtrlPressed = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
        const isShiftPressed = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0;
        const isAltPressed = (event.get_state() & Clutter.ModifierType.MOD1_MASK) != 0;
        const { workspaceManager } = global;
        const vertical = workspaceManager.layout_rows === -1;
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;

        let which;
        switch (symbol) {
        /*case Clutter.KEY_Return:*/
        case Clutter.KEY_Page_Up:
            if (vertical)
                which = Meta.MotionDirection.UP;
            else if (rtl)
                which = Meta.MotionDirection.RIGHT;
            else
                which = Meta.MotionDirection.LEFT;
            break;
        case Clutter.KEY_Page_Down:
            if (vertical)
                which = Meta.MotionDirection.DOWN;
            else if (rtl)
                which = Meta.MotionDirection.LEFT;
            else
                which = Meta.MotionDirection.RIGHT;
            break;
        case Clutter.KEY_Home:
            which = 0;
            break;
        case Clutter.KEY_End:
            which = workspaceManager.n_workspaces - 1;
            break;
        case Clutter.KEY_space:
            if (isCtrlPressed && isShiftPressed) {
                _Util.openPreferences();
            } else if (isAltPressed) {
                Main.ctrlAltTabManager._items.forEach(i => {if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i)});
            } else if (opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && isCtrlPressed) {
                _Util.activateSearchProvider(SEARCH_RECENT_FILES_PREFIX);
            } else if (opt.WINDOW_SEARCH_PROVIDER_ENABLED/* && SEARCH_WINDOWS_SPACE*/) {
                _Util.activateSearchProvider(SEARCH_WINDOWS_PREFIX);
            }
            return Clutter.EVENT_STOP;
        case Clutter.KEY_Down:
        case Clutter.KEY_Left:
        case Clutter.KEY_Right:
        case Clutter.KEY_Up:
            if (Main.overview._overview._controls._searchController.searchActive) {
                Main.overview.searchEntry.grab_key_focus();
            } else /*if (OVERVIEW_MODE && !WORKSPACE_MODE)*/ {
                Main.ctrlAltTabManager._items.forEach(i => {if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i)});
            }
            return Clutter.EVENT_STOP;
        default:
            return Clutter.EVENT_PROPAGATE;
        }

        let ws;
        if (which < 0)
            // Negative workspace numbers are directions
            ws = workspaceManager.get_active_workspace().get_neighbor(which);
        else
            // Otherwise it is a workspace index
            ws = workspaceManager.get_workspace_by_index(which);

        if (/*SHIFT_REORDERS_WS && */isShiftPressed) {
            let direction;
            if (which === Meta.MotionDirection.UP || which === Meta.MotionDirection.LEFT)
                direction = -1;
            else if (which === Meta.MotionDirection.DOWN || which === Meta.MotionDirection.RIGHT)
                direction = 1;
            if (direction)
                _reorderWorkspace(direction);
                // make all workspaces on primary monitor visible for case the new position is hidden
                Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => w.visible = true);
                return Clutter.EVENT_STOP;
        }

        if (ws)
            Main.wm.actionMoveWorkspace(ws);

        return Clutter.EVENT_STOP;
    },
}

// same copy of this function should be available in OverviewControls and WorkspacesView
function _getFitModeForState(state) {
    switch (state) {
    case ControlsState.HIDDEN:
    case ControlsState.WINDOW_PICKER:
        return FitMode.SINGLE;
    case ControlsState.APP_GRID:
        if (opt.WS_ANIMATION && opt.SHOW_WS_TMB)
            return FitMode.ALL;
        else
            return FitMode.SINGLE;
    default:
        return FitMode.SINGLE;
    }
}

// ------------------ Reorder Workspaces - callback for Dash and workspacesDisplay -----------------------------------

function _reorderWorkspace(direction = 0) {
    let activeWs = global.workspace_manager.get_active_workspace();
    let activeWsIdx = activeWs.index();
    let targetIdx = activeWsIdx + direction;
    if (targetIdx > -1 && targetIdx < (global.workspace_manager.get_n_workspaces())) {
        global.workspace_manager.reorder_workspace(activeWs, targetIdx);
    }
}
