/****** fmt.base.js *******/ 

/* global safeText:false */
/**
 *
 */
var FMT = {};

FMT._cache = {};

FMT.NO_FIXED_PRECISION = -1;

// Legacy type for "Date/Time" data format which will be treated as Text when applying DST group/sort/filter.
FMT.type_dat = "dat";
// New type for "Date/Time" format which will be group/sort/filter as date values properly.
FMT.type_dat2 = "dat2";

FMT.DEFAULT_CURRENCY_CODE = "__DEFAULT__";

FMT.convertToNumber = function (nStr) {
    var isNegative = false;
    var num = Number.NaN;

    if (nStr == null || nStr.length == 0) return null;

    switch (typeof nStr) {
        case "number": return nStr;
        case "string": break;
        default: nStr = "" + nStr;
    }

    if (nStr.indexOf("-") == 0 || (nStr.indexOf("(") == 0 && nStr.lastIndexOf(")") == nStr.length - 1)) {
        isNegative = true;
    }

    if (nStr.indexOf(",") == -1) {
        num = parseFloat(nStr);
    } else {
        num = parseFloat(nStr.replace(/,/g,""));
    }

    if (_.isNaN(num)) {
        // attempt to extract numerics
        nStr = nStr.replace(/[^\d.]*/g, "");
        num = parseFloat(nStr);
        if (_.isNaN(num)) {
            return null;
        }
    }

    if (isNegative && num >0) {
        num = -num;
    }

    return num;
};

FMT.stripNbsp = function (str) {
    return (str == "&nbsp;") ? "" : str;
};


FMT.convertToString = function (num, precision, separator) {
    var nStr;
    var x, x1, x2, rgx;
    // separator is a two-character string where the first character is the radix and the second character
    // is the decimal point.  "*" represents empty string and "_" represents an empty space.
    var radix = ",";
    var decimalPoint = ".";

	if(num == Infinity) return "&infin;";
	if(num == -Infinity) return "-&infin;";

	if(separator) {
		radix = separator.charAt(0);
		if(radix == "*") radix = "";
		if(radix == "_") radix = " ";
		decimalPoint = separator.charAt(1);
	}

    if(precision && precision === FMT.NO_FIXED_PRECISION) {
        nStr = parseFloat(num.toFixed(6)) + "";
    } else {
        nStr = num.toFixed(precision);
    }
    x = nStr.split(".");
    x1 = x[0];
    x2 = x.length > 1 ? decimalPoint + x[1] : "";
	if(radix.length) {
		rgx = /(\d+)(\d{3})/;
		while (rgx.test(x1)) {
			x1 = x1.replace(rgx, "$1" + radix + "$2");
		}
	}

    return x1 + x2;
};

FMT.countChars = function (str, chaar, max) {
    var count = 0;
    var index = -1;
    while ((index = str.indexOf(chaar, index + 1)) != -1) {
        count++;
        if (max && (count >= max)) {
            return count;
        }
    }
    return count;
};

FMT.identifyFormat = function (data) {
    // check for numbers
    function guessLink (xData) {
        var format = "hyp";
        var dataEnding = xData.charAt(xData.length-3) + xData.charAt(xData.length-2) + xData.charAt(xData.length-1);
        switch (dataEnding.toLowerCase()) {
            case "png":
            case "jpg":
            case "gif":
            case "svg":
            case "bmp":
            case "ico":
                format = "img";
                break;
            default:
                break; //do nothing
        }
        return format;
    }

    if (data != null && /\d+/.test(data)) {
		if (typeof data == "number"  ) return "num";

        if (data.indexOf("/") == -1 && data.indexOf(":") == -1) {
            if (data.indexOf("$") != -1) {
                return "cur";
            } else if (data.indexOf("%") != -1) {
                return "pct";
            } else if (data.indexOf(",") != -1 && (this.countChars (data, ",", 5) == 5)) {
                return "spk";
            } else if (/\d+-.+-\d+/.test(data)) {
                if(FMT.parseDate(data) != FMT.INVALID_DATE) {
                    return FMT.type_dat;
                }

                return "txt";
            }
            return "num";
        } else if (data[0] == "/" || data.indexOf ("http") == 0){
            return guessLink (data);
        }
    } else if (data != null && (data[0] == "/" || data.indexOf ("http") == 0) ) {
        return guessLink (data);
    }

	if(FMT.parseDate(data) != FMT.INVALID_DATE) {
		return FMT.type_dat;
	}

	return "txt";
};

//Using formatter in highcharts
FMT.formatForHighcharts = function(fmtId, values_array, args) {
    return FMT.format(fmtId, values_array, args, true);
};

FMT.format = function(fmtId, values_array, args, isHighchartsFormatter) {
    var fmt = FMT._cache[fmtId];
    var mergedArgs = FMT.getMergedFmtArgs(args);
    if (!fmt) {
        if (!FMT[fmtId]) {
            //console.error("[FMT] Could not find format: " + fmtId);
            fmtId = "txt";
        }

        fmt = new FMT[fmtId];
        FMT._cache[fmtId] = fmt;
    }

    return fmt.format(values_array, mergedArgs, isHighchartsFormatter);
};

FMT.formatAffix = function(type, args, isHighchartsFormatter) {
    var affix = args[type] ? args[type] : "";
    var fontStyle = args[type + "FontStyle"];
    var fontColour = args[type + "FontColour"];
    var styledAffix;
    var affixClasses = "label-affix ";
    var affixStyle = "";
    var fontStyles = [];

    if (typeof fontStyle === "undefined" && typeof fontColour === "undefined") {
        fontStyle = args.fontStyle;
        fontColour = args.fontColour;
    }

    affix = safeText(affix, isHighchartsFormatter);

    if (affix && !args.disableStyles) {
        styledAffix = "<span";

        if (fontStyle) {
            fontStyles = fontStyle.split(",");

            fontStyles.forEach(function(fontStyle, i) {
                if (i > 0) {
                    affixClasses += " ";
                }

                affixClasses += "label-style-" + fontStyle;
            });
        }

        if (fontStyles.indexOf("bold") === -1) {
            affixClasses += (affixClasses !== "label-affix " ? " " : "") + "label-style-no_bold";
        }

        if (fontStyles.indexOf("italic") === -1) {
            affixClasses += (affixClasses !== "label-affix " ? " " : "") + "label-style-no_italic";
        }

        if (fontColour) {
            if (fontColour.indexOf("#") === -1) {
                affixClasses += (affixClasses !== "label-affix " ? " " : "") + fontColour;
            } else {
                affixStyle += "color:" + fontColour + ";";
            }
        }

        if (affixClasses !== "label-affix ") {
            styledAffix += " class=\"" + affixClasses + "\"";
        }

        if (affixStyle !== "") {
            styledAffix += " style=\"" + affixStyle + "\"";
        }

        styledAffix += ">" + affix + "</span>";

        affix = styledAffix;
    }

    return affix;
};

/**
 * Get the merged format args between the user set arg values and automatically detected arg values.
 * User set arg values takes precedence over the auto detected arg values.
 */
FMT.getMergedFmtArgs = function(args) {
    // When auto detected format args are available (when autoFmt=true), we will merge the arg values with user set args.
    if (args.autoFmt && args.autoFmt.fmtArgs) {
        return $.extend({},args.autoFmt.fmtArgs, args);
    } else {
        // If no auto detection, return the user set args.
        return args;
    }
};


FMT.base = $.klass({

    id : false,
    name : false,

    initialize : function() {

    },

    format : function(values_array, args) {

    }

});


jQuery.fn.outerHTML = function(s) {
    return (s)
        ? this.before(s).remove()
        : jQuery("<p>").append(this.eq(0).clone()).html();
};


FMT.toModelType = function(fmt){
    switch(fmt) {
        case FMT.type_dat2:
            // This new model type is not guarded isAutoFormatFeatureOn because if we are on PDPN and auto format feature
            // is off it also means VirtualColumn feature will be off. And this model type will NOT be used.
            return 3.0;
        case "num":
        case "cur":
        case "pct":
        case "dur":
        case "hrz":
            return 2.0;
        default:
            return 1.0;
    }
};

FMT.typeToDefaultFmt = function(type) {
    switch (type) {
        case 3.0:
            return FMT.type_dat2;
        case 2.0:
            return "num";
        case 1.0:
        default:
            return "txt";
    }
};

FMT.DEFAULT_CURRENCY = "usd";

/**
 * Given two potentially conflicting data format, resolve to a more suitable type so the data can be formatted properly.
 * This is mosly used to resolve data format for an axis when there are more than series sharing the axis and may or
 * may not have conflicting data format for those series.
 *
 * 1. For the same data format type, we will try to resolve potential conflicting sub-properties, such as currency.
 *      - When we have conflicting currency, we will set the currency symbol to "non", i.e. no currency symbol.
 * 2. For different data format type, if one of the data format is either Text or Date, we will resolve to the generic type "txt.
 * 3. For different data format type, if both type are numeric (such as one percentage and one currency) we will resolve to the generic type "num".
 */
FMT.resolveFormats = function(fmt1, fmt2) {
    var fmt = {fmt: fmt1.fmt, fmtArgs: fmt1.fmtArgs};

    if (fmt1.fmt == fmt2.fmt) {
        // When the 2 format type match
        if (fmt1.fmt == "cur" && FMT.getFormatArg(fmt1, "currency", FMT.DEFAULT_CURRENCY) != FMT.getFormatArg(fmt2, "currency", FMT.DEFAULT_CURRENCY)) {
            fmt.fmtArgs.currency = "non";
        }
    } else {
        if (FMT.isNumericType(fmt1.fmt) && FMT.isNumericType(fmt2.fmt)) {
            // If both types are numeric type, we will resolve to the type "num".
            fmt.fmt = "num";
        } else {
            // Otherwise, we default to the type "txt".
            fmt.fmt = "txt";
        }
    }

    return fmt;
};

/**
 * Get a format property value by its name. If property doesn't exist, return undefined.
 */
FMT.getFormatArg = function(fmt, name, defaultValue) {
    var value;
    if (fmt && fmt.fmtArgs && fmt.fmtArgs.hasOwnProperty(name)) {
        value = fmt.fmtArgs[name];
    }
    return value ? value : defaultValue;
};

FMT.isNumericType = function(fmt) {
    return 2.0 == FMT.toModelType(fmt);
};

FMT.updateMiniChartSizes = function (args) {
    var chartWidth = Number(args.width);
    switch (args.chart_size) {
        default:
        case "xx-small":
            args.height = 14;
            if (args.iconDisplayed) chartWidth -= 16;
            break;
        case "x-small":
            args.height = 17;
            if (args.iconDisplayed) chartWidth -= 22;
            break;
        case "small":
            args.height = 20;
            if (args.iconDisplayed) chartWidth -= 26;
            break;
        case "medium":
            args.height = 24;
            if (args.iconDisplayed) chartWidth -= 31;
            break;
        case "large":
            args.height = 32;
            if (args.iconDisplayed) chartWidth -= 44;
            break;
        case "x-large":
            args.height = 44;
            if (args.iconDisplayed) chartWidth -= 55;
            break;
        case "xx-large":
            args.height = 60;
            if (args.iconDisplayed) chartWidth -= 78;
            break;
    }
    args.width = Math.floor(chartWidth);
};
;/****** fmt.bulletchart.js *******/ 

/* global CXTheme:false, FMT:false, _sanitizeNumbers:false, sparklineNumberFormatter:false */
FMT.bulletchart = $.klass({

    defaultArgs : {
        width:120,
        height:18,
        type: "bullet",
        targetColor:"cx-theme_444",
        targetWidth: 4,
        performanceColor:"cx-theme_666",
        rangeColors:[],
        highlightLighten:1.00,
        tooltipOffsetX: 20,
        tooltipOffsetY: 20,
        tooltipFormat: "{{fieldkey:fieldNames}}: {{value}}",
        tooltipValueLookups: {
            fieldNames: { p:"Performance", r:"Range", t:"Target" }
        }
    },

    format : function(str,args) {
        var values;
        var i;
        var $sp;
        var $dashboard;

        if (typeof str == "object") {
            str = str[0];
        }

        if (typeof str == "string") {
            values = str.split(",");
        } else {
            values = str;
        }
        _sanitizeNumbers(values);

        args.rangeColors = [];
        args = $.extend({}, this.defaultArgs, args);

        args.targetColor = CXTheme.getThemeColour(args.targetColor);
        args.performanceColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.performanceColor));

        for (i = 0; i < args.rangeThemeColors.length; i++) {
            args.rangeColors.push(CXTheme.getThemeColour(args.rangeThemeColors[i]));
        }


        FMT.updateMiniChartSizes(args);

        args.numberFormatter = function(val){
            return sparklineNumberFormatter(val);
        };

        // HACK: This is a hack to fix very wide tooltips when display:flex on body
        $dashboard = $(".c-dashboard");
        if (KF.company.hasFeature("one_product_ui") && $dashboard.length > 0) {
            args.tooltipContainer = $dashboard;
        }

        $sp = $("<span>").appendTo(document.body);
        $sp.sparkline(values, args);

        return $sp;
    }

});

FMT.bch = FMT.bulletchart;;/****** fmt.currency.js *******/ 

/* global FMT:false */
FMT.currency = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var i;
        var num;
        var symbol;
        var nStr;
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {

            num = FMT.convertToNumber(values_array[i]);
            if (num == null) {
                if (values_array[i]) {
                    html += values_array[i];
                }
                continue;
            }

            switch (args.currency) {
                default:
                case "usd": symbol = "$"; break;
                case "gbp": symbol = "\u00a3"; break;
                case "eur": symbol = "\u20ac"; break;
                case "jpy": symbol = "\u00a5"; break;
                case "brl": symbol = "\u0052\u0024"; break;
                case "cup": symbol = "\u20b1"; break;
                case "dkk": symbol = "\u006b\u0072"; break;
                case "idr": symbol = "\u0052\u0070"; break;
                case "npr": symbol = "\u20a8"; break;
                case "ang": symbol = "\u0192"; break;
                case "fra": symbol = "\u0046\u0072"; break;
                case "trl": symbol = "\u20a4"; break;
                case "aed": symbol = "د.إ"; break;
                case "cny": symbol = "\u00a5"; break;
                case "myr": symbol = "\u0052\u004d"; break;
                case "kwd": symbol = "ك"; break;
                case "non": symbol = ""; break;
            }

            if (num < 0) {
                nStr = prefix + (args.paren == "cur" ? "(" : "-") + symbol + FMT.convertToString((num * -1), parseInt(args.precision)) + (args.pct == "active" ? "%" : "") + (args.paren == "cur" ? ")" : "") + suffix;
            } else {
                nStr = prefix + symbol + FMT.convertToString(num, parseInt(args.precision), args.separator) + (args.pct == "active" ? "%" : "") + suffix;
            }

            html += nStr;
        }
        return (html.length?html:"&nbsp;");
    }

});

FMT.cur = FMT.currency;;/****** fmt.date.js *******/ 

/* global FMT:false, safeText:false */
FMT.date = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var dString = values_array[0].toString();
        var pDate;
        var epoch = 0;
        var inputFormat = undefined;
        var DEFAULT_FORMAT = "dddd, MMMM d, yyyy"; //Don't use pr.defaults constant as PDF can't pick it up
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        // If we received formula result metadata from DPN and it contains the flag ignoreDateFormat, we will skip formatting.
        if (args.resultMetadata && args.resultMetadata.ignoreDateFormat) {
            return safeText("" + dString, isHighchartsFormatter);
        }

        if(args.dateInputFormat != "default"){
            if(args.dateInputFormat == "custom")inputFormat = args.dateInputFormatCustom;
            else inputFormat = args.dateInputFormat;
        }
        pDate = FMT.parseDate(dString, inputFormat);
        if (pDate !== FMT.INVALID_DATE) {
            if (pDate != null) {
                if (args.dateFormat) {
                    if (args.dateFormat == "custom") {
                        if (args.dateFormatCustom) {
                            html += pDate.toString(args.dateFormatCustom);
                        } else {
                            html += pDate.toString(DEFAULT_FORMAT);
                        }
                    } else {
                        html += pDate.toString(args.dateFormat);
                    }
                } else {
                    html += pDate.toString(DEFAULT_FORMAT);
                }
                epoch = pDate.getTime();
            }
            if (pDate == null || isNaN(pDate.getTime())) {
                return safeText("" + dString, isHighchartsFormatter);
            } else {
                return prefix + "<span epoch=\"" + epoch + "\">" + (html.length ? html : "&nbsp;") + "</span>" + suffix;
            }
        } else {
            return safeText("" + dString, isHighchartsFormatter);
        }
    }

});

// alias
FMT.dat = FMT.date;


FMT.parseDate = function parseDate(dString, formatAs) {
    var pDate;

    //Code to catch twitter date format
    if (dString) {
        dString = dString + ""; // always convert to a string
        if (dString.match(/[A-Za-z]{3} [A-Za-z]{3} [0-9]{1,2} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2} \+[0-9]{3,4} [0-9]{4}/)) {
            //Strip day of week to make it parsable by datejs
            dString = dString.substring(4);
        } else if (dString.match(/[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}T[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}\.[0-9]{3}(Z|((\+|-)[0-9]{1,2}:[0-9]{1,2}))/)) {
            //strip out milliseconds
            dString = dString.replace(/\.[0-9]{3}/, "");
        }

        if (formatAs) {
            pDate = Date.parseExact(dString, formatAs);
        } else {
            pDate = Date.parse(dString);
        }

        if (pDate == null) {
            pDate = new Date(Number(dString));
        }
    } else {
        return FMT.INVALID_DATE;
    }

    return pDate;
};


/**
 * Determine whether the data contains epoch values (integers). As well as trying to detect whether it's in milliseconds or seconds format.
 */
FMT.parseEpochDate = function parseEpochDate(values) {
	var firstInt = FMT.getFirstIntegerValue(values);
	var maxYear, dateAsEpochMs;
	// First epoch value has to be in integer format.
	if (firstInt) {
		maxYear = new Date().getFullYear() + 10;
		// First assume the value is in seconds format and create a date object based on that.
		dateAsEpochMs = new Date(firstInt * 1000);
		if (dateAsEpochMs.getFullYear() > maxYear) {
			// If this time stamp is to far ahead of current year, then it less likely to be in seconds format, return ms format.
			return "epoch-ms";
		} else {
			// Otherwise return the epoch seconds format.
			return "epoch";
		}
	}
	return false;
};

/**
 * Get the first integer value from an array of values and skipping header and empty values.
 */
FMT.getFirstIntegerValue = function getFirstIntegerValue(values) {
	var i;
	var re = /^-?\d+$/; // Regex for integer values (positive or negative) which represents a epoch timestamp.
	if (values && values.length > 0) {
		// Iterates through the values, ignoring header or empty string and return the first value if it's integer.
		for (i = 0; i < values.length; i++) {
			if (typeof(values[i]) == "number") {
				// If it's already a number, return.
				return values[i];
			} else if (values[i] && values[i].trim().length > 0) {
				if (values[i].match(re)) {
					return parseInt(values[i]);
				} else {
					// This check for i > 1 is allow skipping the header row. If we hit a non-empty value
					// after the header row and it's NOT a integer, return false. This is not a epoch value.
					if (i > 1) {
						return false;
					}
				}
			}
		}
	}
	return false;
};

/**
 * Convert date.js format patterns to Java patterns.
 */
FMT.convertDateJsFormatToJava = function convertDateJsFormatToJava(format) {
    var converted = format;
    var i, ch;
    var result = "";
    if (converted) {
        // See https://github.com/datejs/Datejs for Date.js patterns.
        converted = converted.trim();
        // First we handle the single character shorthand Date.js have and expand them to full format.
        if (converted === "d") {
            result = "M/d/yyyy";
        } else if (converted === "D") {
            result = "EEEE, MMMM dd, yyyy";
        } else if (converted === "F") {
            result = "EEEE, MMMM dd, yyyy h:mm:ss a";
        } else if (converted === "m") {
            result = "MMMM dd";
        } else if (converted === "r") {
            result = "EEE, dd MMM yyyy HH:mm:ss";
        } else if (converted === "s") {
            result = "yyyy-MM-ddTHH:mm:ss";
        } else if (converted === "t") {
            result = "h:mm a";
        } else if (converted === "T") {
            result = "h:mm:ss a";
        } else if (converted === "u") {
            result = "yyyy-MM-dd HH:mm:ssX";
        } else if (converted === "y") {
            result = "MMMM, yyyy";
        } else {
            // Capturing the case where existing String is quoting T already (a special case).
            converted = converted.replace("'T'", "T");
            // If we have single quote which should be used as string literal, we will replace it with !! and handle it later.
            converted = converted.split("'").join("!!");
            // Now we convert Date.js pattern to match Java pattern. Date.js has a few reserved string for patterns.
            // Anything else will be treated as string literal. In Java pattern we need to quote string literals.
            for (i = 0; i < converted.length; i++) {
                ch = converted.charAt(i);
                if (ch.match(/\w/) && ch.match(/[^smhHdMytSZ]/)) {
                    result += "'" + ch + "'";
                } else {
                    result += ch;
                }
            }
            // When we have multiple string literal adjacent, we need to remove the duplicated quotes.
            result = result.split("''").join("");
            // Now we change the previous single quote which should be used literally back to Java single quote escape pattern ''
            result = result.split("'!!'").join("''");
            result = result.split("!!").join("''");
            // Now we do the normal replacement for different date pattern between date.js to Java.
            result = result
                .replace("tt", "a")
                .replace("t", "a")
                .replace("dddd", "EEEE")
                .replace("ddd", "EEE")
                .replace("Z", "X");
        }
    }
    return result;
};


FMT.isDateFormat = function isDateFormat(format) {
    return format == FMT.type_dat || format == FMT.type_dat2;
};


FMT.DATE_INPUT_FORMAT_CUSTOM = "custom";
FMT.DATE_INPUT_FORMAT_GOOGLE_ANALYTICS = "yyyyMMdd";
FMT.INVALID_DATE = "Invalid Date";
;/****** fmt.discrete.js *******/ 

/* global FMT:false, _sanitizeNumbers:false, CXTheme:false */
FMT.discrete = $.klass({

    defaultArgs : {
        width:60,
        height:18,
        type: "discrete",
        barHeight:"auto",
        customBarHeight:"",
        discreteThreshold:"",
        discreteBarColor: "cx-theme_blue_3",
        discreteThresholdColor: "cx-theme_red_3",
        chartRangeOption:"auto",
        minCustomVal:"",
        maxCustomVal:"",
        tooltipOffsetX: 20,
        tooltipOffsetY: 20
    },

    format : function(str,args) {
        var values;
        var $sp;
        var $dashboard;

        if (typeof str == "object") {
            str = str[0];
        }

        if (typeof str == "string") {
            values = str.split(",");
        } else {
            values = str;
        }
        _sanitizeNumbers(values);


        args = $.extend({}, this.defaultArgs, args);

        args.lineColor = CXTheme.getThemeColour(args.discreteBarColor);

        if (args.discreteThreshold != "") {
            args.thresholdValue = parseFloat(args.discreteThreshold);
            args.thresholdColor = CXTheme.getThemeColour(args.discreteThresholdColor);
        }

        if (args.chartRangeOption == "cust" && args.minCustomVal != "") args.chartRangeMin = FMT.convertToNumber(args.minCustomVal);
        if (args.chartRangeOption == "cust" && args.maxCustomVal != "") args.chartRangeMax = FMT.convertToNumber(args.maxCustomVal);

        FMT.updateMiniChartSizes(args);

        if (args.barHeight == "cust" && args.customBarHeight != "") {
            args.lineHeight = args.height * (parseFloat(args.customBarHeight) / 100);
        }

        // HACK: This is a hack to fix very wide tooltips when display:flex on body
        $dashboard = $(".c-dashboard");
        if (KF.company.hasFeature("one_product_ui") && $dashboard.length > 0) {
            args.tooltipContainer = $dashboard;
        }

        $sp = $("<span>").appendTo(document.body);
        $sp.sparkline(values,args);

        return $sp;
    }

});

FMT.dsc = FMT.discrete;
;/****** fmt.duration.js *******/ 

/* global FMT:false */
FMT.duration = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var isNegative;
        var totalSeconds;
        var rounder;
        var totalDays;
        var hours;
        var totalHours;
        var minutes;
        var totalMinutes;
        var seconds;
		var i;
        var durFormat = "";
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {
            totalSeconds = FMT.convertToNumber(values_array[i]);
            if (totalSeconds == null) {
                continue;
            }

            isNegative = totalSeconds < 0;
            totalSeconds = Math.abs(totalSeconds);

            rounder = Math.floor;

            totalDays = totalSeconds / 86400;
            hours = (totalSeconds % 86400) / 3600;
            totalHours = totalSeconds / 3600;
            minutes = (totalSeconds % 3600) / 60;
            totalMinutes = totalSeconds / 60;
            seconds = totalSeconds % 60;

            if(args.durationFormat) {
                durFormat = args.durationFormat;
            }

            switch(durFormat) {
                case "mm:ss":
                default:
                    html += rounder(totalMinutes) + "m:" + FMT.convertToString(seconds, parseInt(args.precision)) + "s";
                    break;

                case "mm":
                    html += FMT.convertToString(totalMinutes, parseInt(args.precision)) + "m";
                    break;

                case "ss":
                    html += FMT.convertToString(totalSeconds, parseInt(args.precision)) + "s";
                    break;

                case "hh:mm:ss":
                    html += rounder(totalHours) + "h:" + rounder(minutes) + "m:" + FMT.convertToString(seconds, parseInt(args.precision)) + "s";
                    break;

                case "hh:mm":
                    html += rounder(totalHours) + "h:" + FMT.convertToString(minutes, parseInt(args.precision)) + "m";
                    break;

                case "hh":
                    html += FMT.convertToString(totalHours, parseInt(args.precision)) + "h";
                    break;

                case "dd:hh:mm:ss":
                    html += rounder(totalDays) + "d:" + rounder(hours) + "h:" + rounder(minutes) + "m:" + FMT.convertToString(seconds, parseInt(args.precision)) + "s";
                    break;

                case "dd:hh:mm":
                    html += rounder(totalDays) + "d:" + rounder(hours) + "h:" + FMT.convertToString(minutes, parseInt(args.precision)) + "m";
                    break;

                case "dd:hh":
                    html += rounder(totalDays) + "d:" + FMT.convertToString(hours, parseInt(args.precision)) + "h";
                    break;

                case "dd":
                    html += FMT.convertToString(totalDays, parseInt(args.precision)) + "d";
                    break;
            }

            if (isNegative) {
                html = "-" + html;
            }
        }

        html = prefix + html + suffix;

        return (html.length ? html : "&nbsp;");
    }
});

// alias
FMT.dur = FMT.duration;
;/****** fmt.horizontal.js *******/ 

/* global FMT:false, G_vmlCanvasManager:false, CXTheme:false */
FMT.horizontal = $.klass({

    defaultArgs : {
        width:120,
        height:18,
        posBarColor:"cx-theme_blue_3",
        negBarColor:"cx-theme_red_3",
        horizRangeColor:"cx-theme_ccc",
        chartRangeOption:"auto",
        minCustomVal:"",
        maxCustomVal:""
    },

    format : function(str,args) {
        var performance;
        var values;
        var $sp;
        var $canvas;
        var ctx;
        var canvas;
        var min, max;
        var zeroWidth;
        var origin;
        var startPos, endPos, zeroPos;

        if (typeof str == "object") {
            str = str[0];
        }

        if (typeof str == "string") {
            values = str.split(",");
            performance = values.length > 0 ? values[0] : 0;
        } else if (typeof str == "object") {
            performance = str.length > 0 ? str[0] : 0;
        } else {
            performance = str;
        }
        performance = FMT.convertToNumber(performance);

        args = $.extend({}, this.defaultArgs, args);

        FMT.updateMiniChartSizes(args);

        $sp = $("<span>").appendTo(document.body);

        $canvas = $("<canvas>")
            .attr("title", "Value: " + performance)
            .text("your browser does not support the canvas tag")
            .css({
                display: "inline-block",
                width: args.width + "px",
                height: args.height + "px",
                "vertical-align": "top"
            });
        $sp.append($canvas);
        canvas = $($canvas)[0];
        if ( window.G_vmlCanvasManager != undefined ) G_vmlCanvasManager.initElement(canvas);

        canvas.width = args.width;
        canvas.height = args.height;

        ctx = canvas.getContext("2d");

        min = (args.minVal < 0 ? args.minVal : 0);
        max = (args.maxVal > 0 ? args.maxVal : 0);

        if (args.chartRangeOption == "cust" && args.minCustomVal != "") min = FMT.convertToNumber(args.minCustomVal);
        if (args.chartRangeOption == "cust" && args.maxCustomVal != "") max = FMT.convertToNumber(args.maxCustomVal);

        zeroWidth = 3;
        origin = {x:0, y:0};

        // range
        if (!args.hideRange) {
            ctx.globalAlpha = 1;
            ctx.fillStyle = CXTheme.getThemeColour(args.horizRangeColor);
            ctx.lineWidth = 1;

            ctx.fillRect(origin.x, origin.y, args.width, args.height);
        }

        // performance
        if (performance != null) {
            ctx.globalAlpha = 1;
            ctx.fillStyle = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(performance < 0 ? args.negBarColor : args.posBarColor));
            ctx.lineWidth = 1;

            startPos = this.valueToPosition(args.width , Math.max(0, min), min, max);
            endPos = this.valueToPosition(args.width, Math.min(performance, max), min, max);

            ctx.fillRect(origin.x + startPos, origin.y, endPos - startPos, args.height);
        }

        // zero bar
        if (performance < 0 || min < 0) {
            ctx.globalAlpha = 1;
            ctx.strokeStyle = CXTheme.current.cx_mini_series_zero;
            ctx.lineWidth = zeroWidth;

            zeroPos = this.valueToPosition(args.width, 0, min, max);
            if (zeroPos == args.width) zeroPos -= zeroWidth / 2;

            ctx.beginPath();
            ctx.moveTo(origin.x + zeroPos, 0);
            ctx.lineTo(origin.x + zeroPos, args.height);
            ctx.stroke();
        }

        return $sp;
    },

    /**
     * Determine the position of the val along the given length
     *
     * @param length
     * @param val
     * @param min
     * @param max
     * @return {Number}
     */
    valueToPosition : function(length, val, min, max) {
        return length * ((val - min) / (max - min));
    }

});

FMT.hrz = FMT.horizontal;
;/****** fmt.hyperlink.js *******/ 

/* global FMT:false */
FMT.hyperlink = $.klass({

    format : function(val,args) {
        var linkText;
        var hrefVal;
        var $div;
        var $link;

        if ( !val || val=="&nbsp;" || val == 0 ) return "";

        //if the link is in the format [text]|[hyperlink] use [text] as the link text
        if(val.toString().indexOf("|") != -1){
            linkText = val.toString().substring(0,val.toString().indexOf("|"));
            hrefVal = val.toString().substring(val.toString().indexOf("|")+1);
        } else {
            //other use url
            hrefVal = val.toString();
            linkText = val.toString();
        }

        $div = $("<div>");
        if(isFinite(Number(hrefVal))){
            hrefVal = "http://" + hrefVal;
        }
        $link = $("<a>").attr("href",hrefVal);

        if(args.hypFixedText){//overide all link text with given text
            $link.text(args.hypFixedText);
        } else {
            //otherwise use the url
            $link.text(linkText);
        }

        if(args.hypSameTab){//if checked open link in the same window as the app
            $link.attr("target","_self");
        } else {
            //default is open in new tab/window
            $link.attr("target","_blank");
        }

        $link.appendTo($div);

        return $div.html();
    }

});

FMT.hyp = FMT.hyperlink;
;/****** fmt.image_src.js *******/ 

/* global FMT:false */
FMT.image_src = $.klass({

    format : function(val,args) {
        var $div = $("<div>");
        var $img = $("<img>").attr("src",val).addClass("table-col-img");
        var $span;

        var constrained = false;

        if ( !val|| val=="&nbsp;" || val == 0) return "";

		if(args.scale == "fd"){
			if(args.imgFixHeight && args.imgFixHeight != ""){
				$img.attr("height",args.imgFixHeight);
                constrained = true;
			} else {
                $img.css("height", "auto");
            }
			if(args.imgFixWidth && args.imgFixWidth != ""){
				$img.attr("width",args.imgFixWidth);
                constrained = true;
			} else {
                $img.css("width", "auto");
            }
		} else if(args.scale == "md"){
			if(args.imgMaxHeight && args.imgMaxHeight != ""){
				$img.css("max-height",args.imgMaxHeight+"px");
                constrained = true;
			}
			if(args.imgMaxWidth && args.imgMaxWidth != ""){
				$img.css("max-width",args.imgMaxWidth+"px");
                constrained = true;
			}

            $img.css("height", "auto");
            $img.css("width", "auto");
		}

        if (constrained) {
            $img.addClass("constrained-size");
        }
        $span = $("<span>").css("display","none");
        $span.text(""+val);
        $img.appendTo($div);
        $span.appendTo ($div);


        return $div.html();
    }

});

FMT.img = FMT.image_src;;/****** fmt.number_with_commas.js *******/ 

/* global FMT:false, safeText:false */
FMT.number_with_commas = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var i;
        var num;
        var nStr;
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {

            num = FMT.convertToNumber(values_array[i]);
            if (num == null) {
                html += safeText (values_array[i], isHighchartsFormatter);
                continue;
            }
            nStr = prefix + FMT.convertToString(num, parseInt(args.precision), args.separator) + (args.pct == "active" ? "%" : "") + suffix;

            html += nStr;
        }
        return (html.length?html:"&nbsp;");
    }

});

FMT.num = FMT.number_with_commas;;/****** fmt.percentage.js *******/ 

/* global FMT:false */
FMT.percentage = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var str;
        var num;
        var nStr;
        var i;
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {
            str = values_array[i];
            num = FMT.convertToNumber(values_array[i]);
            if (num == null) {
                html += str;
                continue;
            }
            num *= 100;
            nStr = prefix + FMT.convertToString(num, parseInt(args.precision), args.separator) + "%" + suffix;
            html += nStr;
        }
        return (html.length?html:"&nbsp;");
    }

});

FMT.pct = FMT.percentage;;/****** fmt.raw.js *******/ 

/* global FMT:false */
FMT.raw = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var i;
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {
            html += (html.length?",":"") + prefix + values_array[i] + suffix;
        }
		return (html.length?html:"&nbsp;");
    }

});

;/****** fmt.sparkbar.js *******/ 

/* global CXTheme:false, FMT:false, _sanitizeNumbers:false, sparklineNumberFormatter:false */
FMT.sparkbar = $.klass({

	defaultArgs: {
		width: 60,
		height: 18,
		type: "bar",
		barWidth: 4,
		barSpacing: 1,
		posBarColor: "cx-theme_blue_3",
		zeroBarColor: "cx-theme_aaa",
		negBarColor: "cx-theme_red_3",
		chartRangeOption: "auto",
		minCustomVal: "",
		maxCustomVal: "",
		highlightLighten: 1.17,
		tooltipOffsetX: 20,
		tooltipOffsetY: 20
	},

	format: function (str, args) {
        var values;
		var barWidth;
		var $sp;
		var $dashboard;

		if (typeof str == "object") {
			str = str[0];
		}

		if (typeof str == "string") {
			values = str.split(",");
		} else {
			values = str;
		}
		_sanitizeNumbers(values);


		args = $.extend({}, this.defaultArgs, args);

		args.barColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.posBarColor));
		args.zeroColor = CXTheme.getThemeColour(args.zeroBarColor);
		args.negBarColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.negBarColor));

		if (args.chartRangeOption == "cust" && args.minCustomVal != "") args.chartRangeMin = FMT.convertToNumber(args.minCustomVal);
		if (args.chartRangeOption == "cust" && args.maxCustomVal != "") args.chartRangeMax = FMT.convertToNumber(args.maxCustomVal);


        FMT.updateMiniChartSizes(args);

		if (values.length > args.width) values.length = Math.max(0, Math.floor(args.width));

		barWidth = Math.floor(args.width / values.length);
		if (barWidth < 1) barWidth = 1;

		args.barWidth = barWidth - 1;

		args.numberFormatter = function (val) {
			return sparklineNumberFormatter(val);
		};

        // HACK: This is a hack to fix very wide tooltips when display:flex on body
        $dashboard = $(".c-dashboard");
        if (KF.company.hasFeature("one_product_ui") && $dashboard.length > 0) {
            args.tooltipContainer = $dashboard;
        }

		$sp = $("<span>").appendTo(document.body);
		$sp.sparkline(values, args);

		return $sp;
	}

});

FMT.spb = FMT.sparkbar;;/****** fmt.sparkline.js *******/ 

/* global CXTheme:false, FMT:false, _sanitizeNumbers:false, sparklineNumberFormatter:false */
FMT.sparkline = $.klass({

	defaultArgs: {
		width: 60,
		height: 18,
		type: "line",
		chartLineFormat: "la",
		includeHighLowPoints: false,
		lineWidth: 2,
		spotRadius: 2.5,
		lineColor: "cx-theme_blue_3",
		spotColor: "cx-theme_blue_3",
		fillColor: CXTheme.current.cx_sparkline_area,
		minSpotColor: "cx-theme_red_3",
		maxSpotColor: "cx-theme_red_3",
		chartRangeOption: "auto",
		minCustomVal: "",
		maxCustomVal: "",
		highlightSpotColor: "#5290e9",
		highlightLineColor: "#d7d7d7",
		tooltipOffsetX: 20,
		tooltipOffsetY: 20
	},

	format: function (str, args) {
        var values;
		var $sp;
		var $dashboard;

		if (typeof str == "object") {
			str = str[0];
		}

		if (typeof str == "string") {
			values = str.split(",");
		} else {
			values = str;
		}
		_sanitizeNumbers(values);


		args = $.extend({}, this.defaultArgs, args);

		args.lineColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.lineColor));
		args.spotColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.spotColor));
		args.fillColor = (args.chartLineFormat == "la" ? CXTheme.getThemeColour(args.fillColor) : false);
		args.minSpotColor = (args.includeHighLowPoints ? CXTheme.getThemeColour(args.minSpotColor) : false);
		args.maxSpotColor = (args.includeHighLowPoints ? CXTheme.getThemeColour(args.maxSpotColor) : false);

		if (args.fillColor && (args.fillColor.toLowerCase() == CXTheme.themes["default"].cx_sparkline_area.toLowerCase() || args.fillColor.toLowerCase() == CXTheme.themes["dark"].cx_sparkline_area.toLowerCase())) {
			args.fillColor = CXTheme.current.cx_sparkline_area;
		}

		args.highlightSpotColor = args.spotColor;

		if (args.chartRangeOption == "cust" && args.minCustomVal != "") args.chartRangeMin = FMT.convertToNumber(args.minCustomVal);
		if (args.chartRangeOption == "cust" && args.maxCustomVal != "") args.chartRangeMax = FMT.convertToNumber(args.maxCustomVal);


		FMT.updateMiniChartSizes(args);

		args.numberFormatter = function (val) {
			return sparklineNumberFormatter(val);
		};

		// HACK: This is a hack to fix very wide tooltips when display:flex on body
		$dashboard = $(".c-dashboard");
		if (KF.company.hasFeature("one_product_ui") && $dashboard.length > 0) {
			args.tooltipContainer = $dashboard;
		}

		$sp = $("<span>").appendTo(document.body);
		$sp.sparkline(values, args);

		return $sp;
	}

});

FMT.spk = FMT.sparkline;;/****** fmt.text.js *******/ 

/* global FMT:false, safeText:false */
FMT.text = $.klass({

    format : function(values_array, args, isHighchartsFormatter) {
        var html = "";
        var i;
        var prefix;
        var suffix;

        if (!args) args = {};

        prefix = FMT.formatAffix("prefix", args, isHighchartsFormatter);
        suffix = FMT.formatAffix("suffix", args, isHighchartsFormatter);

        for (i = 0; i < values_array.length; ++i) {
            html += (html.length?",":"") + prefix + safeText(values_array[i], isHighchartsFormatter) + suffix;
        }
		return (html.length?html:"&nbsp;");
    }

});

// alias
FMT.txt = FMT.text;
FMT.tx = FMT.text;
FMT.dat2 = FMT.text;;/****** fmt.winloss.js *******/ 

/* global FMT: false, _sanitizeNumbers: false, CXTheme: false, sparklineNumberFormatter: false */

FMT.winloss = $.klass({

    defaultArgs : {
        width:60,
        height:18,
        type: "tristate",
        winThreshold:"",
        barWidth: 4,
        barSpacing: 1,
        winColor:"cx-theme_blue_3",
        drawColor:"cx-theme_aaa",
        lossColor:"cx-theme_red_3",
        highlightLighten:1.17,
        tooltipOffsetX: 20,
        tooltipOffsetY: 20
    },

    format : function(str,args) {
        var values;
        var tv;
        var $x;
        var barWidth;
        var $sp;
        var $dashboard;

        if (typeof str == "object") {
            str = str[0];
        }

        if (typeof str == "string") {
            values = str.split(",");
        } else {
            values = str;
        }
        _sanitizeNumbers(values);


        args = $.extend({},this.defaultArgs,args);

        args.posBarColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.winColor));
        args.negBarColor = (args.reactionColour && args.reactionColour.length > 0 ? args.reactionColour : CXTheme.getThemeColour(args.lossColor));
        args.zeroBarColor = CXTheme.getThemeColour(args.drawColor);

        tv = parseFloat(args.winThreshold && args.winThreshold != "" ? args.winThreshold : 0);
        for ($x = 0; $x < values.length; $x++) {
            if (values[$x] == tv) {
                values[$x] = 0;
            } else if (values[$x] > tv) {
                values[$x] = 1;
            } else {
                values[$x] = -1;
            }
        }


        FMT.updateMiniChartSizes(args);

        if (values.length > args.width) values.length = Math.floor(args.width);

        barWidth = Math.floor(args.width / values.length);
        if(barWidth < 1) barWidth = 1;

        args.barWidth = barWidth - 1;

        args.numberFormatter = function(val){
            return sparklineNumberFormatter(val);
        };

        // HACK: This is a hack to fix very wide tooltips when display:flex on body
        $dashboard = $(".c-dashboard");
        if (KF.company.hasFeature("one_product_ui") && $dashboard.length > 0) {
            args.tooltipContainer = $dashboard;
        }

        $sp = $("<span>").appendTo(document.body);
        $sp.sparkline(values, args);

        return $sp;
    }

});

FMT.wlc = FMT.winloss;;