var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _a, _Monitors_instance;
import { EventEmitter } from "events";
import Logger from "../clients/logger";
import { System } from "./system";
import { singleton } from "./Singleton";
import { clone, getWindowDescriptor } from "./util";
import { getMyWindowIdentifier, roundToDecimal } from "./util";
import SystemManagerClient from "../common/systemManagerClient";
class Monitors extends EventEmitter {
    constructor() {
        super();
        this.allMonitors = [];
        this.ready = false;
    }
    /**
     * Use this method to gain access to the Monitors singleton. Never construct
     * a Monitors class using new.
     * @returns Singleton instance of Monitors
     */
    static async getInstance() {
        if (!__classPrivateFieldGet(_a, _a, "f", _Monitors_instance)) {
            __classPrivateFieldSet(_a, _a, singleton("Monitors", _a), "f", _Monitors_instance);
            await __classPrivateFieldGet(_a, _a, "f", _Monitors_instance).initialize();
            return __classPrivateFieldGet(_a, _a, "f", _Monitors_instance);
        }
        return __classPrivateFieldGet(_a, _a, "f", _Monitors_instance);
    }
    /**
     * Initialize the instance. Resolves promise when monitor info has been fetched.
     *
     * System events "monitor-info-changed" and "session-changed" will trigger Monitors to refresh its cached monitor info
     */
    async initialize() {
        await this.refreshMonitors();
        System.addEventListener("monitor-info-changed", () => {
            this.refreshMonitors();
        });
    }
    /**
     * Retrieves monitor info from the system and sends an event that docking responds to.
     * If the number of monitors or id of all monitors hasn't changed, its assumed this
     * is a scaling/resolution change. The internal state will still be updated and the
     * returned result will include a 'monitorsChanged' boolean to indicate wether it
     * has changed or not
     */
    async refreshMonitors() {
        Logger.system.info("refreshing cached monitors");
        const monitorInfo = await System.getMonitorInfo();
        this.allMonitors = [];
        /**
         * FEA returns an array of non-primary monitors, and then a single entry for primaryMonitor. It's more
         * useful for us internally to simply have an array of all monitors. This code creates that array (`this.allMonitors`).
         */
        let { primaryMonitor } = monitorInfo;
        primaryMonitor.whichMonitor = "primary";
        primaryMonitor.position = 0;
        this.allMonitors.push(Object.assign({}, primaryMonitor));
        for (let i = 0; i < monitorInfo.nonPrimaryMonitors.length; i++) {
            let monitor = monitorInfo.nonPrimaryMonitors[i];
            monitor.whichMonitor = i;
            monitor.position = i + 1;
            this.allMonitors.push(monitor);
        }
        this.emit("monitors-changed", {
            monitors: this.allMonitors,
        });
        this.ready = true;
        return this.allMonitors;
    }
}
_a = Monitors;
_Monitors_instance = { value: null };
/**
 * Receive a callback whenever the desktop's monitor configuration changes
 */
export const onMonitorsChanged = async (cb) => {
    const monitorsInstance = await Monitors.getInstance();
    monitorsInstance.on("monitors-changed", cb);
};
/**
 * Resolves the monitor on which the provided point lie. If the point lies outside of any monitor and the shouldReturnPrimaryDisplayAsDefault is true
 * then the primary monitor is returned otherwise the null.
 *
 * This assumes scaled dimensions for the monitor (For example from electron or WPF directly).
 */
export const getMonitorFromScaledXY = async (x, y, shouldReturnPrimaryDisplayAsDefault = true) => {
    const monitorsInstance = await Monitors.getInstance();
    if (!monitorsInstance.ready) {
        await monitorsInstance.refreshMonitors();
        return await getMonitorFromScaledXY(x, y, shouldReturnPrimaryDisplayAsDefault);
    }
    let theMonitor = null;
    const monitors = monitorsInstance.allMonitors;
    for (let monitor of monitors) {
        const { monitorRect } = monitor;
        // Are our coordinates inside the monitor? Note that
        // left and top are inclusive. right and bottom are exclusive
        if (x >= monitorRect.left && x < monitorRect.right && y >= monitorRect.top && y < monitorRect.bottom) {
            theMonitor = monitor;
            break;
        }
    }
    return shouldReturnPrimaryDisplayAsDefault ? (theMonitor !== null && theMonitor !== void 0 ? theMonitor : monitors[0]) : theMonitor;
};
/**
 * Retrieves a monitor descriptor for a window.
 */
export const getMonitorFromWindow = async (windowDescriptor) => {
    var _b, _c, _d, _e;
    const x = (_b = (Number.isFinite(windowDescriptor.x) ? windowDescriptor.x : windowDescriptor.defaultLeft)) !== null && _b !== void 0 ? _b : 0;
    const y = (_c = (Number.isFinite(windowDescriptor.y) ? windowDescriptor.y : windowDescriptor.defaultTop)) !== null && _c !== void 0 ? _c : 0;
    const x2 = x + ((_d = windowDescriptor.defaultWidth) !== null && _d !== void 0 ? _d : 0);
    const y2 = y + ((_e = windowDescriptor.defaultHeight) !== null && _e !== void 0 ? _e : 0);
    // get monitor of top-left
    let monitor = await getMonitorFromScaledXY(x, y);
    if (monitor) {
        return clone(monitor);
    }
    else {
        Logger.system.debug("getMonitorFromWindow - top-left is off screen, trying bottom right");
        // get monitor of bottom-right
        let monitorInner = await getMonitorFromScaledXY(x2, y2);
        if (monitorInner) {
            return clone(monitorInner);
        }
        else {
            Logger.system.debug("getMonitorFromWindow - bottom-right is off screen, getting primary");
            const monitors = await getAllMonitors();
            return clone(monitors[0]);
        }
    }
};
/**
 * This method can be used to retrieve monitor info from the window service. Use this from the client API.
 */
export const getWhichMonitorClientSide = async (routerClient, params = {}, cb) => {
    var _b;
    await SystemManagerClient.waitForStartup("windowService");
    const myWindowIdentifier = await getMyWindowIdentifier();
    params.windowIdentifier = (_b = params.windowIdentifier) !== null && _b !== void 0 ? _b : myWindowIdentifier;
    const { err, response } = await routerClient.query("Launcher.getMonitorInfo", params);
    cb === null || cb === void 0 ? void 0 : cb(err, response.data);
    return { err, data: response.data };
};
/**
 * This method can be used to retrieve all monitor info from the window service. Use this from the client API.
 */
export const getAllMonitorsClientSide = async (routerClient, cb) => {
    await SystemManagerClient.waitForStartup("windowService");
    const { err, response } = await routerClient.query("Launcher.getMonitorInfoAll", {});
    cb === null || cb === void 0 ? void 0 : cb(err, response.data);
    return { err, data: response.data };
};
/**
 * Gets monitor info for all system monitors. Don't use this in client code.
 */
export const getAllMonitors = async () => {
    const monitorsInstance = await Monitors.getInstance();
    const monitors = monitorsInstance.ready ? monitorsInstance.allMonitors : await monitorsInstance.refreshMonitors();
    const clonedMonitors = JSON.parse(JSON.stringify(monitors));
    return clonedMonitors;
};
/**
 * Convenience function to get the monitor for the current window
 * @param {WindowIdentifier} [windowIdentifier] The window to find the monitor for. Current window if empty.
 */
export const getMonitor = async (windowIdentifier) => {
    const windowDescriptor = await getWindowDescriptor(windowIdentifier);
    if (!windowDescriptor) {
        throw "getMonitor: Can't locate windowDescriptor.";
    }
    else {
        return await getMonitorFromWindow(windowDescriptor);
    }
};
/**
 * Given bounds of a window, will check all monitors against those bounds
 * Will return true if it is completely on a single monitor, false otherwise
 */
const isOnAMonitor = (monitorsInstance, bounds) => monitorsInstance.allMonitors.some((monitor) => {
    const { monitorRect } = monitor;
    return (bounds.left >= monitorRect.left &&
        bounds.left <= monitorRect.right &&
        bounds.right >= monitorRect.left &&
        bounds.right <= monitorRect.right &&
        bounds.top >= monitorRect.top &&
        bounds.top <= monitorRect.bottom &&
        bounds.bottom >= monitorRect.top &&
        bounds.bottom <= monitorRect.bottom &&
        bounds.height <= monitorRect.height &&
        bounds.width <= monitorRect.width);
});
/**
 * Manually initialize Monitors. Used in window service.
 */
export const getMonitorsInstance = async () => await Monitors.getInstance();
/**
 * Determine if the window's bounds cause it to overlap the docked window. If so, calculate a new position adjacent to the docked window.
 * Otherwise, the current bounds will be returned
 * @param monitor - The monitor associated with the moving window.
 * @param bounds - The location where the moving window is dropped
 * @param dockedWindowPosition - The edge the toolbar is docked to
 */
export const getBoundsWhenMovedOffDockedWindow = (monitor, bounds, dockedWindowPosition) => {
    const { availableRect } = monitor;
    const newBounds = clone(bounds);
    let shouldMove = false;
    /*
        Prevents the window titlebar from overlapping the docked toolbar.
        If the window is found to overlap, calculate a new position relative to the docked toolbar.

        The position of the docked window is used instead of just checking that the availableRect != monitorRect because we need to distinguish
        between the docked toolbar and taskbar which will both be taken out of availableRect. It should still be possible
        to overlap the taskbar unless the taskbar and docked window share an edge. There is taskbar data on the monitor object,
        the height is inaccurate in the shared edge case. Instead of needing to check every permutation of docked window + taskbar position,
        the check is greatly simplified by only checking the shared edge with the docked window.

        Only the top of the window instead of the entire titlebar space is checked to simplify the algorithm. It may be possible for the
        bottom of the titlebar to overlap the docked toolbar in very specific cases (For example another monitor on top of the toolbar monitor
        the window is moved just into the upper monitor space when there is no taskbar.)
        */
    if (dockedWindowPosition === "top" && bounds.top < availableRect.top) {
        newBounds.top = availableRect.top;
        shouldMove = true;
    }
    else if (dockedWindowPosition === "bottom" && bounds.top > availableRect.bottom) {
        newBounds.top = availableRect.bottom - bounds.height;
        shouldMove = true;
    }
    newBounds.bottom = newBounds.top + newBounds.height;
    return { shouldMove, newBounds };
};
/**
 * Calculate the new bounds of a window if moved onto the monitor by pulling the monitor along the line
 * between the top-left of the window and the center of the monitor.
 *
 * @returns an object with the name of the window, monitor and calculated bump bounds
 */
const calculateBumpBounds = (monitor, bounds, rectToUse = "availableRect") => {
    // 25px is a currently a reasonable distance to bounce the window
    const BOUNCE_DISTANCE = 25;
    // Depending if the monitor has claimed space, determine rectangle
    const monitorRect = monitor[rectToUse] || monitor.availableRect || monitor.monitorRect; // Not sure why this or condition existed but preserving in case it handled missing rects.
    // The new bounds
    const newBounds = clone(bounds);
    // If the top of the window is less than 25px from the bottom, bump it up
    if (newBounds.top + BOUNCE_DISTANCE > monitorRect.bottom) {
        newBounds.top = monitorRect.bottom - BOUNCE_DISTANCE;
    }
    // Move the window laterally if needed
    if (newBounds.right < monitorRect.left) {
        newBounds.left = monitorRect.left - newBounds.width - BOUNCE_DISTANCE;
    }
    // Calculate distance the window moved (this helps when assigning which monitor to move the window to)
    const distanceMoved = Math.sqrt((bounds.left - newBounds.left) ** 2 + (bounds.top - newBounds.top) ** 2);
    return {
        newBounds,
        distanceMoved,
        monitor,
    };
};
/**
 * Calculate the new bounds of a window if moved onto the monitor by pulling the monitor along the line
 * between the top-left of the window and the center of the monitor
 */
const getNewBoundsWhenMovedToMonitor = (monitor, bounds, rectToUse = "availableRect") => {
    // Depending if the monitor has claimed space, determine rectangle
    const monitorRect = monitor[rectToUse] || monitor.availableRect || monitor.monitorRect; // Not sure why this or condition existed but preserving in case it handled missing rects.
    // Placeholder for new bounds
    const newBounds = clone(bounds);
    // adjust vertical offset from monitor by moving top down or bottom up
    if (bounds.top < monitorRect.top) {
        newBounds.top = monitorRect.top;
    }
    else if (bounds.top > monitorRect.bottom - bounds.height) {
        newBounds.top = monitorRect.bottom - bounds.height;
    }
    // Adjust horizontal offset from monitor by moving left-edge rightward or right-edge leftward
    if (bounds.left < monitorRect.left) {
        newBounds.left = monitorRect.left;
    }
    else if (bounds.left > monitorRect.right - bounds.width) {
        newBounds.left = monitorRect.right - bounds.width;
    }
    // Recalculate bottom / right, based on movement of top / left, maintaining width / height
    newBounds.bottom = newBounds.top + newBounds.height;
    newBounds.right = newBounds.left + newBounds.width;
    // Truncate portions off monitor in case we are downsizing from a maximized window
    if (newBounds.right > monitorRect.right)
        newBounds.right = monitorRect.right;
    if (newBounds.top < monitorRect.top)
        newBounds.top = monitorRect.top;
    if (newBounds.left < monitorRect.left)
        newBounds.left = monitorRect.left;
    if (newBounds.bottom > monitorRect.bottom)
        newBounds.bottom = monitorRect.bottom;
    // Recalculate width, height in case of truncation to ensure the window fits on the new monitor
    newBounds.height = newBounds.bottom - newBounds.top;
    newBounds.width = newBounds.right - newBounds.left;
    // Calculate distance the window moved
    const distanceMoved = Math.sqrt((bounds.left - newBounds.left) ** 2 + (bounds.top - newBounds.top) ** 2);
    return {
        newBounds,
        distanceMoved,
        monitor,
    };
};
/**
 * Converts window bounds to percentage values relative to their position on the monitor.
 */
export const convertBoundsToPercentage = async (bounds, monitorPosition) => {
    const monitors = await getAllMonitors();
    const monitor = monitors.find((mon) => mon.position === monitorPosition);
    if (!monitor) {
        const err = `Monitor not found for position ${monitorPosition}`;
        return { err };
    }
    const decimalPrecision = 3;
    const newBounds = {
        percentTop: `${roundToDecimal(((bounds.top - monitor.availableRect.top) / monitor.availableRect.height) * 100, decimalPrecision)}%`,
        percentLeft: `${roundToDecimal(((bounds.left - monitor.availableRect.left) / monitor.availableRect.width) * 100, decimalPrecision)}%`,
        percentWidth: `${roundToDecimal((bounds.width / monitor.availableRect.width) * 100, decimalPrecision)}%`,
        percentHeight: `${roundToDecimal((bounds.height / monitor.availableRect.height) * 100, decimalPrecision)}%`,
    };
    return { err: null, data: newBounds };
};
/**
 * Calculate the bumped bounds. These bounds will reflect changes to ensure that the bounds provide enough
 * for the titlebar to be dragged after a move. For instance, the titlebar may be dragged too low against the Windows
 * taskbar, precluding movement. In this case, the new bounds will bump the window up slightly so that it can be moved.
 */
export const getBumpedBounds = (monitorsInstance, bounds, rectToUse = "availableRect") => {
    // This should never happen since adjustBoundsToBeOnMonitor() is only used in windowService which
    // guarantees that the monitors have been initialized
    if (!monitorsInstance)
        return bounds;
    // isOnAMonitor will return true IFF the window is completely on a single monitor
    if (isOnAMonitor(monitorsInstance, bounds)) {
        // If the window is already on a monitor keep the old bounds and stay on monitor
        Logger.system.info("Launcher.adjustBoundsToSlightlyBeOnMonitor: on monitor.");
        return bounds;
    }
    // Calculate if the window is on any monitor, and the distance between the top left and the center of the window
    const monitorAdjustments = monitorsInstance.allMonitors.map((monitor) => {
        const monitorToAdjustBounds = monitor.rawMonitor ? monitor.rawMonitor : monitor;
        return calculateBumpBounds(monitorToAdjustBounds, bounds, rectToUse);
    });
    // Get the closest monitor, the one with minimum distanceMoved
    const monitorAdjustmentClosest = monitorAdjustments.sort((md1, md2) => md1.distanceMoved - md2.distanceMoved)[0];
    // notify the movement
    Logger.system.info("Launcher.adjustBoundsToSlightlyBeOnMonitor: not on monitor.  bounds", bounds, "monitor name", monitorAdjustmentClosest.monitor.name, "newBounds", monitorAdjustmentClosest.newBounds);
    // Assign bounds
    return monitorAdjustmentClosest.newBounds;
};
/**
 * Takes a window's bounds and makes sure it's on a monitor. If the window is just off the monitor, bounds will be adjusted to be on the monitor it was on and then the check will occur again. If the window isn't on a monitor (or runs off of a monitor), we determine the closest monitor
 * based on the distance from the top-left corner of the window to the center of the monitor, and then pull the monitor along that line
 * until the window is on the edge of the monitor.
 *
 * Returns the new bounds for the window. which are different from currentBounds only if the window needs to be relocated
 */
export const adjustBoundsToBeOnMonitor = (monitorsInstance, bounds, rectToUse = "availableRect") => {
    // This should never happen since adjustBoundsToBeOnMonitor() is only used in windowService which
    // guarantees that the monitors have been initalized
    if (!monitorsInstance)
        return bounds;
    if (isOnAMonitor(monitorsInstance, bounds)) {
        // If the window is already on a monitor keep the old bounds and stay on monitor
        Logger.system.info("Launcher.adjustWindowDescriptorBoundsToBeOnMonitor: on monitor.");
        return bounds;
    }
    // calculate if the window is on any monitor, and the distance between the top left and the center of the window
    const monitorAdjustments = monitorsInstance.allMonitors.map((monitor) => {
        const monitorToAdjustBounds = monitor.rawMonitor ? monitor.rawMonitor : monitor;
        return getNewBoundsWhenMovedToMonitor(monitorToAdjustBounds, bounds, rectToUse);
    });
    // Get the closest monitor, the one with minimum distanceMoved
    const monitorAdjustmentClosest = monitorAdjustments.sort((md1, md2) => md1.distanceMoved - md2.distanceMoved)[0];
    // notify the movement
    Logger.system.info("Launcher.adjustWindowDescriptorBoundsToBeOnMonitor: not on monitor.  bounds", bounds, "monitor name", monitorAdjustmentClosest.monitor.name, "newBounds", monitorAdjustmentClosest.newBounds);
    // assign bounds
    return monitorAdjustmentClosest.newBounds;
};
/**
 * Promisified version of adjustBoundsToBeOnMonitor() which doesn't require a monitorsInstance
 */
export const adjustBoundsToBeOnMonitorAsync = async (bounds, rectToUse = "availableRect") => {
    const monitorsInstance = await Monitors.getInstance();
    return adjustBoundsToBeOnMonitor(monitorsInstance, bounds, rectToUse);
};
/**
 * Computes the position of the taskbar by analyzing the difference between the monitorRect and availableRect.
 *
 * Returns null if there is no taskbar.
 */
export const getTaskbar = (monitor) => {
    if (monitor.monitorRect.bottom != monitor.availableRect.bottom) {
        return {
            top: monitor.availableRect.bottom,
            left: monitor.availableRect.left,
            right: monitor.availableRect.right,
            bottom: monitor.monitorRect.bottom,
            width: monitor.availableRect.width,
            height: monitor.monitorRect.bottom - monitor.availableRect.bottom,
            edge: "bottom",
        };
    }
    else if (monitor.monitorRect.top != monitor.availableRect.top) {
        return {
            top: monitor.monitorRect.top,
            left: monitor.availableRect.left,
            right: monitor.availableRect.right,
            bottom: monitor.availableRect.top,
            width: monitor.availableRect.width,
            height: monitor.monitorRect.top - monitor.availableRect.top,
            edge: "top",
        };
    }
    else if (monitor.monitorRect.right != monitor.availableRect.right) {
        return {
            top: monitor.monitorRect.top,
            left: monitor.availableRect.right,
            right: monitor.monitorRect.right,
            bottom: monitor.availableRect.bottom,
            width: monitor.monitorRect.width - monitor.availableRect.width,
            height: monitor.monitorRect.height,
            edge: "right",
        };
    }
    else if (monitor.monitorRect.left != monitor.availableRect.left) {
        return {
            top: monitor.monitorRect.top,
            left: monitor.monitorRect.left,
            right: monitor.availableRect.left,
            bottom: monitor.availableRect.bottom,
            width: monitor.monitorRect.width - monitor.availableRect.width,
            height: monitor.monitorRect.height,
            edge: "left",
        };
    }
    return null;
};
/**
 * Gets the area of the overlap between two rectangles.
 */
export const getOverlap = (rect1, rect2) => {
    let xOverlap;
    if (rect1.left >= rect2.left && rect1.left <= rect2.right) {
        xOverlap = rect2.right - rect1.left;
    }
    else if (rect2.left >= rect1.left && rect2.left <= rect1.right) {
        xOverlap = rect1.right - rect2.left;
    }
    else {
        return 0;
    }
    let yOverlap;
    if (rect1.top >= rect2.top && rect1.top <= rect2.bottom) {
        yOverlap = rect2.bottom - rect1.top;
    }
    else if (rect2.top >= rect1.top && rect2.top <= rect1.bottom) {
        yOverlap = rect1.bottom - rect2.top;
    }
    else {
        return 0;
    }
    return xOverlap * yOverlap;
};
/**
 * Given a set of monitors, will return the monitor on which a set of bounds overlaps.
 * If the window bounds are across more than one monitor then the monitor with the most overlapping surface area with
 * the window is returned.
 *
 * If there is no overlap due to the window being off-monitor it will return the first monitor in the array.
 */
export const getMonitorByBoundsSync = (windowBounds, monitors) => {
    const overlaps = monitors.map((monitor) => {
        var _b;
        return {
            monitor,
            overlap: getOverlap(windowBounds, ((_b = monitor.rawMonitor) === null || _b === void 0 ? void 0 : _b.monitorRect) || monitor.availableRect),
        };
    });
    // If there is no overlap due to the window being off-screen it will return the first monitor in the array
    if (overlaps.length === 0)
        return monitors[0];
    // Return the monitor with the greatest overlap.
    return overlaps.reduce((prev, current) => (prev.overlap > current.overlap ? prev : current)).monitor;
};
export const getMonitorByBounds = async (windowBounds) => {
    const monitors = await getAllMonitors();
    return getMonitorByBoundsSync(windowBounds, monitors);
};
//# sourceMappingURL=Monitors.js.map