function Telnet(target, connect_callback, disconnect_callback) {

var that = {},  // Public API interface
    vd, ws, sQ = [];
    termType = "Viewdata";


Array.prototype.pushStr = function (str) {
    var n = str.length;
    for (var i=0; i < n; i++) {
        this.push(str.charCodeAt(i));
    }
}

function do_send() {
    if (sQ.length > 0) {
        ws.send(sQ);
        sQ = [];
    }
}

function do_recv() {
        vd.write(ws.rQshiftStr(ws.rQlen()));
}



that.connect = function(host, port, encrypt, url) {
    var host = host,
        port = port,
        scheme = "ws://", uri;

    if ((!host) || (!port)) {
        console.log("must set host and port");
        return;
    }

    if (ws) {
        ws.close();
    }

    if (encrypt) {
        scheme = "wss://";
    }
    uri = scheme + host + ":" + port + url;

    ws.open(uri);
}

that.disconnect = function() {
    if (ws) {
        ws.close();
    }
    vd.curs_set(true, false);

    disconnect_callback();
}


function constructor() {
    /* Initialize Websock object */
    ws = new Websock();

    ws.on('message', do_recv);
    ws.on('open', function(e) {
        vd.curs_set(true, true);
        connect_callback();
    });
    ws.on('close', function(e) {
        that.disconnect();
    });
    ws.on('error', function(e) {
        that.disconnect();
    });

    /* Initialize the terminal emulator/renderer */

    vd = new VD(40, 24, target);

    /*
     * Override VD I/O routines
     */

    // Set handler for sending characters
    vd.getch(
        function send_chr(chr, vt) {
            var i;
            for (i = 0; i < chr.length; i++) {
                sQ.push(chr.charCodeAt(i));
            }
            do_send();
            vd.getch(send_chr);
        }
    );

    vd.curs_set = function(vis, grab, eventist)
    {
        if (vis !== undefined)
            this.cursor_vis_ = (vis > 0);
        if (eventist === undefined)
            eventist = window;
        if (grab === true || grab === false) {
            if (grab === this.grab_events_)
                return;
            if (grab) {
                this.grab_events_ = true;
                VD.the_vt_ = this;
                Util.addEvent(eventist, 'keydown', vd.key_down);
                Util.addEvent(eventist, 'keyup', vd.key_up);
            } else {
                Util.removeEvent(eventist, 'keydown', vd.key_down);
                Util.removeEvent(eventist, 'keyup', vd.key_up);
                this.grab_events_ = false;
                VD.the_vt_ = undefined;
            }
        }
    }

    vd.key_down = function(e) {
        var vt = VD.the_vt_, keysym, ch, str = "";

        if (vt === undefined)
            return true;

        keysym = getKeysym(e);

        if (keysym < 128) {
            if (e.ctrlKey) {
                if (keysym == 64) {
                    // control 0
                    ch = 0;
                } else if ((keysym >= 97) && (keysym <= 122)) {
                    // control codes 1-26
                    ch = keysym - 96;
                } else if ((keysym >= 91) && (keysym <= 95)) {
                    // control codes 27-31
                    ch = keysym - 64;
                }
            } else {
                ch = keysym;
                switch (ch) {
                case 34:
                    ch = 126; break;
                case 126:
                    ch = 0x40; break;
                case 64:
                    ch = 0x22; break;
                case 39:
                    ch = 0x5f; break;
                case 95:
                    ch = 0x23; break;
                case 96:
                    ch = 0x27; break;
                }
            }
            str = String.fromCharCode(ch);
        } else {
            switch (keysym) {
            case 65505: // Shift, do not send directly
                break;
            case 65507: // Ctrl, do not send directly
                break;
            case 65293: // Carriage return, line feed
                str = '_'; break;
            case 65288: // Backspace
                str = '\b'; break;
            case 65307: // Escape
                str = '\x1b'; break;
            case 65361: // Left arrow 
                str = '\x08'; break;
            case 65362: // Up arrow 
                str = '\x0b'; break;
            case 65363: // Right arrow 
                str = '\x09'; break;
            case 65364: // Down arrow 
                str = '\x0a'; break;
            }
        }

        if (str) {
            vt.key_buf_.push(str);
            setTimeout(VD.go_getch_, 0);
        }

        Util.stopEvent(e);
        return false;
    }

    vd.key_up = function(e) {
        var vt = VD.the_vt_;
        if (vt === undefined)
            return true;
        Util.stopEvent(e);
        return false;
    }


    return that;
}

return constructor(); // Return the public API interface

}
