Engine.Utils = class
{

    /// <summary>
    /// Current Time in MS as LONG
    /// </summary>
    /// <returns></returns>
    static getTimer()
    {
        var date = new Date();
        return date.getTime();
    }

    static initializeServerTime(serverTimeInSeconds)
    {
        UtilsStatics.serverTimeOffset = serverTimeInSeconds - Engine.Utils.nowTime();
    }

    static nowTimeServer()
    {
        return Engine.Utils.nowTime() + UtilsStatics.serverTimeOffset;
    }

    static getValueOrDefault(nullable, defaultValue)
    {
        if (nullable === undefined || nullable == null) return defaultValue;
        return nullable;
    }

    /// <summary>
    /// Current Time in Seconds as int
    /// </summary>
    /// <returns></returns>
    static nowTime()
    {
        var date = new Date();
        return date.getTime() / 1000;
    }
	
    static parseBigNumberString(number)
    {
		var match = UtilsStatics.bigNumberRegex.exec(number);
        var magnitude = 0;
        var thousands = 0;
        var hundreds = 0;
        if (number.indexOf('e') != -1 || number.indexOf('E') != -1)
        {
            return parseFloat(number);
        }
        if (match)
        {
            var mag = match[3];
            magnitude = UtilsStatics.symbols.indexOf(mag);
            if (match[1])
            {
                thousands = parseInt(match[1]);
                if (match[2] && match[2] != "")  
                { // if numbers are split by comma, multiply by 1000
                    thousands *= 1000;
                }
            }
            else
            {
                thousands = 0;
            }
            hundreds = match[2] && match[2] != "" ? parseInt(match[2]) : 0;
        }

        return (thousands + hundreds) * Math.pow(1000, magnitude + 1);
    }

    static formatCountdown(timeRemaining, padHours)
    {
        if (padHours === undefined) padHours = true;

        var cooldownRemaining = Math.floor(timeRemaining);
        var hours = Math.floor(cooldownRemaining) / (60 * 60);
        cooldownRemaining -= hours * 60 * 60;

        var mins = Math.floor(cooldownRemaining) / 60;
        cooldownRemaining -= mins * 60;

        var seconds = Math.floor(cooldownRemaining);

        var hoursStr = hours > 0 ? hours + ":" : "";
        if (padHours && hours > 0 && hours < 10) hoursStr = "0" + hoursStr;

        var minStr = "";
        if (hours > 0 || mins > 0)
        {
            minStr = mins + ":";
            if (mins < 10) minStr = "0" + minStr;
        }

        var secondsStr = seconds + "";
        if (seconds < 10) secondsStr = "0" + secondsStr;

        var countdown = hoursStr + minStr + secondsStr;

        return countdown;
    }

    static formatFixedSizeCountdown(timeRemaining)
    {
        var cooldownRemaining = Math.floor(timeRemaining);
        var hours = Math.floor(cooldownRemaining / (60 * 60));
        cooldownRemaining -= hours * 60 * 60;

        var mins = Math.floor(cooldownRemaining / 60);
        cooldownRemaining -= mins * 60;

        var seconds = Math.floor(cooldownRemaining);

        var countdown = (hours < 10 ?"0":"") + hours + ":" + (mins < 10 ? "0" : "") + mins + ":" + (seconds < 10 ? "0" : "") + seconds;

        return countdown;
    }

    static formatTime(seconds, show_seconds, abrv)
    {
        if (show_seconds === undefined) show_seconds = true;
        if (abrv === undefined) abrv = false;

        if (seconds < 60) show_seconds = true;

        return Engine.Utils.formatTime2(Math.floor(seconds), show_seconds ? UtilsStatics.TimeResolution.Seconds : UtilsStatics.TimeResolution.Minutes, abrv);
    }


    static SetTimeStrings(dayParam, hourParam, minuteParam, secondParam, dayPlurParam, hourPlurParam, minutePlurParam, secondPlurParam, 
        dayAbrParam, hourAbrParam, minuteAbrParam, secondAbrParam)
    {
        UtilsStatics.day = dayParam;
        UtilsStatics.hour = hourParam;
        UtilsStatics.minute = minuteParam;
        UtilsStatics.second = secondParam;

        UtilsStatics.dayPlur = dayPlurParam;
        UtilsStatics.hourPlur = hourPlurParam;
        UtilsStatics.minutePlur = minutePlurParam;
        UtilsStatics.secondPlur = secondPlurParam;

        UtilsStatics.dayAbr = dayAbrParam;
        UtilsStatics.hourAbr = hourAbrParam;
        UtilsStatics.minuteAbr = minuteAbrParam;
        UtilsStatics.secondAbr = secondAbrParam;
    }

    static formatTime2(timeInSeconds, maxRes, useAbbreviations, maxDivisions)
    {

        if (maxRes === undefined) maxRes = UtilsStatics.TimeResolution.Seconds;
        if (useAbbreviations === undefined) useAbbreviations = false;
        if (maxDivisions === undefined) maxDivisions = null;


        var days = Math.floor(timeInSeconds / (60 * 60 * 24));
        var timeRemaining = timeInSeconds - (days * 60 * 60 * 24);

        var hours = Math.floor(timeRemaining / (60 * 60));
        timeRemaining -= (hours * 60 * 60);

        var minutes = Math.floor(timeRemaining / 60);
        timeRemaining -= (minutes * 60);

        var seconds = timeRemaining;

        var dayNames = useAbbreviations ? { "First":UtilsStatics.dayAbr, "Second":UtilsStatics.dayAbr } : {"First":" "+ UtilsStatics.day + " ", "Second":" " + UtilsStatics.dayPlur + " "};
        var hourNames = useAbbreviations ? { "First":UtilsStatics.hourAbr, "Second":UtilsStatics.hourAbr } : {"First":" " + UtilsStatics.hour + " ", "Second":" " + UtilsStatics.hourPlur + " "};
        var minuteNames = useAbbreviations ? { "First":UtilsStatics.minuteAbr, "Second":UtilsStatics.minuteAbr } : {"First":" " + UtilsStatics.minute + " ", "Second":" " + UtilsStatics.minutePlur + " "};
        var secondNames = useAbbreviations ? { "First":UtilsStatics.secondAbr, "Second":UtilsStatics.secondAbr } : {"First":" " + UtilsStatics.second + " ", "Second":" " + UtilsStatics.secondPlur + " "};

		var divisionsUsed = 0;

        var output = "";

		if (days > 0 && maxRes >= UtilsStatics.TimeResolution.Days && (maxDivisions == null || divisionsUsed < maxDivisions))
		{
            output += days + (days == 1 ? dayNames.First : dayNames.Second);
			divisionsUsed++;
		}
		
		if (hours > 0 && maxRes >= UtilsStatics.TimeResolution.Hours && (maxDivisions == null || divisionsUsed < maxDivisions))
		{
            output += hours + (hours == 1 ? hourNames.First : hourNames.Second);
			divisionsUsed++;
		}
		
		if (minutes > 0 && maxRes >= UtilsStatics.TimeResolution.Minutes && (maxDivisions == null || divisionsUsed < maxDivisions))
		{
            output += minutes + (minutes == 1 ? minuteNames.First : minuteNames.Second);
			divisionsUsed++;
		}
		
		if (seconds > 0 && maxRes >= UtilsStatics.TimeResolution.Seconds && (maxDivisions == null || divisionsUsed < maxDivisions))
		{
            output += seconds + (seconds == 1 ? secondNames.First : secondNames.Second);
			divisionsUsed++;
		}

        return output.trim();
	}

    static formatBigNumber(number, round)
    {
        if (round === undefined) round = false;

        if (!isFinite(number)) return "INF";

        // If it's a small number with decimals, just return the number back
        if (!round && number < 100 && (number - Math.floor(number) != 0)) 
        {
            var rounded = number < 0 ? Math.round(number * -1000) * -1 : Math.round(number * 1000); //Javascript rounds towards positive infinity, but we want to round away from zero
            return "" + (rounded / 1000.0).toFixed(Engine.Utils.getFixed(number));
        }

        if (round && number < 100)
        { 
            return number < 0 ? Math.round(number * -1) * -1 : Math.round(number) + ""; //Javascript rounds towards positive infinity, but we want to round away from zero
        }

        if (number < 100 && number == Math.floor(number)) return number + "";

        if (!round && number < 1000 && number != Math.floor(number)) return number.toFixed(Engine.Utils.getFixed(number));

        if (UtilsStatics.ForceScientific && number < 10000)
		{
			var out = number.toFixed(0);
			if (out.length == 4) out = out.substr(0, 1) + "," + out.substr(1);
			return out;
		}

        if (UtilsStatics.divisors.length == 0)
        {
            for (var i = 0; i < UtilsStatics.symbols.length; i++) UtilsStatics.divisors.push(Math.pow(1000, i + 1));
        }

        // Determine what the number is and which symbol we're going to use
        var amount = number;
        var symbol = "";
        for (var i = UtilsStatics.divisors.length - 1; i >= 0; i--)
        {
            if (number >= UtilsStatics.divisors[i])
            {
                amount = (number / UtilsStatics.divisors[i]);
                symbol = UtilsStatics.symbols[i];
                break;
            }
        }

        if (UtilsStatics.ForceScientific || amount >= 1000)
        {
            //return number.toString("#.00e00");
            var amount_str = Number.parseFloat(number).toExponential(2).replace("+", "");
            if (amount_str.charAt(amount_str.length - 2) == "e") amount_str = amount_str.slice(0, amount_str.length - 1) + "0" + amount_str.slice(amount_str.length - 1);
            return amount_str;
        } 
        else 
        {
            // determine number of whole digits (will dictat how many decimals we show)
            var whole = Math.floor(amount);
            var amount_str = "" + whole;

            var decimalDivider = 100 / Math.pow(10, amount_str.length - 1);

            // round to nearest decimal place
            amount = (Math.floor(amount * decimalDivider)) / decimalDivider;

            var amountString = "" + amount;

            var noDecimal = amountString.replace(".", "");
            if (noDecimal.length == 2)
            {
                if (amountString.indexOf(".") != -1) amountString += "0";
                else amountString += ".0";
            }
            else if (noDecimal.length == 1) amountString += ".00";

            return amountString + symbol; // concat symbol
        }

    }

    static getFixed(n)
    {
        if (n * 10 - Math.floor(n * 10) == 0)
        {
            return 1;
        }
        return 2;
    }

    static isNumberInvalid(n)
    {
        if (!isFinite(n)) return true;
        return false;
    }

    static sanitizeNumber(n)
    {
        if (isFinite(n)) return Number(n);
        return 0;
    }

    static md5(str)
    {
        return md5b(str);
        //TODO IMPLEMENT THIS...
        /*
        MD5 md5Hash = MD5.Create();

        byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(str));
        StringBuilder sBuilder = new StringBuilder();

        for (int i = 0; i < data.Length; i++)
        {
            sBuilder.Append(data[i].ToString("x2"));
        }

        // Return the hexadecimal string.
        return sBuilder.ToString();
        */
    }

    static formatNumber(cost)
    {
        return cost;
        //return String.Format("{0:#,##0}", cost);
    }

    // normal random variate generator; mean m, standard deviation s
    static boxMullerRandomNormal(m, s) 
	{				       
		var x1, x2, w, y1;
		var y2;

		do {
			x1 = 2.0 * Math.random() - 1.0;
			x2 = 2.0 * Math.random() - 1.0;
			w = x1* x1 + x2* x2;
        } while ( w >= 1.0 );

	    w = Math.sqrt( (-2.0 * Math.log(w ) ) / w );
	    y1 = x1* w;
        y2 = x2* w;

		return( m + y1* s );
	}

    /// <summary>
    /// Get a double between 0.0 <= x < 1.0
    /// </summary>
    /// <returns></returns>
    static randomDouble()
    {
        return Math.random();
    }

    /// <summary>
    /// Get an integer between min and max
    /// </summary>
    /// <param name="min">min inclusive</param>
    /// <param name="max">max exclusive</param>
    /// <returns></returns>
    static getRandom(min, max)
    {
        return Math.floor(Math.random() * Math.floor(max - min)) + min;
    }

    /// <summary>
    /// Fisher-Yates shuffle rearraange the items in the list
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="list"></param>
    static shuffle(list)
    {
        for (var i = 0; i < list.length; i++)
        {
            var r = i + Math.floor(Math.random() * (list.length - i));
            var t = list[r];
            list[r] = list[i];
            list[i] = t;
        }
    }

	/// <summary>
	/// Get the time in timer format ( hh:mm:ss ). Hours not displayed if 0. 
	/// </summary>
	/// <param name="timeSeconds"></param>
	/// <returns></returns>
	static formatTimeAsTimer(timeSeconds)
	{
		var hours = 0;// (int)cooldownRemaining / (60 * 60);
		var mins = Math.floor(timeSeconds) / 60;
		var seconds = Math.floor(timeSeconds) - (hours * 60 * 60) - (mins * 60);

		var hoursStr = hours > 0 ? hours + ":" : "";
		if (hours > 0 && hours < 10) hoursStr = "0" + hoursStr;

		var minStr = mins > 0 ? mins + ": " : "00: ";
		if (mins > 0 && mins < 10) minStr = "0" + minStr;

		var secondsStr = seconds + "";
		if (seconds < 10) secondsStr = "0" + secondsStr;

		return hoursStr + minStr + secondsStr;
	}

	/// <summary>
	/// Rotates the given point around (0,0) by radians.
	/// </summary>
	/// <param name="point"></param>
	/// <param name="radians"></param>
	/// <returns></returns>
	static rotatePoint(point, radians)
	{
		var cos = Math.cos(radians);
		var sin = Math.sin(radians);
		var x = point.x * cos - point.y * sin;
		var y = point.x * sin + point.y * cos;
		return new PIXI.Point(x, y);
	}

	/// <summary>
	/// Gets whether or not an email is valid
	/// </summary>
	/// <param name="email">Email to be checkd</param>
	/// <returns>whether or not the emial is valid</returns>
	static isValidEmail(email)
    {
        var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return re.test(String(email).toLowerCase());
    }

    static copyObject(obj)
    {
        var newObj = {};
        for (var key in obj)
        {
            newObj[key] = obj[key];
        }
        return newObj;
    }
	
	static deepCopy(obj)
    {		
		if (Array.isArray(obj))
		{
			var newArr = [];
			for (var i in obj)
			{
				newArr[i] = this.deepCopy(obj[i]);
			}
			return newArr;
		}
		if (typeof obj == "object")
		{
			var newObj = {};
			for (var key in obj)
			{
				newObj[key] = this.deepCopy(obj[key]);
			}
			return newObj;
		}		
        return obj;
    }

    static concatObject(a, b)
    {
        var newObj = {};
        if (a != null)
		{
			for (var key in a) newObj[key] = a[key];
		}
		if (b != null)
		{
			for (var key in b) newObj[key] = b[key];
		}
        return newObj;
    }

    static toCamelCase(input, firstUpper)
    {
        if (firstUpper === undefined) firstUpper = true;

        var result = input.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { return index == 0 && !firstUpper ? letter.toLowerCase() : letter.toUpperCase(); }).replace(/\s+/g, '');
        return result;

    }

    static toUnderlineCase(input)
    {
        var toUnderlineCaseReg = /[^A-Z0-9][A-Z0-9]/g;
        return input.replace(toUnderlineCaseReg, match => [match.slice(0, 1), "_", match.slice(1)].join('')).toLowerCase();
    }

    static openInNewTab(url)
    {
        var win = window.open(url, '_blank');
        win.focus();
    }

    static readRange(data, )
    {

    }

	static scaleAngleRadians(radians, scaleX, scaleY)
	{
		return Math.atan2(Math.sin(radians) * scaleY, Math.cos(radians) * scaleX);
	}

	static scaleAngle(degrees, scaleX, scaleY)
	{
		return Engine.Utils.scaleAngleRadians(degrees * Math.PI / 180.0, scaleX, scaleY) * 180.0 / Math.PI;
	}

    static formatDate(date)
    {
        var month = this.monthPrefix(date.getMonth() + 1);
        var day = date.getDate()
        var year = date.getFullYear();
        var hour = date.getHours();
        var ampm = "am";
        if (hour >= 12) 
        {   
            hour -= 12;
            ampm = "pm";
        }
        if (hour == 0) hour += 12;
        var min = date.getMinutes();
        if (min < 10) min = "0" + min;

        return "" + month + " " + day + ", " + year + " " + hour + ":" + min + " " + ampm;
    }

    static monthPrefix(month)
    {
        switch (month)
        {
            case 1:
                return "Jan";
            case 2:
                return "Feb";
            case 3:
                return "Mar";
            case 4:
                return "Apr";
            case 5:
                return "May";
            case 6:
                return "June";
            case 7:
                return "July";
            case 8:
                return "Aug";
            case 9:
                return "Sept";
            case 10:
                return "Oct";
            case 11:
                return "Nov";
            case 12:
                return "Dec";
            default:
                return "Mon";
        }
    }
	
	static getComparisonOp(comparison)
	{
		switch (comparison.toLowerCase())
		{
			case "less_than":
			case "lt":
			case "<":
				return UtilsStatics.ComparisonOp.LT;
			case "less_than_or_equal":
			case "less_than_equal":
			case "lte":
			case "<=":
				return UtilsStatics.ComparisonOp.LTE;
			case "greater_than":
			case "gt":
			case ">":
				return UtilsStatics.ComparisonOp.GT;
			case "greater_than_or_equal":
			case "greater_than_equal":
			case "gte":
			case ">=":
				return UtilsStatics.ComparisonOp.GTE;
			case "eq":
			case "equal":
			case "equal_to":
			case "equals":
			case "=":
			case "==":
				return UtilsStatics.ComparisonOp.EQ;
			case "ne":
			case "neq":
			case "not_equal":
			case "not_equal_to":
			case "!equal":
			case "!=":
			case "<>":
				return UtilsStatics.ComparisonOp.NEQ;
		}
		return null;
	}
	
	static makeComparison(valueLeft, comparison, valueRight)
	{
		if (typeof comparison == "string")
		{
			comparison = Engine.Utils.getComparisonOp(comparison);
		}
		
		if (comparison != null)
		{
			switch (comparison)
			{
				case UtilsStatics.ComparisonOp.LT:
					return valueLeft < valueRight;
				case UtilsStatics.ComparisonOp.LTE:
					return valueLeft <= valueRight;
				case UtilsStatics.ComparisonOp.GT:
					return valueLeft > valueRight;
				case UtilsStatics.ComparisonOp.GTE:
					return valueLeft >= valueRight;
				case UtilsStatics.ComparisonOp.EQ:
					return valueLeft == valueRight;
				case UtilsStatics.ComparisonOp.NEQ:
					return valueLeft != valueRight;
			}
		}
		return false;
	}

    static reportError(error, message)
    {
        if (message === undefined) message = error;
        
        Debug.LogError(error)
        Engine.DialogManagerInstance.showMessageBox(message, "ok");
    }

    // stack 105034 thankyou!
    static uuidv4() 
    {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
}

var UtilsStatics = {};

UtilsStatics.ComparisonOp = { LT:0, LTE:1, GT:2, GTE:3, EQ:4, NEQ:5 };

UtilsStatics.day = "day";
UtilsStatics.hour = "hour";
UtilsStatics.minute = "minute";
UtilsStatics.second = "second";

UtilsStatics.dayPlur = "days";
UtilsStatics.hourPlur = "hours";
UtilsStatics.minutePlur = "minutes";
UtilsStatics.secondPlur = "seconds";

UtilsStatics.dayAbr = "d";
UtilsStatics.hourAbr = "h";
UtilsStatics.minuteAbr = "m";
UtilsStatics.secondAbr = "s";

UtilsStatics.serverTimeOffset = 0;

UtilsStatics.TimeResolution = { "Days":1, "Hours":2, "Minutes":3, "Seconds":4 }

UtilsStatics.ForceScientific = false;
UtilsStatics.symbols = ["K", "M", "B", "t", "q", "Q", "s", "S", "o", "n", "d", "U", "D", "T", "Qt", "Qd", "Sd", "St", "O", "N", "v", "c"];
UtilsStatics.divisors = [];

UtilsStatics.bigNumberRegex = /(\d*),?(\d*)(.*)/;