function toSingleString(array, delimiter) {
    if (array instanceof Array) {
        var s = "";
        for (var i = 0; i < array.length; i++) {
            if (i) {
                s += delimiter;
            }
            s += array[i];
        }
        return s;
    } else {
        return array;
    }
}

function jsonValuesToString(jsonObject, delimiter) {
    var s = "";
    var firstKey = true;

    for (var key in jsonObject) {
        if (jsonObject.hasOwnProperty(key)) {
            if (firstKey) {
                firstKey = false;
            } else {
                s += delimiter;
            }
            s += jsonObject[key];
        }
    }
    return s;
}

function generatePage(pageNumber, itemsPerPage, allRows) {
    var pageRows = [];
    var startIndex = itemsPerPage * (pageNumber - 1);

    if (allRows instanceof Array) {
        for (var i = startIndex; i < allRows.length; i++) {
            if (i < startIndex + itemsPerPage) {
                pageRows.push(allRows[i]);
            } else {
                break;
            }
        }
    } else {
        pageRows.push(allRows);
    }
    return pageRows;
}

function booleanToInt(bool) {
    return bool ? 1 : 0;
}

function stringToBoolean(str) {
    return str == 'true';
}

function showPopup(popupSelector, callbackOnShown) {
    var popup = $(popupSelector);
    var targetPlace = 'body';

    window.activePopup = popup;

    if (popup.length) {
        popup.detach().appendTo(targetPlace).modal({
            backdrop: 'static'
        });

        setTimeout(function () {
            if (typeof callbackOnShown !== 'undefined') {
                callbackOnShown();
            }
            if (popup.find('.modal-body').first().attr("style") === undefined) {
                var winHeight = popup.find('.modal-body').first().outerHeight();
                popup.find('.modal-body').first().css('height', winHeight + 'px');
            }
        }, 10);

        popup.on('hide.bs.modal', function (e) {
            window.activePopup = null;
        })

        if (!popup.hasClass("not-resize")) {
            popup.find(".modal-dialog").resizable({
                handles: 'e, w'
            });
        }

        popup.find(".modal-dialog").draggable({
            handle: ".modal-header"
        });

        setTimeout(function () {
            window.dispatchEvent(new Event('resize'));
        }, 0);
    } else {
        console.error("Popup window with id '" + popupSelector + "' doesn't exist");
    }
}

function hidePopup(popupName) {
    $(popupName).modal('hide');
}

function printNameById(id, values) {
    if (values) {
        for (var i = 0; i < values.length; i++) {
            if (id == values[i].id) {
                return values[i].name;
            }
        }
    }

    return id;
}

function isFileNameValid(fname) {
    var rg1 = /^[^\\/:\*\?"<>\|]+$/; // forbidden characters \ / : * ? " < > |
    var rg2 = /^\./; // cannot start with dot (.)
    var rg3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; // forbidden file names
    var rg4 = /[^\S ]/g; //no whitespaces except space

    return rg1.test(fname) && !rg2.test(fname) && !rg3.test(fname) && !rg4.test(fname);
}

function validate(form) {
    if (typeof form.$error == 'undefined' || Object.keys(form.$error).length == 0) {
        return null;
    }

    var errors = [];

    for (var key in form.$error) {
        for (var index = 0; index < form.$error[key].length; index++) {
            switch (key) {
                case 'required':
                    errors.push(form.$error[key][index].$name + ' cannot be blank.');
                    break;

                default:
                    errors.push(form.$error[key][index].$name + ' invalid ' + key + '.');
                    break;
            }
        }
    }

    return errors;
}

function replaceWhiteSpace(inputString, replaceCharacter) {
    return inputString.replace(/\s/g, replaceCharacter);
}

function convertDateToUTC(date) {
    return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}

function numberLeadingZero(num, size) {
    num = num.toString();
    while (num.length < size) num = "0" + num;
    return num;
}

function timeToDateString(millis) {
    var date = convertDateToUTC(new Date(millis));
    var year = date.getFullYear();
    var month = numberLeadingZero(date.getMonth() + 1, 2)
    var day = numberLeadingZero(date.getDate(), 2)

    return year + "." + month + "." + day;
}

function timeToDateTimeString(millis, removeTodaysDate) {
    var date = convertDateToUTC(new Date(millis));
    var today = convertDateToUTC(new Date());

    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    var day = date.getDate();
    var hour = date.getHours();
    var minute = date.getMinutes();
    var seconds = date.getSeconds();

    month = month < 10 ? "0" + month : month;
    day = day < 10 ? "0" + day : day;
    hour = hour < 10 ? "0" + hour : hour;
    minute = minute < 10 ? "0" + minute : minute;
    seconds = seconds < 10 ? "0" + seconds : seconds;

    if (removeTodaysDate && date.getFullYear() == today.getFullYear() && date.getMonth() == today.getMonth() && date.getDate() == today.getDate()) {
        return hour + ":" + minute + ":" + seconds;
    } else {
        return year + "." + month + "." + day + " " + hour + ":" + minute + ":" + seconds;
    }
}

function isValidDate(dateString) {
    // First check for the pattern
    if (!/^\d{4}\.\d{1,2}\.\d{1,2}$/.test(dateString))
        return false;

    // Parse the date parts to integers
    var parts = dateString.split(".");
    var year = parseInt(parts[0], 10);
    var month = parseInt(parts[1], 10);
    var day = parseInt(parts[2], 10);

    // Check the ranges of month and year
    if (year < 1000 || year > 3000 || month == 0 || month > 12)
        return false;

    var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    // Adjust for leap years
    if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
        monthLength[1] = 29;

    // Check the range of the day
    return day > 0 && day <= monthLength[month - 1];
}

function getDate(dateString, removeOffset) {
    var splitted = dateString.split('.');
    var year = parseInt(splitted[0]);
    var month = parseInt(splitted[1]) - 1;
    var day = parseInt(splitted[2]);

    var date = new Date(year, month, day);
    return removeOffset ? new Date(date.getTime() - (date.getTimezoneOffset() * 60000)) : date;
}

function getDateString(dateObj) {
    return dateObj.getFullYear() + "." + (dateObj.getMonth() + 1) + "." + dateObj.getDate();
}

// requires date objects as arguments
function getDaysBetweenDates(dateFrom, dateTo) {
    var dateFromMillis = dateFrom.getTime();
    var dateToMillis = dateTo.getTime();
    return Math.round((dateToMillis - dateFromMillis) / (1000 * 3600 * 24));
}

function dateIsAfter(date1, date2, acceptSameDates) {
    var date1Cloned = new Date(date1.getTime());
    var date2Cloned = new Date(date2.getTime());

    if (date1Cloned.getUTCFullYear() > date2Cloned.getUTCFullYear()) return true;
    if (date1Cloned.getUTCFullYear() < date2Cloned.getUTCFullYear()) return false;

    if (date1Cloned.getUTCMonth() > date2Cloned.getUTCMonth()) return true;
    if (date1Cloned.getUTCMonth() < date2Cloned.getUTCMonth()) return false;

    if (date1Cloned.getUTCDate() > date2Cloned.getUTCDate()) return true;
    if (date1Cloned.getUTCDate() < date2Cloned.getUTCDate()) return false;

    return acceptSameDates;
}

function getRunTime(millis, displayMs) {
    if (!displayMs) {
        millis = Math.round(millis / 100) * 100; //round to one decimal place
    }

    var seconds = ((millis / 1000) % 60) | 0;
    var minutes = ((millis / (1000 * 60)) % 60) | 0;
    var hours = ((millis / (1000 * 60 * 60)) % 24) | 0;
    var days = (millis / (1000 * 60 * 60 * 24)) | 0;

    if (days != "0") {
        return days + "d " + hours + ":" + minutes + ":" + seconds;
    } else if (hours != "0") {
        return hours + "h " + minutes + "m " + seconds + "s";
    } else if (minutes != "0") {
        return minutes + "m " + seconds + "s";
    } else if (seconds != "0" || !displayMs) {
        return (millis / 1000) + "s";
    } else return millis + "ms";
}

function addDays(date, days) {
    var result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
}

function getConditionsGridId(actionName) {
    var gridName = actionName.replace(/[\W_]+/g, "-"); //replace non-alphanumeric characters
    return gridName + "_grid";
}

function checkDoubleValue(value) {
    var parsed = parseFloat(value);
    if (parsed == value) return true;
    else return false;
}

function checkIntegerValue(value) {
    var parsed = parseInt(value);
    if (parsed == value) return true;
    else return false;
}

function valueFilled(value) {
    if ((!value && value !== 0 && value !== false && value !== 'false') || value === 'not set' || value === 'undefined') {
        return false;
    }
    return true;
}

//pretty print
function formatXML(xml) {
    var formatted = '';
    var reg = /(>)(<)(\/*)/g;
    xml = xml.replace(reg, '$1\r\n$2$3');
    var pad = 0;
    jQuery.each(xml.split('\r\n'), function (index, node) {
        var indent = 0;
        if (node.match(/.+<\/\w[^>]*>$/)) {
            indent = 0;
        } else if (node.match(/^<\/\w/)) {
            if (pad != 0) {
                pad -= 1;
            }
        } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
            indent = 1;
        } else {
            indent = 0;
        }

        var padding = '';
        for (var i = 0; i < pad; i++) {
            padding += '  ';
        }

        formatted += padding + node + '\r\n';
        pad += indent;
    });

    return formatted;
}

function sortByKey(array, key) {
    return array.sort(function (a, b) {
        var x = a[key];
        var y = b[key];
        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
    });
}

function objectToURL(data) {
    var url = "";

    var i = 0;
    for (var key in data) {
        if (i != 0) {
            url += "&";
        }

        url += key + "=" + data[key];

        i++;
    }

    return encodeURI(url);
}

function encodeUriCustom(url) {
    var url = encodeURI(url).replace(/\+/g, '%2B');

    return url;
}

function correctStateName(name) {
    return name.replace(/[\W_]+/g, "-"); //replace non-alphanumeric characters
}

function endsWith(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
}

function getHelpText(xml, elementName) {
    var taskObj = xmlToObject(xml);
    if (taskObj) {
        var settingsObj = taskObj.find("Settings")[0];
        if (settingsObj) {
            var wantedElem = getChildElements(settingsObj, elementName)[0];
            if (wantedElem) {
                return correctAttrValue(wantedElem.attributes['help']);
            }
        }
    }
}

//returns element page offset
function getPos(el) {
    for (var lx = 0, ly = 0; el != null; lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent);
    return {
        x: lx,
        y: ly
    };
}

function arrayContains(needle, arrhaystack) {
    return (arrhaystack.indexOf(needle) > -1);
}

function getItem(array, variable, value) {
    if (array) {
        for (var i = 0; i < array.length; i++) {
            var item = array[i];
            if (item[variable] == value) {
                return item;
            }
        }
    }
}

function removeItem(array, variable, value) {
    for (var i = 0; i < array.length; i++) {
        var item = array[i];
        if (item[variable] == value) {
            array.splice(i, 1);
            return;
        }
    }
}

function arrayNotEmpty(array) {
    return array && array.length;
}

function arrayEmpty(array) {
    return !arrayNotEmpty(array);
}

function trimToDecimalPlaces(value, decimals) {
    var valueStr = '' + value;
    var dotIndex = valueStr.indexOf('.');

    if (dotIndex != -1) {
        var trimmedLength = parseInt(dotIndex + decimals + 1);

        if (valueStr.length <= trimmedLength) {
            return value;
        } else {
            return parseFloat(valueStr.substr(0, trimmedLength));
        }
    } else return value;
}

function getDecimalsCount(value) {
    var valueStr = '' + value;
    var dotIndex = valueStr.indexOf('.');

    if (dotIndex != -1) {
        var valueSplit = valueStr.split('.');
        return valueSplit[1].length;
    } else return 0;
}

function getTotalOffset(element) {
    var top = 0,
        left = 0;
    do {
        top += element.offsetTop || 0;
        left += element.offsetLeft || 0;
        element = element.offsetParent;
    } while (element);

    return {
        top: top,
        left: left
    };
};

function get2Digits(value) {
    return ("0" + value).slice(-2);
}

function loadToArray(array, data, addOnly) {
    if (!addOnly) array.length = 0;

    if (!data) {
        return;
    }

    for (var i = 0; i < data.length; i++) {
        array.push(data[i]);
    }
}

String.prototype.hashCode = function () {
    var hash = 0,
        i, chr, len;
    if (this.length === 0) return hash;
    for (i = 0, len = this.length; i < len; i++) {
        chr = this.charCodeAt(i);
        hash = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
    }
    return hash;
};

function initDatePicker(elementId) {
    $('#' + elementId).datetimepicker({
        //language: 'uk',
        weekStart: 1,
        todayBtn: 1,
        autoclose: 1,
        todayHighlight: 1,
        startView: 2,
        minView: 2,
        forceParse: 0
    });
}

window.saveStrategyToDatabank = function (data) {
    console.log("Saving strategy from AlgoWizard to SQ", data);
    /* 
        $.post("http://" + window.location.hostname + ":" + window.location.port + "/resultsDatabankActions/saveStrategyXml", data,
            function (result, status) {
                console.log("Result: " + result + "\nStatus: " + status);
                var r = JSON.parse(result);

                if (r.success) {
                    sqNotices.addNotice('success', r.success);
                } else if (r.error) {
                    sqNotices.addNotice('error', r.error);
                }

                switchToApp(data.app);
            }); */

    $.ajax({
        url: "http://" + window.location.hostname + ":" + window.location.port + "/resultsDatabankActions/saveStrategyXml",
        type: 'post',
        crossDomain: true,
        data: data,
        headers: {
            "sq-auth-token": getAuthToken(),
        },
        dataType: 'json',
        success: function (result, status) {
            console.log("Result: ", result, "\nStatus: " + status);

            if (result.success) {
                sqNotices.addNotice('success', result.success);
            } else if (result.error) {
                sqNotices.addNotice('error', result.error);
            }

            switchToApp(data.app);
        }
    });
}

function addApp(app) {
    app.actions = [];
    window.apps.push(app);
}

function getApp(appCode) {
    if (!valueFilled(window.parent.apps)) return null;
    else return getItem(window.parent.apps, 'appCode', appCode);
}

function appsPreloaded() {
    for (var i = 0; i < window.parent.appsToPreload.length; i++) {
        var app = getItem(window.parent.apps, 'appCode', window.parent.appsToPreload[i]);
        if (!app) {
            console.error("App '" + window.parent.appsToPreload[i] + "' not found among window.parent.apps", window.parent.apps);
            return false;
        }

        if (app.appCode == 'AlgoWizard') continue;
        if (app.appCode == 'RESULTS') continue;

        if (!app.loaded) return false;
    }

    return true;
}

function isInitialized() {
    for (var i = 0; i < window.parent.apps.length; i++) {
        var app = window.parent.apps[i];

        if (app.appCode != 'AlgoWizard' && !app.loaded) {
            return false;
        }
    }
    return true;
}

function switchToApp(appCode) {
    var apps = window.parent.apps;
    window.parent.activeApp = appCode;

    console.log("Switching to app " + appCode + "...", apps);

    var curApp = null;

    for (var i = 0; i < apps.length; i++) {
        var app = apps[i];

        if (appCode == app.appCode) {

            curApp = app;

            var iframeSelector = '#' + app.iframeId;
            var linkSelector = '#' + app.linkId;

            $('.sq4-mainmenu>ul li a', top.document).removeClass('active');
            $('.sq4-mainframe', top.document).hide();

            var attr = $(iframeSelector, top.document).attr('src');

            // For some browsers, `attr` is undefined; for others,
            // `attr` is false.  Check for both.
            if (typeof attr === typeof undefined || attr === false) {
                $(iframeSelector, top.document).attr('src', app.url);
            }

            $(linkSelector, top.document).addClass('active');
            $(iframeSelector, top.document).show();

            var iframe = $(iframeSelector)[0];
            if (iframe) {
                var iframeWindow = iframe.contentWindow;
                setTimeout(function () {
                    iframeWindow.dispatchEvent(new Event('resize'));
                    iframeWindow.focus();
                }, 100);
            }

            $.ajax({
                url: "http://" + window.location.hostname + ":" + window.location.port + "/main/appSwitched",
                type: 'get',
                crossDomain: true,
                data: {
                    productCode: appCode,
                },
                headers: {
                    "sq-auth-token": getAuthToken()
                },
                dataType: 'json',
            });

            if (window.sendAppSwitchedEvent) {
                window.sendAppSwitchedEvent(appCode);
            } else {
                console.log("method not found");
            }

            break;
        }
    }

    if (appCode != 'BUILDER' && appCode != 'OPTIMIZER' && appCode != 'RETESTER' && appCode != 'TASKMANAGER' && appCode != "AlgoWizard") {
        showResults(false);
    } else if (appCode == "AlgoWizard") {
        if ("awShowResults" in top.document.getElementById(curApp.iframeId).contentWindow) {
            top.document.getElementById(curApp.iframeId).contentWindow.awShowResults(curApp.resultsShown);
        } else {
            showResults(false);
        }
    } else if (curApp) {
        showResults(curApp.resultsShown);
    }

    if (appCode == "SQXBUSINESS") {
        window.sq4bLoadInterval = setInterval(function () {
            if (top.document.getElementById(curApp.iframeId).contentWindow.appInited) {
                top.document.getElementById(curApp.iframeId).contentWindow.appInited();
                clearInterval(window.sq4bLoadInterval);
            }
        }, 200);
    }
}

function someProjectRunning() {
    var appObjs = window.parent.apps;

    for (var i = 0; i < appObjs.length; i++) {
        var appObj = appObjs[i];
        if (appObj.running) return true;
    }

    return false;
}

function isMainApp() {
    return window.appConfig.product == 'SQUANT' || window.appConfig.product == 'AlgoWizard' || window.appConfig.product == 'QDM' || window.appConfig.product == 'SQEDITOR';
}

function isAlgoWizard() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.product == 'AlgoWizard';
}

function isAlgoWizardCloud() {
    return window.top && window.top.aw_config_complete && window.top.aw_config_complete.algoWizardType == "Cloud";
}

function isStrategyQuant() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.product == 'SQUANT';
}

function isQuantDataManager() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.product == 'QDM';
}

function isMTAnalyzer() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.code == 'MTANALYZER';
}

function mainAppExists() {
    return window.parent && window.parent.appConfig && (window.parent.appConfig.product == "SQUANT" || window.parent.appConfig.product == "QDM");
}

function productHasDatabank() {
    return window.appConfig && (window.appConfig.product == 'BUILDER' || window.appConfig.product == 'RETESTER' || window.appConfig.product == 'OPTIMIZER' || window.appConfig.product == 'TASKMANAGER');
}

function InitializationData() {
    return angular.copy(isMainApp() ? window.InitializationDataObject : window.parent.InitializationDataObject);
}

function isCodeEditor() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.product == 'SQEDITOR';
}

function isPortfolioComposer() {
    return window.parent && window.parent.appConfig && window.parent.appConfig.product == 'PORTFOLIOCOMPOSER';
}




function objectToList(obj) {
    var data = [];

    for (var key in obj) {
        data.push({
            'name': key,
            'value': obj[key] + ""
        });
    }

    return data;
}

function checkActions(actions, callback) {
    for (var i = 0; i < actions.length; i++) {
        var action = actions[i];
        if (action.product == window.appConfig.product) {
            callback(action);
            actions.splice(i, 1);
            i--;
        }
    }
}

function getTFSeconds(tf) {
    if (tf == 'Intraday') return -1;
    if (tf == 'TICK') return 0;
    if (tf == 'Weekly') return 7 * 24 * 3600;
    if (tf == 'Monthly') return 4 * 7 * 24 * 3600;

    if (!valueFilled(tf) || typeof tf != "string" || tf.length < 2) {
        console.warn('Cannot get timeframe seconds count for timeframe "' + tf + '"');
        return 100000;
    }

    var prefix = tf.substr(0, 1);
    var value = parseInt(tf.substr(1, tf.length - 1));

    switch (prefix) {
        case 'S':
            return value;
        case 'M':
            return 60 * value;
        case 'H':
            return 3600 * value;
        case 'D':
            return 24 * 3600 * value;
        default:
            console.error("Unknown timeframe prefix '" + prefix + "'");
            return 100000000;
    }
}

function getNewTimeframe(importedTimeframe, wantedTimeframe) {
    if (wantedTimeframe == "Same as main data") return wantedTimeframe;

    if (!valueFilled(importedTimeframe)) return 'H1';
    if (!valueFilled(wantedTimeframe)) return getPriorityTF(importedTimeframe);

    var importedTF = getTFSeconds(importedTimeframe);
    var wantedTF = getTFSeconds(wantedTimeframe);

    if (importedTF <= wantedTF) return wantedTimeframe;
    else return getPriorityTF(importedTimeframe);
}

function getPriorityTF(importedTF) {
    var priorityTFs = ['H1', 'H4', 'D1'];

    for (var i = 0; i < priorityTFs.length; i++) {
        if (getTFSeconds(priorityTFs[i]) >= getTFSeconds(importedTF)) return priorityTFs[i];
    }

    return 'D1';
}

function toggleChildrenWatchers(element, pause) {
    angular.forEach(element.children(), function (childElement) {
        toggleAllWatchers(angular.element(childElement), pause);
    });
}

function toggleAllWatchers(element, pause) {
    var data = element.data();
    if (data.hasOwnProperty('$scope') && data.$scope.hasOwnProperty('$$watchers') && data.$scope.$$watchers) {
        if (pause) {
            data._bk_$$watchers = [];
            $.each(data.$scope.$$watchers, function (i, watcher) {
                data._bk_$$watchers.push($.extend(true, {}, watcher))
            });

            data.$scope.$$watchers = [];
        } else {
            if (data.hasOwnProperty('_bk_$$watchers')) {
                $.each(data._bk_$$watchers, function (i, watcher) {
                    data.$scope.$$watchers.push($.extend(true, {}, watcher))
                });
            }
        }

    }
    toggleChildrenWatchers(element, pause);
}

/**
 * Shows the end of the string 
 */
function limitString(string, maxPlaces, limitBeginning) {
    if (string.length > maxPlaces) {
        if (limitBeginning) {
            string = '...' + string.substr(string.length - maxPlaces + 3);
        } else {
            string = string.substr(string.length - maxPlaces, maxPlaces - 3) + "...";
        }
    }

    return string;
}
/**
 * Shows the beginning of the string 
 */
function limitString2(string, maxPlaces, endChar) {
    endChar = endChar || "...";

    if (string.length > maxPlaces) {
        return string.substr(0, maxPlaces) + endChar;
    }

    return string;
}

function removeLastChar(string) {
    if (string.length > 0) return string.substr(0, string.length - 1);
    return string;
}

function isPopupOpen(selector) {
    return $(selector).css('display') != 'none';
}

function httpGetAsync(theUrl, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function () {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200 && callback) {
            callback(xmlHttp.responseText);
        }
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous 
    xmlHttp.send(null);
}

function isEmptyObject(obj) {
    return !obj || Object.keys(obj).length === 0;
}

function getResultsWindow() {
    var $iframe = $("#iframe_results_overlayed", window.parent.document);

    if ($iframe && $iframe[0]) return $iframe[0].contentWindow;

    return null;
}

function awShowResults(show, injector) {
    var $iframe = $("#iframe_results_overlayed", window.parent.document);

    if (show) {
        $iframe.addClass("active");
        setTimeout(function() {
            if ($iframe[0]) {
                var iframeWindow =
                    $iframe[0].contentWindow || $iframe[0].contentDocument;
                iframeWindow.dispatchEvent(new Event("resize"));
            }
        }, 100);
    } else {
        $iframe.removeClass("active");
    }

    var activeApp = getItem(
        window.parent.apps,
        "appCode",
        window.parent.activeApp
    );
    if (activeApp != undefined) {
        activeApp.resultsShown = show;
    }

    if(injector) {
        let awEditor = injector.get("AlgoWizardService");
        if(awEditor) awEditor.editorAction("showResults", { show: show });
    }
}

function showResults(show) {
    var $iframe = $('#iframe_results_overlayed', window.parent.document);

    if (show) {
        $iframe.addClass("active");
        var newHeight = $('.split-pane-component-dashboard').height() - 32;
        if ($('.task-manager-menu').length > 0) {
            newHeight -= 30;
            $iframe.addClass('iframe_results_overlayed_tasks');
        } else {
            $iframe.removeClass('iframe_results_overlayed_tasks');
            $iframe.removeClass('iframe_results_overlayed_algowizard');
        }
        $iframe.css('height', newHeight + 'px');
    } else {
        $iframe.removeClass("active");
        $iframe.css('height', '');
    }

    var activeApp = getItem(window.parent.apps, 'appCode', window.parent.activeApp);
    if (activeApp != undefined) {
        activeApp.resultsShown = show;
    }
}

function implodeObjects(array, variable) {
    var str = "";

    for (var i = 0; i < array.length; i++) {
        var symbol = array[i][variable];
        str += symbol + ",";
    }

    return str.substr(0, str.length - 1);
}

function formatNumberWithCommas(x, useComma) {
    var parts = x.toString().split(".");
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, (useComma ? "," : "."));
    return parts.join(".");
}

function currentTimeMillis() {
    return new Date().getTime();
}

function getTaskInfoPanelSettings(elSettings, settingsPlugin, injector) {
    if (!settingsPlugin) {
        console.error("Cannot find settings tab plugin");
        return "N/A";
    }

    var elSettingsTab = getChildElement(elSettings, settingsPlugin.configElemName);
    return elSettingsTab && settingsPlugin.getSettingsDescription ? settingsPlugin.getSettingsDescription(elSettingsTab, injector) : "N/A";
}

function getMMDescription(elMoneyManagement, mmMethods, L) {
    var methodElems = getChildElements(elMoneyManagement, "Method");

    for (var i = 0; i < methodElems.length; i++) {
        if (getAttrBooleanValue(methodElems[i], "use", false)) {
            var methodType = getAttrStringValue(methodElems[i], "type");
            var method = getItem(mmMethods, "class", methodType);
            var description = method.description;

            if (!method || !description) return L ? L.tsq("N/A") : "N/A";

            description = L ? L.tsq(description) : description;

            var params = getMMMethodParams(methodElems[i]);
            var templateParts = description.split("#");
            var description = "";

            for (var a = 0; a < templateParts.length; a++) {
                var part = templateParts[a];
                var param = getItem(params, "key", part);
                description += param ? param.value : part;
            }

            return description;
        }
    }

    return "N/A";
}

function getMMMethodParams(elMethod) {
    var params = [];
    var elParams = getChildElement(elMethod, "Params");
    var paramElems = getChildElements(elParams, "Param");

    for (var i = 0; i < paramElems.length; i++) {
        var elParam = paramElems[i];
        params.push({
            key: getAttrValue(elParam, "key"),
            value: (elParam.childNodes[0] ? elParam.childNodes[0].nodeValue : "???")
        });
    }

    return params;
}

function isSessionNameValid(session) {
    var format = /^[a-zA-Z0-9_-]*$/;

    return format.test(session);
}

function isSymbolNameValid(symbol) {
    var format = /^[a-zA-Z0-9_@.:$]*$/;

    return format.test(symbol);
}

function sqDebounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this,
            args = arguments;
        var later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

function sqThrottle(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this,
            args = arguments;
        var later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

function formatIntNumber(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}

function Ltsq(text) {
    return text;
}

function Ltsqt(originalText , params) {
    let debug = window.top.langDebugMode;
    let translatedText = null;

    //nemozeme odstranit white spaces ako to bolo lebo sa nenamapuje preklad na original text
    //originalText = originalText ? originalText.trim().replace(/\s\s+/g, " ") : originalText; //remove multiple whitespaces

    if (window.top.commonData && originalText) {
        if ("Map" in window.top && (window.top.commonData.translations instanceof window.top["Map"])) {
            var translatedString = window.top.commonData.translations.get(originalText);
            if (translatedString != undefined) {
                translatedText = params ? translatedString.format(params) : translatedString;
            } else {
                var translatedString = window.top.commonData.translations.get(originalText.trim());
                if (translatedString != undefined) {
                    translatedText = params ? translatedString.format(params) : translatedString;
                }
            }
        }
        else {
            for (var i = 0; i < window.top.commonData.translations.length; i++) {
                var translation = window.top.commonData.translations[i];
                if (translation[0] == originalText || translation[0] == originalText.trim()) {
                    translatedText = params ? translation[1].format(params) : translation[1];
                }
            }
        }
    }

    if(!translatedText) {
        translatedText = params ? originalText.format(params) : originalText
    }

    if(debug) {
        translatedText="*"+translatedText+"*";
    }

    return translatedText;
}

String.prototype.format = function (args) {
    var str = this;
    var i = 0;
    return str.replace(String.prototype.format.regex, function (item) {
        var type = item.substr(item.length - 1);
        var secondChar = item.substring(1, 2);
        var decimals = secondChar == "." ? parseInt(item.substring(2, 3)) : -1;
        switch (type) {
            case "d":
                return parseInt(args[i++]);
            case "f":
                return trimToDecimalPlaces(args[i++], decimals > 0 ? decimals : 5);
            default:
                return args[i++];
        }
    });
};
String.prototype.format.regex = /%[0-9]?\.?[0-9]?[sdf]/g;

function getColumnType(type, plType) {
    switch (type) {
        case 'Decimal2':
            return 'float2';
        case 'Decimal4':
            return 'float5';
        case 'Decimal5':
            return 'float5';
        case 'Decimal2Pct':
            return 'percent';
        case 'Decimal2PL':
            switch (parseInt(plType)) {
                case 10:
                    return 'dollars';
                case 20:
                    return 'percent';
                case 30:
                    return 'pips';
                default:
                    return 'dollars';
            }
        case 'Decimal2Pips':
            return 'pips';
        case 'Integer':
            return 'integer';
        case 'Time':
            return 'dateTime';
        default:
            return 'text';
    }
}

function getSortType(type) {
    switch (type) {
        case 'Decimal2':
            return 'number';
        case 'Decimal4':
            return 'number';
        case 'Decimal5':
            return 'number';
        case 'Decimal2Pct':
            return 'number';
        case 'Decimal2PL':
            return 'number';
        case 'Decimal2Pips':
            return 'number';
        case 'Integer':
            return 'number';
        case 'Time':
            return 'number';
        default:
            return 'text';
    }
}

function sqTrottle(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this,
            args = arguments;
        var later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

function getAuthToken() {
    if (window.appConfig && window.appConfig.product == 'SQUANT') {
        return window.sqAuthToken;
    } else if (window.parent && window.parent.appConfig) {
        return window.parent.sqAuthToken;
    }
}

function countDecimals(value) {
    if (Math.floor(value) === value) return 0;
    return value.toString().split(".")[1].length || 0;
}

// Function to download data to a file
function createFileDownload(data, filename, type) {
    var file = new Blob([data], { type: type });
    if (window.navigator.msSaveOrOpenBlob) // IE10+
        window.navigator.msSaveOrOpenBlob(file, filename);
    else { // Others
        var a = document.createElement("a"),
            url = URL.createObjectURL(file);
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        setTimeout(function () {
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
        }, 0);
    }
}

function addLeadingZeros(num, size) {
    var s = num + "";
    while (s.length < size) s = "0" + s;
    return s;
}

function isChartParam(param) {
    return param.key == "#Chart#" || (param.paramType && param.paramType == "data");
}

function isBasketAlias(symbol) {
    return symbol.startsWith('[') && symbol.endsWith(']');
}

function cssId(text) {
    return text.replace(/^[^a-z0-9]+|[^\w-]+/gi, "-").toLowerCase();
}

class CustomPluginController {

    static index = 0;

    constructor(module, controllerProvider, callback){
        this.module = module;
        this.controllerProvider = controllerProvider;
        this.callback = callback;
    }

    init(){
        let instance = this;
        const controllerName = "CustomPluginController-" + (CustomPluginController.index++);

        angular.module("" + this.module).controller(controllerName, function ($scope, AppService, DatabankService) {
            $scope.onClick = (tab) => {
                if(!instance.callback){
                    console.error("Callback function not defined");
                    return;
                }
                instance.callback(AppService.getProject(), AppService.getTask().name, AppService.getDatabank().title, tab.getSelectedStrategies().split(","));
            }
        });

        this.registerController(this.module, controllerName);
 
        return controllerName;
    }

    registerController(moduleName, controllerName) {
        // Here I cannot get the controller function directly so I
        // need to loop through the module's _invokeQueue to get it
        var queue = angular.module("" + moduleName)._invokeQueue;
        for(var i=0;i<queue.length;i++) {
            var call = queue[i];
            if(call[0] == "$controllerProvider" && call[1] == "register" && call[2][0] == controllerName) {
                this.controllerProvider.register(controllerName, call[2][1]);
            }
        }
    }

    static sendRequest(url, method, data, callback){
        var xmlHttp = new XMLHttpRequest();

        xmlHttp.onreadystatechange = function() { 
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200){
                if(callback){
                    callback(xmlHttp.responseText);
                }
            }
        }

        xmlHttp.open(method || "GET", url, false); 
        xmlHttp.send(data);
    }

}

const FreeDataDisclaimer = Ltsqt("Please read and confirm Data Disclaimer for Free Data.");

function checkForEngine(engine, blockEngine) {
    if(!engine || !blockEngine) {
        return true;
    }
    
    if(!blockEngine.includes('-'+engine) && (blockEngine.includes(engine) || blockEngine.includes('*'))) {
        return true;
    }

    return false
}

function checkDirectiveReady(directiveInstance, directiveName, functionName) {
    if (!directiveInstance) {
        console.warn("Directive instance not available");
        return false;
    }
    if (!directiveInstance[functionName]) {
        console.warn("Directive '" + directiveName + "' function '" + functionName + "' not available");
        return false;
    }
    return true;
}