/**
 * Map2Fun.com
 * 2010-1-10
 * @author robin
 */
/**
 * Packages
 */
var map2fun = {}, app;
map2fun.control = {};
map2fun.control.ui = {};
map2fun.data = {};
map2fun.utils = {};

google.load("jquery", "1.4.1");
google.load("maps", "2");

/*
 * Static Variable
 */
map2fun.DEBUG = true;
map2fun.SYSTEM_UNINSTALLED = -1;
map2fun.SYSTEM_PRELOADED = 0;
map2fun.SYSTEM_LOADED = 1;
map2fun.SYSTEM_OPERATING = 2;
map2fun.VERSION = 1.0;
map2fun.COMPONENT_UNINSTALLED = 0;
map2fun.COMPONENT_LOADED = 1;
map2fun.USER_OFF = 0;
map2fun.USER_ON = 1;
map2fun.COOKIES_LIFE = 30;
map2fun.UNIT_TYPE = ["购物", "旅馆", "餐馆", "娱乐", "公园、广场", "景点", "其它"];
map2fun.MODULES = {
    "LocationSelect": "lib/scripts/LocationSelect.js",
    "Geo": "http://j.maxmind.com/app/geoip.js",
    "Facebox": "lib/facebox/facebox.js",
    "MD5": "lib/scripts/md5.js",
    "DataControl": "lib/scripts/DataControl.js",
    "Scroll": "lib/scripts/jquery.scrollTo-min.js",
    "Models": "lib/scripts/models.js"
};
if (map2fun.DEBUG) {//for debug
    map2fun.VERIFY_CODE_PATH = "lib/images/verifyCode.jpeg";
    map2fun.AUTH_PATH = "test/json/login_ok.json";
    map2fun.REG_PATH = "test/json/reg_error.json";
    map2fun.NODE_DETAIL_PATH = "nodeDetail.do";
    map2fun.PATH_DETAIL_PATH = "pathDetail.do";
    map2fun.NODE_ITEMS_PATH = "test/json/nodeItems.json";
    map2fun.AD_DETAIL_PATH = "adDetail.do";
    map2fun.ADD_AD_PATH = "sponsor.do";
    map2fun.ADD_NODE_PATH = "test/json/add_node_ok.json";
    map2fun.ADD_PATH_PATH = "test/json/add_path_error.json";
    map2fun.UNIT_DETAIL_PATH = "shopDetail.do";
    map2fun.ADD_UNIT_PATH = "test/json/add_unit_error.json";
    map2fun.UNIT_FINDER_PATH = "test/json/find_units.json";
    map2fun.PATH_FINDER_PATH = "test/json/find_path_ok.json";
}
else {//for prodcution
    map2fun.VERIFY_CODE_PATH = "verifyCode.jpeg";
    map2fun.AUTH_PATH = "login.do";
    map2fun.REG_PATH = "reg.do";
    map2fun.NODE_DETAIL_PATH = "nodeDetail.do";
    map2fun.PATH_DETAIL_PATH = "pathDetail.do";
    map2fun.AD_DETAIL_PATH = "adDetail.do";
    map2fun.ADD_AD_PATH = "sponsor.do";
    map2fun.ADD_NODE_PATH = "action/addnode.do";
    map2fun.ADD_PATH_PATH = "test/json/add_path_ok.json";
    map2fun.UNIT_DETAIL_PATH = "shopDetail.do";
    map2fun.ADD_UNIT_PATH = "action/addBusiness.do";
    map2fun.UNIT_FINDER_PATH = "findUnit.do";
    map2fun.PATH_FINDER_PATH = "test/json/find_path_ok.json";
}

/**
 * @package map2un.data
 * @class ClentGeo
 */
map2fun.data.ClientGeo = function(c){
    this.country_code = "";
    this.country_name = "";
    this.city = "";
    this.region = "";
    this.region_name = "";
    this.latitude = -1;
    this.longitude = -1;
    this.postal_code = "";
    this.clientLatLng = new Object();
    
    this.get(c);
};

map2fun.data.ClientGeo.prototype.get = function(c){
    this.country_code = geoip_country_code();
    this.country_name = geoip_country_name();
    this.city = geoip_city();
    this.region = geoip_region();
    this.region_name = geoip_region_name();
    this.latitude = parseFloat(geoip_latitude()) || -1;
    this.longitude = parseFloat(geoip_longitude()) || -1;
    //this.postal_code = geoip_postal_code();
    if (map2fun.utils.Cookie.enabled()) {
        if (!c.latitude || !c.longitude) {
            c.country_code = geoip_country_code();
            c.country_name = geoip_country_name();
            c.city = geoip_city();
            c.region = geoip_region();
            c.region_name = geoip_region_name();
            c.latitude = geoip_latitude();
            c.longitude = geoip_longitude();
            //c.postal_code = geoip_postal_code();
            c.store(map2fun.COOKIES_LIFE);
        }
        else {
            this.country_code = c.country_code;
            this.country_name = c.country_name;
            this.city = c.city;
            this.region = c.region;
            this.region_name = c.region_name;
            this.latitude = parseFloat(c.latitude) || -1;
            this.longitude = parseFloat(c.longitude) || -1;
            //this.postal_code = c.postal_code;
        }
        
    }
    else {
        this.country_code = geoip_country_code();
        this.country_name = geoip_country_name();
        this.city = geoip_city();
        this.region = geoip_region();
        this.region_name = geoip_region_name();
        this.latitude = parseFloat(geoip_latitude()) || -1;
        this.longitude = parseFloat(geoip_longitude()) || -1;
        //this.postal_code = geoip_postal_code();
    }
    this.clientLatLng = new google.maps.LatLng(this.latitude, this.longitude);
};

/**
 * @namespace map2fun.control
 * @constructor AccountManager
 * @classDescription Login, logout, register logic
 */
map2fun.control.AccountManager = function(){
    this.userName;
    this.userEmail;
    this.status = map2fun.USER_OFF;
    this.geoCookies = new map2fun.utils.Cookie("geo");
    this.accountCookies = new map2fun.utils.Cookie("account");
    this.clientGeo = new map2fun.data.ClientGeo(this.geoCookies);
    
};

map2fun.control.AccountManager.prototype.login = function(param){
    var _class = this;
	param.authType = "login";
    $.getJSON(map2fun.AUTH_PATH, param, function(r){
        if (r.result[0].status == 1) {
            _class.welcome(param.userName);
            _class.status = map2fun.USER_ON;
            if (param.rememberMe) {
                var accountCookie = new map2fun.utils.Cookie("account");
                if (accountCookie.user) {
                    accountCookie.remove();
                }
                accountCookie.user = param.userName;
                accountCookie.store(map2fun.COOKIES_LIFE);
            }
            app.msg("登陆成功！");
        }
        else {
            app.alert("对不起，登陆失败！可能是密码或用户名错误......")
        }
    });
};

map2fun.control.AccountManager.prototype.welcome = function(userName){
    $("#top .welcome").text("你好," + userName + "! ");
    $(".userTip .userLogin, .userTip .userRegister").hide();
    $(".userTip .userLogout").show();
    $(".goUserPanel").css("display", "block");
};

map2fun.control.AccountManager.prototype.logout = function(){
    this.status = map2fun.USER_OFF;
    var accountCookie = new map2fun.utils.Cookie("account");
    accountCookie.remove();
    $.getJSON(map2fun.AUTH_PATH, {
        authType: "logout"
    });
    $(".userTip .userLogin, .userTip .userRegister").show();
    $(".userTip .userLogout").hide();
    $("#top .welcome").text("你好！");
    
    $(".goUserPanel").hide();
    app.msg("已经成功登出本程序！");
};

map2fun.control.AccountManager.prototype.register = function(param){
    var _class = this;
    $.getJSON(map2fun.REG_PATH, param, function(r){
        if (r.result[0].status == 1) {
            _class.welcome(r.result[0].userName);
            _class.status = map2fun.USER_ON;
            
            var accountCookie = new map2fun.utils.Cookie("account");
            if (accountCookie.user) {
                accountCookie.remove();
            }
            accountCookie.user = r.result[0].userName;
            accountCookie.store(map2fun.COOKIES_LIFE);
            app.msg("注册成功!");
            
        }
        else {
            $(".registerWin form").find("div.msg").remove();
            var msgBox = $("<p class='warning regError'></p>");
            $.each(r.result[0].msg, function(i, item){
                msgBox.prepend(item + "<br />");
            });
            $("<div class='msg'></div>").prepend(msgBox).prependTo(".registerWin form");
        }
    });
};

map2fun.control.AccountManager.prototype.autoLogin = function(){
    if (map2fun.utils.Cookie.enabled() && this.accountCookies.user) {//auto login using cookies
        var _class = this;
        $("#top .welcome").text("正在尝试自动登陆.....");
        $.getJSON(map2fun.AUTH_PATH, {
            "authType": "autoLogin",
            "userName": _class.accountCookies.user
        }, function(r){
            if (r.result[0].status == 1) {
                _class.welcome(_class.accountCookies.user);
                _class.status = map2fun.USER_ON;
            }
            else {
                var accountCookie = new map2fun.utils.Cookie("account");
                accountCookie.remove();
                $(".userTip .userLogin, .userTip .userRegister").show();
                $(".userTip .userLogout").hide();
            }
        });
    }
};

/**
 * @package map2fun.control
 * @class AppManager
 */
map2fun.control.AppManager = function(){
    this.status = map2fun.SYSTEM_UNINSTALLED;
    this.accountManager = new map2fun.control.AccountManager();
    this.dataControl;
    this.map;
    this.cache = new Object();
    this.ui = new Object();
};

map2fun.control.AppManager.prototype.getVersion = function(){
    return map2fun.VERSION;
};

map2fun.control.AppManager.prototype.data = function(name, value){
    if (arguments.length == 1) {
        return this.cache[name];
    }
    else 
        if (arguments.length == 2) {
            this.cache[name] = value;
        }
};

map2fun.control.AppManager.prototype.load = function(module, callback, css){
    $("script").each(function(i){
        if (this.src && this.src == module) {
            return;
        }
    });
    $.getScript(map2fun.MODULES[module], function(){
        if (callback) 
            setTimeout(callback, 800);
    });
    if (css) {
        $("head").append("<link rel='stylesheet' type='text/css' media='screen, projection' href='" + css + "' />")
    }
};

map2fun.control.AppManager.prototype.preload = function(callback){
    $(window).unload(app.exit);
    $(".nojs").hide();
    
    $(".action").live("click", function(){//stop propagation
        return false;
    });
    
    $.ajaxSetup({
        cache: true,
        type: "POST",
        timeout: 8000
    });
    
    $(".external").live("click", function(){
        this.target = "_blank";
    });
    
    $("#overlay").ajaxSend(function(){
        $(this).show();
    }).ajaxSuccess(function(){
        $(this).fadeOut();
    }).ajaxError(function(event, requet, settings){
        $(this).fadeOut();
        app.alert("通讯失败，请检查网络链接后再刷新重试;<br />请求出错的地址：" + decodeURI(settings.url));
    });

    if (callback) 
        callback();
};

map2fun.control.AppManager.prototype.addComponent = function(uiEle){
    this.ui[uiEle.name] = uiEle;
    return uiEle;
};

map2fun.control.AppManager.prototype.msg = function(msg){
    $.facebox("<div class='msg'><h3>提示</h3>" + msg + "</div>");
};

map2fun.control.AppManager.prototype.alert = function(msg){
    $.facebox("<div class='msg'><h3>错误</h3>" + msg + "</div>");
};

map2fun.control.AppManager.prototype.show = function(msg){
    $.facebox(msg);
};

map2fun.control.AppManager.prototype.exit = function(){
    GUnload();
    delete window.app;
};

map2fun.control.AppManager.prototype.debug = function(){
    if (map2fun.DEBUG && window.console) {
        console.log(arguments);
    }
};

/**
 * @package map2fun.control.ui
 * @class UIElement
 */
map2fun.control.ui.UIElement = function(name){
    this.status = map2fun.COMPONENT_UNINSTALLED;
    this.name = name || "unnamed";
};

/**
 * @package map2fun.control.ui
 * @class View
 */
map2fun.control.ui.View = function(name, player, width, height, node){
    map2fun.control.ui.UIElement.call(this, name);
    
    this.player = player;
    this.title = name;
    this.height = height;
    this.width = width;
    
    this.getContent = function(){
        return $(node).parent(".wrapper").html()
    };
    
};

map2fun.control.ui.View.prototype = new map2fun.control.ui.UIElement();
delete map2fun.control.ui.View.prototype.stauts;
delete map2fun.control.ui.View.prototype.name;

/**
 * @namespace map2fun.control.ui
 * @constructor
 */
map2fun.control.ui.MainView = function(name){
    map2fun.control.ui.UIElement.call(this, name);
    this.buttons = new Object();
    this.views = new Object();
    this.activity = "";
    
};

map2fun.control.ui.MainView.prototype = new map2fun.control.ui.UIElement();
delete map2fun.control.ui.MainView.prototype.stauts;
delete map2fun.control.ui.MainView.prototype.name;

map2fun.control.ui.MainView.prototype.init = function(callback){
    if (callback) 
        callback();
};

map2fun.control.ui.MainView.prototype.swap = function(act, data){
};

map2fun.control.ui.MainView.prototype.setActivity = function(act){
    if (this.activity == act) 
        return;
    else {
        this.activity = act;
    }
};

/**
 * @package map2fun.control.ui
 * @class Lightbox
 */
map2fun.control.ui.Lightbox = function(name){
    map2fun.control.ui.UIElement.call(this, name);
};

map2fun.control.ui.Lightbox.prototype = new map2fun.control.ui.UIElement();
delete map2fun.control.ui.Lightbox.prototype.stauts;
delete map2fun.control.ui.Lightbox.prototype.name;


map2fun.control.ui.Lightbox.prototype.open = function(/* View */win, data){
    $.facebox(win.getContent());
};

map2fun.control.ui.Lightbox.prototype.close = function(){
    $.facebox.close();
};

/*
 * @package map2fun.utils
 * @class Cookie
 */
map2fun.utils.Cookie = function(name){
    this.$name = name;
    
    var allcookies = document.cookie;
    if (allcookies == "") 
        return;
    
    var cookies = allcookies.split(';');
    var cookie = null;
    for (var i = 0; i < cookies.length; i++) {
        cookies[i] = map2fun.utils.trim(cookies[i]);
        if (cookies[i].substring(0, name.length + 1) == (name + "=")) {
            cookie = cookies[i];
            break;
        }
    };
    
    if (cookie == null) 
        return;
    
    var cookieval = cookie.substring(name.length + 1);
    
    var a = cookieval.split('&'); // Break it into an array of name/value pairs
    for (var i = 0; i < a.length; i++) // Break each pair into an array
         a[i] = a[i].split(':');
    
    for (var i = 0; i < a.length; i++) {
        this[a[i][0]] = decodeURIComponent(a[i][1]);
    };
};

map2fun.utils.Cookie.prototype.store = function(daysToLive, path, domain, secure){
    var cookieval = "";
    for (var prop in this) {
        if ((prop.charAt(0) == '$') || ((typeof this[prop]) == 'function')) 
            continue;
        if (cookieval != "") 
            cookieval += '&';
        cookieval += prop + ':' + encodeURIComponent(this[prop]);
    };
    
    var cookie = this.$name + '=' + cookieval;
    if (daysToLive || daysToLive == 0) {
        cookie += "; max-age=" + (daysToLive * 24 * 60 * 60);
    }
    
    if (path) 
        cookie += "; path=" + path;
    if (domain) 
        cookie += "; domain=" + domain;
    if (secure) 
        cookie += "; secure";
    
    document.cookie = cookie;
};


map2fun.utils.Cookie.prototype.remove = function(path, domain, secure){
    for (var prop in this) {
        if (prop.charAt(0) != '$' && typeof this[prop] != 'function') 
            delete this[prop];
    };
    this.store(0, path, domain, secure);
};

map2fun.utils.Cookie.enabled = function(){
    if (navigator.cookieEnabled != undefined) 
        return navigator.cookieEnabled;
    
    if (Cookie.enabled.cache != undefined) 
        return map2fun.utils.Cookie.enabled().cache;
    
    document.cookie = "testcookie=test; max-age=10000";
    
    var cookies = document.cookie;
    if (cookies.indexOf("testcookie=test") == -1) {
        // The cookie was not saved
        return map2fun.utils.Cookie.enabled().cache = false;
    }
    else {
        document.cookie = "testcookie=test; max-age=0";
        return map2fun.utils.Cookie.enabled().cache = true;
    }
};

/**
 * @namespace map2fun.utils
 */
map2fun.utils.getParam = function(){
    /*
     * From the Javascript Definite Guide
     */
    var args = new Object();
    var query = location.search.substring(1); // Get query string
    var pairs = query.split("&"); // Break at ampersand
    for (var i = 0; i < pairs.length; i++) {
        var pos = pairs[i].indexOf('='); // Look for "name=value"
        if (pos == -1) 
            continue; // If not found, skip
        var argname = pairs[i].substring(0, pos); // Extract the name
        var value = pairs[i].substring(pos + 1); // Extract the value
        value = decodeURIComponent(value); // Decode it, if needed
        args[argname] = value; // Store as a property
    };
    return args; // Return the object
};

map2fun.utils.trim = function(s){
    return s.replace(/(^\s*)|(\s*$)/g, "");
};

map2fun.utils.serialize = function(obj, forUrl){
    if (forUrl) 
        return $.param(obj);
    else {
        var str = "";
        for (var o in obj) {
            str += (obj[o] + " ");
        };
        return $.trim(str);
    }
};

map2fun.utils.unserialize = function(str){
    var obj = {};
    var ary = obj.spilt(" ");
    obj.province = ary[0];
    obj.city = ary[1];
    obj.district = ary[2];
    return obj;
};

map2fun.utils.encodeSignedNumber = function(num){
    var sgn_num = num << 1;
    if (num < 0) {
        sgn_num = ~ (sgn_num);
    }
    return (map2fun.utils.encodeNumber(sgn_num));
};

map2fun.utils.encodeNumber = function(num){

    var encodeString = "";
    while (num >= 0x20) {
        encodeString += (String.fromCharCode((0x20 | (num & 0x1f)) + 63));
        num >>= 5;
    }
    encodeString += (String.fromCharCode(num + 63));
    return encodeString;
};

map2fun.utils.encode = function(points){
    var plat = 0;
    var plng = 0;
    var encoded_points = "";
    var encoded_levels = "";
    for (var i = 0; i < points.length; ++i) {
        var point = points[i];
        var lat = point.Latitude;
        var lng = point.Longitude;
        //var level = point.Level;
        var late5 = Math.floor(lat * 1e5);
        var lnge5 = Math.floor(lng * 1e5);
        dlat = late5 - plat;
        dlng = lnge5 - plng;
        plat = late5;
        plng = lnge5;
        encoded_points += map2fun.utils.encodeSignedNumber(dlat) + map2fun.utils.encodeSignedNumber(dlng);
        //encoded_levels += map2fun.utils.encodeNumber(level);
    };
    
    return encoded_points;
};

//decode a encoded string for an array of points
map2fun.utils.decode = function(encoded){
    var len = encoded.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;
    
    while (index < len) {
        var b;
        var shift = 0;
        var result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        }
        while (b >= 0x20);
        var dlat = ((result & 1) ? ~ (result >> 1) : (result >> 1));
        lat += dlat;
        
        shift = 0;
        result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        }
        while (b >= 0x20);
        var dlng = ((result & 1) ? ~ (result >> 1) : (result >> 1));
        lng += dlng;
        
        array.push([lat * 1e-5, lng * 1e-5]);
    };
    
    return array;
};

// Decode an encoded levels string into a list of levels.
map2fun.utils.decodeLevels = function(encoded){
    var levels = [];
    
    for (var pointIndex = 0; pointIndex < encoded.length; ++pointIndex) {
        var pointLevel = encoded.charCodeAt(pointIndex) - 63;
        levels.push(pointLevel);
    }
    return levels;
};

if (!String.prototype.startsWith) {
    String.prototype.startsWith = function(str){
        if (str) 
            return this.substring(0, str.length) == str;
    };
}

if (!Array.prototype.unique) {
    Array.prototype.unique = function(){
        if (this.length < 2) 
            return this;
        if (this.length == 2) 
            return this[0] === this[1] ? this.shift() : this;
        var a = this.slice(0), v = a[0], i = 1, f = 1;
        while (i) {
            if (a[i] === v) {
                a.splice(i, 1);
            }
            console.log("array: ", a);
            if (i == a.length) {
                v = a[f];
                i = ++f;
            }
            else {
                i++;
            }
            if (f == a.length || i > a.length) {
                break;
            }
        };
        return a;
    };
}

if (!Array.prototype.indexOf) 
    Array.prototype.indexOf = function(item, i){
        i || (i = 0);
        var length = this.length;
        if (i < 0) 
            i = length + i;
        for (; i < length; i++) 
            if (this[i] === item) 
                return i;
        return -1;
    };

(function(){
	map2fun.utils.hex_md5 = hex_md5;
    var hexcase = 0;
    var b64pad = "";
    var chrsz = 8;
    
    function hex_md5(s){
        return binl2hex(core_md5(str2binl(s), s.length * chrsz));
    };
    function b64_md5(s){
        return binl2b64(core_md5(str2binl(s), s.length * chrsz));
    };
    function str_md5(s){
        return binl2str(core_md5(str2binl(s), s.length * chrsz));
    };
    function hex_hmac_md5(key, data){
        return binl2hex(core_hmac_md5(key, data));
    };
    function b64_hmac_md5(key, data){
        return binl2b64(core_hmac_md5(key, data));
    };
    function str_hmac_md5(key, data){
        return binl2str(core_hmac_md5(key, data));
    };
    
    
    function md5_vm_test(){
        return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
    };
    
    function core_md5(x, len){
        /* append padding */
        x[len >> 5] |= 0x80 << ((len) % 32);
        x[(((len + 64) >>> 9) << 4) + 14] = len;
        
        var a = 1732584193;
        var b = -271733879;
        var c = -1732584194;
        var d = 271733878;
        
        for (var i = 0; i < x.length; i += 16) {
            var olda = a;
            var oldb = b;
            var oldc = c;
            var oldd = d;
            
            a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
            d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
            c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
            b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
            a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
            d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
            c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
            b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
            a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
            d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
            c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
            b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
            a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
            d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
            c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
            b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
            
            a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
            d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
            c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
            b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
            a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
            d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
            c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
            b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
            a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
            d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
            c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
            b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
            a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
            d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
            c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
            b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
            
            a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
            d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
            c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
            b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
            a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
            d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
            c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
            b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
            a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
            d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
            c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
            b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
            a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
            d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
            c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
            b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
            
            a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
            d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
            c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
            b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
            a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
            d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
            c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
            b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
            a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
            d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
            c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
            b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
            a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
            d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
            c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
            b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
            
            a = safe_add(a, olda);
            b = safe_add(b, oldb);
            c = safe_add(c, oldc);
            d = safe_add(d, oldd);
        }
        return Array(a, b, c, d);
        
    };
    
    function md5_cmn(q, a, b, x, s, t){
        return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
    };
    function md5_ff(a, b, c, d, x, s, t){
        return md5_cmn((b & c) | ((~ b) & d), a, b, x, s, t);
    };
    function md5_gg(a, b, c, d, x, s, t){
        return md5_cmn((b & d) | (c & (~ d)), a, b, x, s, t);
    };
    function md5_hh(a, b, c, d, x, s, t){
        return md5_cmn(b ^ c ^ d, a, b, x, s, t);
    };
    function md5_ii(a, b, c, d, x, s, t){
        return md5_cmn(c ^ (b | (~ d)), a, b, x, s, t);
    };
    
    function core_hmac_md5(key, data){
        var bkey = str2binl(key);
        if (bkey.length > 16) 
            bkey = core_md5(bkey, key.length * chrsz);
        
        var ipad = Array(16), opad = Array(16);
        for (var i = 0; i < 16; i++) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
        
        var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
        return core_md5(opad.concat(hash), 512 + 128);
    };
    
    
    function safe_add(x, y){
        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    };
    
    function bit_rol(num, cnt){
        return (num << cnt) | (num >>> (32 - cnt));
    };
    
    function str2binl(str){
        var bin = Array();
        var mask = (1 << chrsz) - 1;
        for (var i = 0; i < str.length * chrsz; i += chrsz) 
            bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
        return bin;
    };
    
    function binl2str(bin){
        var str = "";
        var mask = (1 << chrsz) - 1;
        for (var i = 0; i < bin.length * 32; i += chrsz) 
            str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask);
        return str;
    };
    
    function binl2hex(binarray){
        var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i++) {
            str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
            hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
        }
        return str;
    };
    
    function binl2b64(binarray){
        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i += 3) {
            var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) |
            (((binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) |
            ((binarray[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF);
            for (var j = 0; j < 4; j++) {
                if (i * 8 + j * 6 > binarray.length * 32) 
                    str += b64pad;
                else 
                    str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
            }
        }
        return str;
    };
})();
