/*
 *  Guacamole - Pure JavaScript/HTML VNC Client
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see .
 */
function VNCClient(display) {
    var STATE_IDLE          = 0;
    var STATE_CONNECTING    = 1;
    var STATE_WAITING       = 2;
    var STATE_CONNECTED     = 3;
    var STATE_DISCONNECTING = 4;
    var STATE_DISCONNECTED  = 5;
    var currentState = STATE_IDLE;
    var stateChangeHandler = null;
    function setState(state) {
        if (state != currentState) {
            currentState = state;
            if (stateChangeHandler)
                stateChangeHandler(currentState);
        }
    }
    this.setOnStateChangeHandler = function(handler) {
        stateChangeHandler = handler;
    }
    function isConnected() {
        return currentState == STATE_CONNECTED
            || currentState == STATE_WAITING;
    }
    var keyIndex = 0;
    var xmlIndex = 0;
    // Layers
    var background = null;
    var cursor = null;
    var cursorImage = null;
    var cursorHotspotX = 0;
    var cursorHotspotY = 0;
    // FIXME: Make object. Clean up.
    var cursorRectX = 0;
    var cursorRectY = 0;
    var cursorRectW = 0;
    var cursorRectH = 0;
    var cursorHidden = 0;
    function redrawCursor() {
        // Hide hardware cursor
        if (cursorHidden == 0) {
            display.className += " hideCursor";
            cursorHidden = 1;
        }
        // Erase old cursor
        cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);
        // Update rect
        cursorRectX = mouse.getX() - cursorHotspotX;
        cursorRectY = mouse.getY() - cursorHotspotY;
        cursorRectW = cursorImage.width;
        cursorRectH = cursorImage.height;
        // Draw new cursor
        cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
    }
	/*****************************************/
	/*** Keyboard                          ***/
	/*****************************************/
    var keyboard = new GuacamoleKeyboard(document);
    this.disableKeyboard = function() {
        keyboard.setKeyPressedHandler(null);
        keyboard.setKeyReleasedHandler(null);
    };
    this.enableKeyboard = function() {
        keyboard.setKeyPressedHandler(
            function (keysym) {
                sendKeyEvent(1, keysym);
            }
        );
        keyboard.setKeyReleasedHandler(
            function (keysym) {
                sendKeyEvent(0, keysym);
            }
        );
    };
    // Enable keyboard by default
    this.enableKeyboard();
    function sendKeyEvent(pressed, keysym) {
        // Do not send requests if not connected
        if (!isConnected())
            return;
        var key_xmlhttprequest = new XMLHttpRequest();
        key_xmlhttprequest.open("GET",
            "key?index=" + (keyIndex++)
                + "&pressed=" + pressed
                + "&keysym=" + keysym);
        key_xmlhttprequest.send(null);
    }
    this.pressKey = function(keysym) {
        sendKeyEvent(1, keysym);
    };
    this.releaseKey = function(keysym) {
        sendKeyEvent(0, keysym);
    };
	/*****************************************/
	/*** Mouse                             ***/
	/*****************************************/
    var mouse = new GuacamoleMouse(display);
    mouse.setButtonPressedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );
    mouse.setButtonReleasedHandler(
        function(mouseState) {
            sendMouseState(mouseState);
        }
    );
    mouse.setMovementHandler(
        function(mouseState) {
            // Draw client-side cursor
            if (cursorImage != null) {
                redrawCursor();
            }
            sendMouseState(mouseState);
        }
    );
    var sendingMouseEvents = 0;
    var mouseEventBuffer = "";
    function sendMouseState(mouseState) {
        // Do not send requests if not connected
        if (!isConnected())
            return;
        // Add event to queue, restart send loop if finished.
        if (mouseEventBuffer.length > 0) mouseEventBuffer += "&";
        mouseEventBuffer += "event=" + mouseState.toString();
        if (sendingMouseEvents == 0)
            sendPendingMouseEvents();
    }
    function sendPendingMouseEvents() {
        // Do not send requests if not connected
        if (!isConnected())
            return;
        if (mouseEventBuffer.length > 0) {
            sendingMouseEvents = 1;
            var mouse_xmlhttprequest = new XMLHttpRequest();
            mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer);
            mouseEventBuffer = ""; // Clear buffer
            // Once response received, send next queued event.
            mouse_xmlhttprequest.onreadystatechange = function() {
                if (mouse_xmlhttprequest.readyState == 4)
                    sendPendingMouseEvents();
            }
            mouse_xmlhttprequest.send(null);
        }
        else
            sendingMouseEvents = 0;
    }
	/*****************************************/
	/*** Clipboard                         ***/
	/*****************************************/
    this.setClipboard = function(data) {
        // Do not send requests if not connected
        if (!isConnected())
            return;
        var clipboard_xmlhttprequest = new XMLHttpRequest();
        clipboard_xmlhttprequest.open("POST", "clipboard");
        clipboard_xmlhttprequest.send(data);
    }
    function desaturateFilter(data, width, height) {
        for (var i=0; i