import { RouterClient } from "../../clients/routerClient";
import { Logger } from "../../clients/logger";
/** Timeout in milliseconds for terminal connection checks that will cause a checkConnection
 * call to fail. */
const CONNECTION_CHECK_TIMEOUT = 1500;
/** Timeout in milliseconds for setting the connect state. */
const SET_CONNECT_STATE_TIMEOUT = 2500;
/**
 * Client class for communicating with the Finsemble Bloomberg Bridge over the the Finsemble Router,
 * which in turn communicates with the Bloomberg Terminal via the Terminal Connect and BLP APIs.
 *
 * This Class may either be imported into code and initialized by passing in an instance
 * of the Finsemble RouterClient and Logger (e.g. in Finsemble Custom Desktop Service) or
 * used as a preload to be applied to a component, where it will be automatically initialized
 * via instances of the RouterClient and Logger referenced from `FSBL.Clients`.
 *
 * @experimental
 */
export class BloombergBridgeClient {
    /**
     * BloombergBridgeClient constructor.
     * @example Instantiating the client in a Finsemble component:
     * ```Javascript
     * let bbg = new BloombergBridgeClient(FSBL.Clients.RouterClient, FSBL.Clients.Logger);
     * ```
     *
     * Instantiating the client in a Finsemble service:
     * ```Javascript
     * let bbg = new BloombergBridgeClient();
     * ```
     */
    constructor() {
        this.connectionEventListener = null;
        this.groupEventListener = null;
        this.routerClient = null;
        this.logger = null;
        this.routerClient = RouterClient;
        this.logger = Logger;
    }
    /**
     * Set a handler function for connection events.
     *
     * Note that only one handler function is permitted, hence calling
     * this multiple times will simply replace the existing handler.
     *
     * @param cb Callback
     * @example
     * ```Javascript
     * let connectionEventHandler = (err, resp) => {
     *     if (!err && resp && resp.registered) {
     *         showConnectedIcon();
     *     } else {
     *         showDisconnectedIcon();
     *     }
     * };
     * bbg.setConnectionEventListener(connectionEventHandler);
     * ```
     */
    setConnectionEventListener(cb) {
        var _a;
        if (this.connectionEventListener) {
            this.removeConnectionEventListener();
        }
        Logger.debug("Added new event listener for Bloomberg connection events.");
        this.connectionEventListener = (err, response) => {
            if (err) {
                Logger.error("Received Bloomberg connection error: ", err);
            }
            else {
                Logger.log("Received Bloomberg connection event: ", response);
            }
            cb(err, response);
        };
        (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.addListener("BBG_connection_status", this.connectionEventListener);
    }
    /**
     * Remove the current connection event handler.
     * @example
     * ```Javascript
     * bbg.removeConnectionEventListener();
     * ```
     */
    removeConnectionEventListener() {
        var _a;
        if (this.connectionEventListener) {
            (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.removeListener("BBG_connection_status", this.connectionEventListener);
            Logger.debug("Removed Bloomberg connection event listener");
        }
        else {
            Logger.warn("Tried to remove non-existent connection event listener");
        }
    }
    /**
     * Set a handler function for Launchpad group context changed events, which
     * are fired when a group's context changes or a new group is created.
     *
     * Note that only one handler function is permitted, hence calling
     * this multiple times will simply replace the existing handler.
     * @param cb Handler function to call on group context change events
     * @example
     * ```Javascript
     * bbg.setGroupEventListener((err, response) => {
     *     if (!err) {
     *         if (response.data.group && response.data.group.type == "monitor") {
     *             Logger.log("Monitor event:\n" + JSON.stringify(response.data, null, 2));
     *         } else {
     *             Logger.log("Security event:\n" + JSON.stringify(response.data, null, 2));
     *         }
     *     } else {
     *             Logger.error("Error returned from setGroupEventListener", err);
     *     }
     * });
     * ```
     */
    setGroupEventListener(cb) {
        var _a;
        if (this.groupEventListener) {
            this.removeGroupEventListener();
        }
        Logger.debug("Set new listener for Bloomberg group context events...");
        this.groupEventListener = (err, response) => {
            if (err) {
                Logger.error("Received Bloomberg group context error: ", err);
            }
            else {
                Logger.debug("Received Bloomberg group context event: ", response);
            }
            cb(err, response);
        };
        (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.addListener("BBG_group_context_events", this.groupEventListener);
    }
    /**
     * Remove the current group context changed event handler.
     * @example
     * ```Javascript
     * bbg.removeGroupEventListener();
     * ```
     */
    removeGroupEventListener() {
        var _a;
        if (this.groupEventListener) {
            (_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.removeListener("BBG_group_context_events", this.groupEventListener);
            Logger.debug("Removed group context event listener");
        }
        else {
            Logger.warn("Tried to remove non-existent group context event listener");
        }
    }
    /**
     * Set the connection state for Bloomberg bridge, i.e. whether it is enabled or not.
     * Note that the remote connection config should only be changed while the connection is
     * disabled or disconnected, as it is read when attempting to connect.
     * @param enabled Boolean flag indicating whether the bridge should connect to Bloomberg.
     * If set false while connected, the bridge will automatically disconnect.
     * @param cb Optional callback that will return response as true if we have successfully set the
     * connect states.
     * @example
     * ```Javascript
     * bbg.setConnectState(true);
     * bbg.setConnectState(false, (err, resp) => { ... });
     * ```
     */
    setEnabled(enabled, cb) {
        var _a;
        Logger.debug(`Setting enabled to: ${enabled}`);
        // if we don't get a response something is wrong
        const timeout = setTimeout(() => {
            Logger.warn("BBG_connect call timed-out", null);
            if (cb) {
                cb("Set enabled timeout", null);
            }
        }, SET_CONNECT_STATE_TIMEOUT);
        void ((_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.query("BBG_connect", { connect: enabled }, (err, resp) => {
            var _a;
            clearTimeout(timeout);
            if (err) {
                Logger.warn("Received error when setting enabled: ", err);
                if (cb) {
                    cb(err, false);
                }
            }
            else {
                if (resp && resp.data && resp.data["status"]) {
                    Logger.debug("enabled set to: ", enabled);
                    if (cb) {
                        cb(null, true);
                    }
                }
                else {
                    Logger.log("Received negative or empty response when setting enabled: ", resp);
                    if (cb) {
                        cb(`Received negative or empty response when setting enabled: ${(_a = resp === null || resp === void 0 ? void 0 : resp.data) === null || _a === void 0 ? void 0 : _a["message"]}`, null);
                    }
                }
            }
        }));
    }
    /**
     * Check that Bloomberg bridge is connected to the Bloomberg Terminal.
     * @param cb Callback for connection response that will return response as true if we are
     * connected.
     * @example
     * ```Javascript
     * let checkConnectionHandler = (err, registered) => {
     *     if (!err && registered) {
     *         showConnectedIcon();
     *     } else {
     *         showDisconnectedIcon();
     *     }
     * };
     * bbg.checkConnection(checkConnectionHandler);
     * ```
     */
    checkConnection(cb) {
        var _a;
        Logger.debug("Checking connection status...");
        // if we don't get a response something is wrong
        const timeout = setTimeout(() => {
            Logger.error("BBG_connection_status check timed-out. Is the Bloomberg Bridge running?", null);
            cb("Connection check timeout", null);
        }, CONNECTION_CHECK_TIMEOUT);
        void ((_a = this.routerClient) === null || _a === void 0 ? void 0 : _a.query("BBG_connection_status", {}, (err, resp) => {
            clearTimeout(timeout);
            if (err) {
                Logger.warn("Received error when checking connection status. Is the Bloomberg Bridge running?", err);
                cb(err, false);
            }
            else {
                if (resp && resp.data && resp.data["registered"]) {
                    Logger.debug("Received connection status: ", resp.data);
                    cb(null, true);
                }
                else {
                    Logger.debug("Received negative or empty response when checking connection status: ", resp);
                    cb(null, false);
                }
            }
        }));
    }
    /**
     * Internal function used to send a Query to the BBG_run_terminal_function responder of
     * BloombergBridge,
     * which implements the majority functions for the BloombergBridgeClient.
     * @param message The query data to pass.
     * @param message.function Required field that determines which function to run.
     * @param cb Callback
     * @private
     */
    queryBloombergBridge(message, 
    // cb: (err: string | Error, response: unknown) => void,
    cb) {
        var _a, _b;
        Logger.debug("BBG_run_terminal_function query:", message);
        (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug("BBG_run_terminal_function query:", message);
        void ((_b = this.routerClient) === null || _b === void 0 ? void 0 : _b.query("BBG_run_terminal_function", message, this.apiResponseHandler(cb)));
    }
    /**
     * Internal function used to return a call back that will wrap the supplied callback and log all
     * responses
     * from the Bloomberg Bridge to aid debugging.
     * @param cb Callback
     * @private
     */
    apiResponseHandler(cb) {
        return (err, response) => {
            var _a, _b, _c, _d;
            if (err) {
                const errMsg = "Error returned by BBG_run_terminal_function: ";
                Logger.error(errMsg, err);
                (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(errMsg, err);
                cb(err, null);
            }
            else if (!response || !response.data || !(response === null || response === void 0 ? void 0 : response.data.status)) {
                const errMsg = ((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.message)
                    ? response.data.message
                    : "Negative status returned by BBG_run_terminal_function: ";
                Logger.warn(errMsg, response);
                (_c = this.logger) === null || _c === void 0 ? void 0 : _c.warn(errMsg, response);
                cb(errMsg, null);
            }
            else {
                const msg = "BBG_run_terminal_function successful, response: ";
                // tslint:disable-next-line:no-magic-numbers
                Logger.debug(msg + JSON.stringify(response.data, null, 2));
                (_d = this.logger) === null || _d === void 0 ? void 0 : _d.log(msg, response);
                cb(null, response.data);
            }
        };
    }
    /**
     * Run a function in one of the 4 Bloomberg panel windows.
     * @param mnemonic The mnemonic of the Bloomberg command to run on a panel
     * @param securities (optional) An array of strings representing one or more securities
     * to pass to the function.
     * @param panel Panel number to run the command on (accepts values "1", "2", "3" or
     * "4")
     * @param tails (optional) parameters passed to the function
     * @param cb Callback
     * @example
     * ```Javascript
     * let mnemonic = "DES";
     * let securities = ["MSFT US Equity"];
     * let panel = 3;
     * let tails = null;
     * bbg.runBBGCommand(mnemonic, securities, panel, tails, (err, response) => {
     *     if (!err) {
     *         Logger.log(`Ran command "${mnemonic}" on panel ${panel}`);
     *     } else {
     *         Logger.error("Error returned from runBBGCommand", err);
     *     }
     * });
     * ```
     */
    runBBGCommand(mnemonic, securities, panel, tails, cb) {
        const message = {
            function: "RunFunction",
            mnemonic,
            securities,
            tails,
            panel,
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Create a new worksheet with the specified securities and name.
     * @param worksheetName Name for the worksheet.
     * @param securities An array of strings representing one or more securities.
     * @param cb Callback
     * @example
     * ```Javascript
     * let securities = ["TSLA US Equity", "AMZN US Equity"];
     * bbg.runCreateWorksheet(worksheetName, securities, (err, response) => {
     *     if (!err) {
     *         if (response && response.worksheet) {
     *             //Id assigned to the worksheet
     *             let worksheetId = response.worksheet.id;
     *             //List of securities resolved by Bloomberg from the input list, unresolvable securities will be removed
     *             let workSheetSecurities = response.worksheet.securities;
     *         } else {
     *             Logger.error("invalid response from runCreateWorksheet", response);
     *         }
     *     } else {
     *         Logger.error("Error returned from runCreateWorksheet", err);
     *     }
     * });
     * ```
     */
    runCreateWorksheet(worksheetName, securities, cb) {
        const message = {
            function: "CreateWorksheet",
            name: worksheetName,
            securities,
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Retrieve all worksheets for the user.
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runGetAllWorksheets((err, response) => {
     *     if (!err) {
     *         if (response && response.worksheets && Array.isArray(response.worksheets)) {
     *             response.worksheets.forEach(worksheet => {
     *                 let worksheetName = worksheet.name;
     *                 let worksheetId = worksheet.id;
     *                 ...
     *             });
     *         } else {
     *             Logger.error("invalid response from runGetAllWorksheets", response);
     *         }
     *     } else {
     *         Logger.error("Error returned from runGetAllWorksheets", err);
     *     }
     * });
     * ```
     */
    runGetAllWorksheets(cb) {
        const message = {
            function: "GetAllWorksheets",
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Retrieve a specific worksheet by id.
     * @param worksheetId Worksheet ID to retrieve.
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runGetWorksheet(worksheetId, (err, response) => {
     *     if (!err) {
     *         if (response && response.worksheet && Array.isArray(response.worksheet.securities)) {
     *             let worksheetName = response.worksheet.name;
     *             let worksheetId = response.worksheet.id;
     *             let workSheetSecurities = response.worksheet.securities;
     *             ...
     *         } else {
     *             Logger.error("invalid response from runGetWorksheet");
     *         }
     *     } else {
     *         Logger.error("Error returned from runGetWorksheet", err);
     *     }
     * });
     * ```
     */
    runGetWorksheet(worksheetId, cb) {
        const message = {
            function: "GetWorksheet",
            id: worksheetId,
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Replaces a specific worksheet by ID with a new list of securities.
     * @param worksheetId  Worksheet ID to replace.
     * @param securities An array of strings representing one or more securities.
     * @param cb Callback
     * ```Javascript
     * let securities = ["TSLA US Equity", "AMZN US Equity"];
     * bbg.runReplaceWorksheet(worksheetId, securities, (err, response) => {
     *     if (!err) {
     *         if (response && response.worksheet) {
     *             //Details of the updated worksheet will be returned
     *             let worksheetName = response.worksheet.name;
     *             //List of securities resolved by Bloomberg from the input list, unresolvable securities will be removed
     *             let workSheetSecurities = response.worksheet.securities;
     *             ...
     *         } else {
     *             Logger.error("invalid response from runReplaceWorksheet", response);
     *         }
     *     } else {
     *         Logger.error("Error returned from runReplaceWorksheet", err);
     *     }
     * });
     * ```
     */
    runReplaceWorksheet(worksheetId, securities, cb) {
        const message = {
            function: "ReplaceWorksheet",
            id: worksheetId,
            securities,
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Gets a list of all available Launchpad component groups.
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runGetAllGroups((err, response) => {
     *     if (!err) {
     *         if (response && response.groups && Array.isArray(response.groups)) {
     *             //do something with the returned data
     *             response.groups.forEach(group => {
     *                 let groupName = group.name;
     *                 let groupType = group.type;
     *                 let groupCurrentValue = group.value;
     *                 ...
     *             });
     *         } else {
     *             Logger.error("Invalid response returned from runGetAllGroups", response);
     *         }
     *     } else {
     *         Logger.error("Error returned from runGetAllGroups", err);
     *     }
     * });
     * ```
     */
    runGetAllGroups(cb) {
        const message = {
            function: "GetAllGroups",
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Returns details of a Launchpad component group by name.
     * @param groupName The name of the component group to retrieve.
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runGetGroupContext(groupName, (err, response) => {
     *     if (!err) {
     *         if (response && response.group) {
     *             let groupName = response.group.name;
     *             let groupType = group.type;
     *             let groupCurrentValue = group.value;
     *             ...
     *         } else {
     *             Logger.error("Invalid response returned from runGetGroupContext", response);
     *         }
     *     } else {
     *         Logger.error("Error returned from runGetGroupContext", err);
     *     }
     * });
     * ```
     */
    runGetGroupContext(groupName, cb) {
        const message = {
            function: "GetGroupContext",
            name: groupName,
        };
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Set the context value of a Launchpad group by name.
     * @param groupName The name of the component group to set the value of.
     * @param value The value to set for hte group, this will usually be a string
     * representing a security.
     * @param cookie (optional) Cookie value identifying a particular component within
     * a group to set the context of. Pass null if not required.
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runSetGroupContext(groupName, newValue, null, (err, response) => {
     *     if (!err) {
     * 	       // You may wish to retrieve the current state of Launchpad group here as Bloomberg
     *         // will resolve any security your set and may therefore its value may differ from
     *         // what you sent.
     *         bbg.runGetGroupContext(groupName, (err2, response2) => { ... });
     *     } else {
     *         Logger.error("Error returned from runSetGroupContext", err);
     *     }
     * });
     * ```
     */
    runSetGroupContext(groupName, value, cookie, cb) {
        const message = {
            function: "SetGroupContext",
            name: groupName,
            value,
        };
        if (cookie) {
            message.cookie = cookie;
        }
        this.queryBloombergBridge(message, cb);
    }
    /**
     * Search for Bloomberg securities by name
     * @param security The string to lookup a security for
     * @param cb Callback
     * @example
     * ```Javascript
     * bbg.runSecurityLookup(security, (err, response) => {
     *     if (!err) {
     * 	       if (response && response.results) {
     *             //do something with the results
     *             response.results.forEach(result => {
     *                 Logger.log(result.name + " " + result.type);
     *                 ...
     *             });
     * 	       } else {
     *             Logger.error("invalid response from runSecurityLookup", response);
     * 	       }
     *     } else {
     * 	       Logger.error("Error returned from runSecurityLookup", err);
     *     }
     * });
     * ```
     */
    runSecurityLookup(security, cb) {
        const message = {
            function: "SecurityLookup",
            security,
        };
        this.queryBloombergBridge(message, cb);
    }
}
export default new BloombergBridgeClient();
//# sourceMappingURL=BloombergBridgeClient.js.map