Array.prototype.last=function(){
    if(!this.length)return null;
    return this[this.length-1];
}

Array.Add=function(array,value){
    if(!Object.IsType(Array,array))return;
    if(Array.Contains(array,value))return;
    array.push(value);
}

Array.Find=function(array,matches,limit){
    var results=[];
    if(Object.IsType(Array,array)){
        var length=array.length;
        limit=parseInt(limit||length);
        for(var i=0;i<length;i++){
            var hit=true;
            for(var x in matches){
                var isFunc=Object.IsType(Function,matches[x]);
                if(array[i][x]!=matches[x]||(isFunc&&!matches[x](array[i],x))){
                    hit=false;
                    break;
                }
            }
            if(hit)results.push(array[i]);
            if(results.length>=limit)break;
        }
    }
    return results;
}

Array.IndexOf=function(array,value){
    if(Object.IsType(Array,array)){
        var isFunc=Object.IsType(Function,value);
        for(var i=0;i<array.length;i++){
            if(array[i]==value||(isFunc&&value(array[i])))return i;
        }
    }
    return -1;
}

Array.Contains=function(array,value){
    if(Object.IsType(Array,array)){
        for(var i=0;i<array.length;i++){
            if(array[i]==value)return true;
        }
    }
    return false;
}

Array.Remove=function(array,value){
    if(!Object.IsType(Array,array))return;
    var index=Array.IndexOf(array,value);
    if(index>-1)return array.splice(index,1);
    return null;
}

Date.GetDate=function(timeZone,isDst){
    var date=new Date();
    if(timeZone!=undefined){
        var localOffset=date.getTimezoneOffset()/-60;
        var offset=timeZone-localOffset;
        if(isDst)offset+=1;
        date.setHours(date.getHours()+offset);
    }
    return date;
}

Date.Matches=function(expected,actual,matchTime){
    if(!Object.IsType(Date,expected))return false;
    if(!Object.IsType(Date,actual))return false;
    return expected.getFullYear()==actual.getFullYear() &&
           expected.getMonth()==actual.getMonth() &&
           expected.getDate()==actual.getDate() && (
               !matchTime||(
                   expected.getHours()==actual.getHours() &&
                   expected.getMinutes()==actual.getMinutes() &&
                   expected.getSeconds()==actual.getSeconds()
               )
           );
}

Date.ToLocal=function(date,timezone,isDst){
    if (!Object.IsType(Date,date))return null;
    var workingDate=new Date(date);
    var localTimezone=date.getTimezoneOffset()/-60;
    if(timezone==undefined)timezone=localTimezone;
    workingDate.setHours(workingDate.getHours()-(localTimezone-(!!isDst*1)-timezone));
    return workingDate;
}

Date.Format=function(format,date,timezone){
    if (!format||!Object.IsType(Date,date))return null;
    if(timezone==undefined)timezone=date.getTimezoneOffset()/-60;
    date=new Date(date);
    date.setHours(date.getHours()-timezone);
    format=format
        .replace(/yyyy/gm,'{0}')
        .replace(/yy/gm,'{1}')
        .replace(/MM/gm,'{2}')
        .replace(/M/gm,'{3}')
        .replace(/dd/gm,'{4}')
        .replace(/d/gm,'{5}')
        .replace(/hh/gm,'{6}')
        .replace(/h/gm,'{7}')
        .replace(/HH/gm,'{8}')
        .replace(/H/gm,'{9}')
        .replace(/mm/gm,'{10}')
        .replace(/m/gm,'{11}')
        .replace(/ss/gm,'{12}')
        .replace(/s/gm,'{13}')
        .replace(/tt/gm,'{14}')
        .replace(/ii/gm,'{15}')
        .replace(/i/gm,'{16}');
    var hours=date.getHours();
    var meridianHours=hours>0?hours>12?hours-12:hours:12;
    return String.Format(
        format,
        date.getFullYear(),
        date.getYear(),
        Number.Pad(date.getMonth()+1),
        date.getMonth()+1,
        Number.Pad(date.getDate()),
        date.getDate(),
        Number.Pad(hours),
        hours,
        Number.Pad(meridianHours),
        meridianHours,
        Number.Pad(date.getMinutes()),
        date.getMinutes(),
        Number.Pad(date.getSeconds()),
        date.getSeconds(),
        hours<12?'AM':'PM',
        Number.Pad(date.getMilliseconds()),
        date.getMilliseconds()
    );
}

Date.FormatDuration=function(durationInSeconds){
    var hours=Math.floor(Math.floor(durationInSeconds/60)/60);
    var minutes=Math.round(Math.floor(durationInSeconds/60)%60);
    var seconds=Math.round(durationInSeconds%60);
    var result=[];
    if (hours) result.push(String.Format("{0} {1}", hours, hours != 1 ? Inrix.Map.Settings.Traffic.TmcDetailText['Hours'] : Inrix.Map.Settings.Traffic.TmcDetailText['Hour']));
    if (minutes) result.push(String.Format("{0} {1}", minutes, minutes != 1 ? Inrix.Map.Settings.Traffic.TmcDetailText['Minutes'] : Inrix.Map.Settings.Traffic.TmcDetailText['Minute']));
    if(seconds)result.push(String.Format("{0} {1}",seconds,seconds!=1?Inrix.Map.Settings.Traffic.TmcDetailText['Seconds']:Inrix.Map.Settings.Traffic.TmcDetailText['Second']));
    return result.join(' ');

}

Date.FormatUTC=function(date,timezone,isDst){
    // 2008-03-20T19:05:59Z
    if (!Object.IsType(Date,date))return null;
    if(timezone==undefined)timezone=date.getTimezoneOffset()/-60;
    date=new Date(date);
    date.setHours(date.getHours()-timezone-(!!isDst*1));
    return String.Format(
        "{0}-{1}-{2}T{3}:{4}:{5}Z",
        date.getFullYear(),
        Number.Pad(date.getMonth()+1),
        Number.Pad(date.getDate()),
        Number.Pad(date.getHours()),
        Number.Pad(date.getMinutes()),
        Number.Pad(date.getSeconds())
    );
}

Date.FormatShort=function(date,timezone,includeTime){
    if (!Object.IsType(Date,date))return null;
    if(timezone==undefined)timezone=date.getTimezoneOffset()/-60;
    date=new Date(date);
    date.setHours(date.getHours()-timezone);
    var hours=date.getHours();
    var today=new Date();
    var dateString;
    if(today.getFullYear()==date.getFullYear()){
        var tempDate=new Date(today);
        tempDate.setDate(tempDate.getDate()-2);
        var list=["2 days ago","Yesterday","Today","Tomorrow","2 days from now"];
        for(var i=0;i<list.length;i++){
            if(Date.Matches(tempDate,date)){
                dateString=list[i];
                if(includeTime)dateString+=" at";
                break;
            }
            tempDate.setDate(tempDate.getDate()+1);
        }
    }
    if(!dateString)dateString=String.Format("{0}/{1}/{2}",date.getFullYear(),Number.Pad(date.getMonth()+1),Number.Pad(date.getDate()));
    if(!includeTime)return dateString;
    return String.Format(
        "{0} {1}:{2}:{3} {4}",
        dateString,
        Number.Pad(hours>0?hours>12?hours-12:hours:12),
        Number.Pad(date.getMinutes()),
        Number.Pad(date.getSeconds()),
        hours<12?'AM':'PM'
    )    
}

// The next two functions depend on localized strings that are currently only available from the traffic page
Date.FormatLocalizedDateString = function(date) {
    var day = Inrix.Map.Settings.Traffic.DateDays[date.getDay()];
    var month = Inrix.Map.Settings.Traffic.DateMonths[date.getMonth()];
    var format = Inrix.Map.Settings.Traffic.DateInfo["DateFormat"];
    return String.Format(format, day, date.getDate(), month, date.getFullYear());
}

Date.FormatLocalizedTimeString = function(date, showSecs) {
    var hours = date.getHours().toString();
    var minutes = (date.getMinutes() > 9 ? "" : "0") + date.getMinutes().toString();
    var seconds = (date.getSeconds() > 9 ? "" : "0") + date.getSeconds().toString();
    var timeFormat = showSecs ? "TimeFormatSeconds" : "TimeFormat";
    if (Inrix.Map.Settings.Traffic.DateInfo["Time24Hour"] == "true") {
        return String.Format(Inrix.Map.Settings.Traffic.DateInfo[timeFormat], hours, minutes, seconds);
    } else {
        var hoursRange = date.getHours() < 12 ? Inrix.Map.Settings.Traffic.DateInfo["TimeAM"] : Inrix.Map.Settings.Traffic.DateInfo["TimePM"];
        return String.Format(Inrix.Map.Settings.Traffic.DateInfo[timeFormat], (date.getHours() + 11) % 12 + 1, minutes, hoursRange, seconds);
    }
}

Date.FormatLocal = function(dateString, timezone, isDst) {
    var date=Date.Parse(dateString);
    if(timezone!=undefined)date=Date.ToLocal(date,timezone,isDst);
    return [Date.FormatLocalizedDateString(date), Date.FormatLocalizedTimeString(date, false)].join(" &shy;");
}

Date.Parse=function(dateString){
    // 2008-03-20T19:05:59Z
    var date = new Date();
    if(Object.IsType(String,dateString)){
        var dateBits=dateString.split(/\D+/);
        date.setUTCFullYear(dateBits[0], dateBits[1]-1, dateBits[2]);
        date.setUTCHours(dateBits[3], dateBits[4], dateBits[5]);
    }
    return date;
}

Date.IsDST=function(date){
    if(!Object.IsType(Date,date))date=new Date();
    var o=date.getTimezoneOffset();
    var d0=new Date(date.getFullYear(),0,1);
    var o0=d0.getTimezoneOffset();
    var d1=new Date(date.getFullYear(),6,1);
    var o1=d1.getTimezoneOffset();
    var isDST=o1!=o0&&o==o1;
    return isDST;
}

Date.GetTimeZone = function(timeZoneOffset, isDST, timeZoneName) {
    if (timeZoneName != undefined) return timeZoneName;
    if(arguments.length<2)isDST=Date.IsDST(new Date());
    var maps={
        ST:{
            "-10":"HAST",
            "-9":"AKST",
            "-8":"PST",
            "-7":"MST",
            "-6":"CST",
            "-5":"EST",
            "-4":"AST",
            "-3":"ADT",
            "-3.5":"NST",
            "-2.5":"HAT",
            "0":"GMT",
            "1":"CET",
            "2":"EET",
            "3":"MSK",
            "4":"MSD",
            "5":"E",
            "6":"F",
            "7":"CXT",
            "8":"WST",
            "9":"WST",
            "9.5":"ACST",
            "10":"AEST",
            "10.5":"ACDT",
            "11":"AEDT",
            "11.5":"NFT"
        },
        DST:{
            "-10":"HAST",
            "-9":"AKDT",
            "-8":"PDT",
            "-7":"MDT",
            "-6":"CDT",
            "-5":"EDT",
            "-4":"ADT",
            "-3":"AST",
            "-3.5":"NST",
            "-2.5":"HAT",
            "0":"WEST",
            "1":"CEST",
            "2":"EEST",
            "3":"MSD",
            "4":"E",
            "5":"E",
            "6":"F",
            "7":"CXT",
            "8":"WST",
            "9":"WST",
            "9.5":"ACST",
            "10":"AEST",
            "10.5":"ACDT",
            "11":"AEDT",
            "11.5":"NFT"
        }
    };
    return String.Format("({0})",maps[isDST?'DST':'ST'][timeZoneOffset]||timeZoneOffset);
}

Error.prototype.toString=function(){
    return this.description||this.message||this;
}

Function.EnsureNamespace=function(nameSpace,rootContainer){
    if(!nameSpace)return;
    var nameSpaces=nameSpace.toString().split('.');
    var container=rootContainer||window;
    for(var i=0;i<nameSpaces.length;i++){
        if(!container[nameSpaces[i]])container[nameSpaces[i]]={};
        container=container[nameSpaces[i]];
    }
}

Function.GetDelegate=function(method,instance){
    var args=Array.prototype.slice.call(arguments,2);
    return function(){
        return method.apply(instance,args.concat(Array.prototype.slice.call(arguments,0)));
    }
}

Function.Resolve=function(className,rootContainer){
    if(!className)return null;
    var method=null;
    if(className){
        var nameSpaces=className.toString().split('.');
        var container=rootContainer||window;
        for(var i=0;i<nameSpaces.length;i++){
            if(!(container=container[nameSpaces[i]]))break;
        }
        method=container;
    }
    return method;
}

Number.Equals=function(expected,actual,precision){
    if(arguments<3)precision=5;
    var magnitude=Math.pow(10,precision);
    return Number.Trim(expected,magnitude) == Number.Trim(actual,magnitude);
}

Number.Pad=function(number,magnitude){
    var padding=[];
    padding[Math.max((magnitude||2)-(number+'').length,0)]=number;
    return padding.join('0');
}

Number.Trim=function(number,magnitude,floor){
    if(!magnitude)magnitude=100;
    return floor ? Math.floor(number*magnitude)/magnitude : Math.round(number*magnitude)/magnitude;
}

Object.Copy=function(source,target){
    var skipMap={'constructor':1};
    for(var x in source){
        if(skipMap[x])continue;
        target[x]=source[x];
    }
}

Object.Clone=function(obj){
    var clone=null;
    if(obj==null)return clone;
    if(obj instanceof Array){
        clone=[];
        for(var i=0;i<obj.length;i++)clone.push(Object.Clone(obj[i]));
    }
    else if(typeof(obj)=='object'){
        clone={};
        var dontEnumProps=['constructor','toString','valueOf','toLocaleString','prototype','isPrototypeOf','propertyIsEnumerable','hasOwnProperty','length','unique'];
        for(var x in obj)clone[x]=Object.Clone(obj[x]);
        for(var i=0;i<dontEnumProps.length;i++){
            var prop=dontEnumProps[i];
            if(clone[prop]!=obj[prop])clone[prop]=obj[prop];
        }
    }
    else if(typeof(obj)=='string')clone=obj;
    else if(!(typeof(obj)==='function'))clone=obj;
    else clone=obj;
    return clone;
}

Object.Equals=function(expected,actual){
    if(!expected||!actual)return expected==actual;
    var list={};
    for(var x in expected){
        if(actual[x]!=expected[x]){
            if(typeof(expected[x])=='object'){
                if(!Object.Equals(expected[x],actual[x]))return false;
            }else return false;
        }
        list[x]=true;
    }
    for(var x in actual){
        if(!list[x])return false;
    }
    return true;
}

Object.IsType=function(expected,actual){
    if(!expected)throw new Error("Object.IsType: parameter 'expected' can not be null. Please provide a type to compare against.");
    if(!actual)return false;
    if(expected==Error&&actual.constructor&&actual.constructor.toString()=="[object Error]")return true;
    return actual.constructor==expected;
}

Object.Matches=function(actual,expected){
    if(!expected)return expected==actual;
    for(var x in expected){
        var match=typeof(expected[x])=='function'?expected[x](actual,x):actual[x]==expected[x];
        if(!match)return false;
    }
    return true;
}

Object.ToJson=function(obj){
    if(obj==null)return "null";
    if(obj instanceof Array){
        var ret=[];
        for(var i=0;i<obj.length;i++)ret.push(Object.ToJson(obj[i]));
        return ['[',ret.join(','),']'].join('');
    }
    if(typeof(obj)=='object'){
        var ret=[];
        for(var x in obj)ret.push(['"',x,'":',Object.ToJson(obj[x])].join(''));
        return ['{',ret.join(','),'}'].join('');
    }
    if(typeof(obj)=='string')return ['"',obj.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\n/g,"\\n").replace(/\r/g,"\\r"),'"'].join('');
    if(!(typeof(obj)==='function'))return obj.toString();
    return '""';
}

Object.FromJson=function(json){
    var obj=null;
    if(json){
        if(/^\(?("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?\)?$/m.test(json)){
            try{obj=eval(['(',')'].join(json))}
            catch(e){
                //throw new Error("Object.fromJson: "+(e.description?e.description:e))
            };
        }//else throw new Error("Object.fromJson: Invalid Json: "+json);
    }
    if(obj)obj.toString=function(){return json;}
    return obj;
}

Object.Set=function(obj,properties){
    if(!obj)return;
    for(var x in properties){
        if(properties[x]&&typeof(properties[x])==="object"){
            if(!obj[x])obj[x]={};
            Object.Set(obj[x],properties[x]);
        }else obj[x]=properties[x];
    }
}

String.Format=function(format){
	var args=arguments;
	return format.replace(/\{(\d*)\}/gm,getArg);
	function getArg(mtch,idx,strPos){
		if(!isNaN(idx)){
			idx=(idx*1)+1;
			if(idx<args.length)return args[idx];
		}
		return mtch;
	}
}

String.Trim=function(str,chars){
    if(!str||!str.toString)return '';
    if(!chars)return str.toString().replace(/^\s+|\s+$/g,'');
    return str.toString().replace(new RegExp(String.Format("(^(\\s|{0})+)|((\\s|{0})+$)",chars),"gm"),'');
}
