function ApiClient(origin, timeout, unauthorized, prefix) {

    var _origin = "";
    var _timeout = 30000;
    var _prefix = "";
    var _unauthorized = null;
    var _failed = [];

    if (typeof origin == "string") {
        while (origin && origin.substring(origin.length - 1) == "/") {
            origin = origin.substring(0, origin.length - 1);
        }
        _origin = origin;
    }

    // 调用超时毫秒数
    if (typeof timeout == "number" && timeout > 0) _timeout = timeout;

    // localStorage 和 sessionStorage 存储项的名称前缀
    if (typeof prefix == "string" && prefix) _prefix = prefix + "|";

    function uuid(hyphenation) {
        var array = [];
        var hex = "0123456789abcdef";
        for (var i = 0; i < 36; i++) array[i] = hex.substr(Math.floor(Math.random() * 16), 1);
        array[14] = "4";
        array[19] = hex.substr((array[19] & 0x3) | 0x8, 1);
        array[8] = array[13] = array[18] = array[23] = "-";
        var result = array.join("");
        if (hyphenation !== true) result = result.replace(/-/gm, "");
        return result;
    }

    function getLocalStorage(_key) {
        var value = localStorage.getItem(_key);
        if (typeof value == "string") {
            if (value.length < 1) {
                localStorage.removeItem(_key);
                value = null;
            }
        }
        return value;
    }

    function onFailed(callback) {
        if (typeof callback == "function") {
            _failed.push(callback);
        }
    }

    function onUnauthorized(callback) {
        if (typeof callback == "function") {
            _unauthorized = callback;
        } else {
            _unauthorized = nul;
        }
    }

    function setLocalStorage(_key, _value) {
        var value = _value;
        if (typeof value == "string") {
            if (value.length < 1) value = null;
        } else value = null;
        if (value == null) {
            var current = localStorage.getItem(_key);
            if (typeof current == "string") localStorage.removeItem(_key);
        } else localStorage.setItem(_key, value);
    }

    function getSession() {
        var session = getLocalStorage(_prefix + "account-session");
        if (session && typeof session == "string") return session;
        session = uuid(false);
        setLocalStorage(_prefix + "account-session", session);
        return session;
    }

    function getTicket() {
        return getLocalStorage(_prefix + "account-ticket");
    }

    function setTicket(value) {
        return setLocalStorage(_prefix + "account-ticket", value);
    }

    function getOrigin() {
        var protocol = window.location.protocol;
        var hostname = window.location.hostname;
        return _origin;
    }

    function getUrl(path) {
        if (!path) return "/";
        if (path.length >= 2 && path.substring(0, 2) == "//") {
            return path;
        }

        if (path.length >= 7 && path.substring(0, 7) == "http://") {
            return path;
        }

        if (path.length >= 8 && path.substring(0, 8) == "https://") {
            return path;
        }

        if (path[0] != "/") path = "/" + path;
        return _origin + path;
    }

    function raiseSucceed(succeed, json, resolve) {
        if (typeof succeed == "function") {
            succeed(json);
            return;
        }
    }

    function raiseFailed(failed, json, reject) {
        if (typeof failed == "function") {
            failed(json);
            return;
        }

        for (var i = 0; i < _failed.length; i++) {
            _failed[i](json);
        }
    }

    function raiseUnauthorized(failed, json, reject) {
        if (typeof _unauthorized == "function") {
            _unauthorized(json);
            return;
        }

        raiseFailed(failed, json, reject);
    }

    function onReadyStateChange(xhr, succeed, failed, resolve, reject) {
        if (xhr.readyState != 4) return;

        showLoading(false);
        if ((xhr.status == 200) || (xhr.status == 304)) {
            var response = xhr.responseText;
            var json = null;
            try {
                json = JSON.parse(response);
            } catch (ex) { }
            if (json) {
                switch (json.status) {
                    case "ok":
                        raiseSucceed(succeed, json, resolve);
                        break;
                    case "unauthorized":
                        raiseUnauthorized(failed, json, reject);
                        break;
                    default:
                        raiseFailed(failed, json, reject);
                        break;
                }
            } else {
                json = { status: "text", message: "Invalid Response", text: response, xhr: xhr };
                raiseFailed(failed, json, reject);
            }
        } else {
            if (typeof failed == "function") {
                if (xhr.status) {
                    var json = { status: "failed", message: "Request failed", xhr: xhr };
                    raiseFailed(failed, json, reject);
                } else {
                    var json = { status: "failed", message: "Invalid status " + String(xhr.status), xhr: xhr };
                    raiseFailed(failed, json, reject);
                }
            }
        }
    }

    function get(url, succeed, failed, timeout) {
        // var origin = getOrigin();
        // while (origin && origin.substring(origin.length - 1) == "/") origin = origin.substring(0, origin.length - 1);

        if (url && url.substring(0, 1) != "/") url = "/" + url;
        // if (data) {
        //     for (var i in data) {
        //         url = url + ((url.indexOf("?") < 0) ? "?" : "&");
        //         url = url + encodeURIComponent(i);
        //         url = url + "=";
        //         url = url + encodeURIComponent(data[i]);
        //     }
        // }
        var ticket = getTicket();
        if (ticket) {
            url = url + ((url.indexOf("?") < 0) ? "?" : "&");
            url += "ticket=" + ticket;
        }

        showLoading(true);
        var xhr = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
        xhr.open("GET", url, true);
        if (timeout) xhr.timeout = timeout;
        else if (_timeout) xhr.timeout = _timeout;
        xhr.onreadystatechange = function () {
            onReadyStateChange(xhr, succeed, failed);
        };
        try {
            xhr.send();
        } catch (ex) {
            if (typeof failed == "function") failed({ status: "failed", message: ex.message, xhr: xhr });
        }

        return xhr;
    }

    function post(url, data, succeed, failed, timeout) {
        // var origin = getOrigin();
        // while (origin && origin.substring(origin.length - 1) == "/") origin = origin.substring(0, origin.length - 1);
        if (url && url.substring(0, 1) != "/") url = "/" + url;

        var content = {
            "ticket": getTicket(),
            "page": window.location.href,
            "session": getSession(),
            "data": data,
        };

        showLoading(true);
        var xhr = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
        xhr.open("POST", url, true);
        if (timeout) xhr.timeout = timeout;
        else if (_timeout) xhr.timeout = _timeout;
        xhr.setRequestHeader("Content-Type", "text/plain");
        xhr.onreadystatechange = function () {
            onReadyStateChange(xhr, succeed, failed);
        };
        try {
            xhr.send(JSON.stringify(content));
        } catch (ex) {
            if (typeof failed == "function") failed({ status: "failed", message: ex.message, xhr: xhr });
        }
        return xhr;
    }

    return {
        onUnauthorized: onUnauthorized,
        onFailed: onFailed,
        getOrigin: getOrigin,
        get: get,
        post: post,
        getTicket: getTicket,
        setTicket: setTicket,
        getSession: getSession
    };
}
