# graphics/graphics.js

    'use strict';

    // Try to remove dependence on window bound JavaScript
    var editorUtils = require('chs-js-utils');
    var $ = require('jquery');

    window.CHS = window.CHS || {};
    CHS.log = CHS.log || function() {};
    // CHS.Utils = CHS.Utils || { assert: function() {} };

    // How often to redraw the display
    var DEFAULT_FRAME_RATE = 40;

    // String list of methods that will be accessible
    // to the user
    var PUBLIC_METHODS = [];
    var PUBLIC_CONSTRUCTORS = [];

    // Pressed keys are actually maintained acorss all
    // graphics instances since there is only one keyboard.
    var pressedKeys = [];

    // Keep track of all graphics instances.
    var allGraphicsInstances = [];
    var graphicsInstanceId = 0;

    /**
     * Set up an instance of the graphics library.
     * @constructor
     * @param {dictionary} options - Options, primarily .canvas, the selector
     *      string for the canvas.
     *      If multiple are returned, we'll take the first one.
     *      If none is passed, we'll look for any canvas
     *      tag on the page.
     */
    function CodeHSGraphics(options) {
        options = options || {};
        this.resetAllState();

        this.globalTimer = true;
        this.currentCanvas = null;
        this.setCurrentCanvas(options.canvas);

        // Are we in debug mode? The default is false.
        this.debugMode = options.debug || false;

        // Since we now have multiple instances of the graphics object
        // give each one a unique id
        this.instanceId = graphicsInstanceId;
        graphicsInstanceId++;

        // override any graphics instance that is already using this ID.
        // if there aren't any, just push this instance onto the end.
        var existingId = this.canvasHasInstance(options.canvas);
        if (existingId !== null) {
            var existingGraphics = allGraphicsInstances[existingId];
            existingGraphics.stopTimer('MAIN_TIMER');
            allGraphicsInstances[existingId] = this;
        } else {
            allGraphicsInstances.push(this);
        };
    }

    /**
     * Adds a method to the public methods constant.
     * @param {string} name - Name of the method.
     */
    CodeHSGraphics.registerPublicMethod = function(name) {
        PUBLIC_METHODS.push(name);
    };

    /**
     * Adds a constructor to the public constructors constant.
     * @param {string} name - Name of the object to be constructed.
     */
     CodeHSGraphics.registerConstructorMethod = function(name) {
        PUBLIC_CONSTRUCTORS.push(name);
    };

    /**
     * Generate strings for the public methods to bring them to the
     * public namespace without having to call them with the graphics instance.
     * @returns {string} Line broken function definitions.
     */
    CodeHSGraphics.getNamespaceModifcationString = function() {
        var result = "\n";
        for(var i = 0; i < PUBLIC_METHODS.length; i++){
            var curMethod = PUBLIC_METHODS[i];

            // Actually create a method in this scope with the name of the
            // method so the student can easily access it. For example, we
            // might have a method like CodeHSGraphics.prototype.add, but we
            // want the student to be able to access it with just `add`, but
            // the proper context for this.
            result += "function " + curMethod + "(){\n"+
                "\treturn __graphics__." + curMethod + ".apply(__graphics__, arguments);\n" +
                "}\n";

            // result += "var " +  curMethod + " = __graphics__." + curMethod + ";\n";
        }
        return result;
    };

    /**
     * Generate strings for the public constructors to bring them to the
     * public namespace without having to call them with the graphics instance.
     * @returns {string} Line broken constructor declarations.
     */
    CodeHSGraphics.getConstructorModificationString = function() {
        var result = "";
        for (var i = 0; i < PUBLIC_CONSTRUCTORS.length; i++) {
            var curMethod = PUBLIC_CONSTRUCTORS[i];

            result += "var " + curMethod + " = __graphics__." + curMethod + ";\n";
        }
        return result;
    };

    /*************** PUBLIC METHODS *******************/

    /**
     * Add an element to the graphics instance.
     * @param {Thing} elem - A subclass of Thing to be added to the graphics instance.
     */
    CodeHSGraphics.prototype.add = function(elem) {
        this.elements.push(elem);
    };
    CodeHSGraphics.registerPublicMethod('add');

    /**
     * Record a click.
     */
    CodeHSGraphics.prototype.waitForClick = function() {
        this.clickCount++;
    };
    CodeHSGraphics.registerPublicMethod('waitForClick');

    /**
     * Assign a function as a callback for click (mouse down, mouse up) events.
     * @param {function} fn - A callback to be triggered on click events.
     */
    CodeHSGraphics.prototype.mouseClickMethod = function(fn) {
        this.clickCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('mouseClickMethod');

    /**
     * Assign a function as a callback for mouse move events.
     * @param {function} fn - A callback to be triggered on mouse move events.
     */
    CodeHSGraphics.prototype.mouseMoveMethod = function(fn) {
        this.moveCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('mouseMoveMethod');

    /**
     * Assign a function as a callback for mouse down events.
     * @param {function} fn - A callback to be triggered on mouse down.
     */
    CodeHSGraphics.prototype.mouseDownMethod = function(fn) {
        this.mouseDownCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('mouseDownMethod');

    /**
     * Assign a function as a callback for mouse up events.
     * @param {function} fn - A callback to be triggered on mouse up events.
     */
    CodeHSGraphics.prototype.mouseUpMethod = function(fn) {
        this.mouseUpCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('mouseUpMethod');

    /**
     * Assign a function as a callback for drag events.
     * @param {function} fn - A callback to be triggered on drag events.
     */
    CodeHSGraphics.prototype.mouseDragMethod = function(fn) {
        this.dragCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('mouseDragMethod');

    /**
     * Assign a function as a callback for keydown events.
     * @param {function} fn - A callback to be triggered on keydown events.
     */
    CodeHSGraphics.prototype.keyDownMethod = function(fn) {
        this.keyDownCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('keyDownMethod');

    /**
     * Assign a function as a callback for key up events.
     * @param {function} fn - A callback to be triggered on key up events.
     */
    CodeHSGraphics.prototype.keyUpMethod = function(fn) {
        this.keyUpCallback = editorUtils.safeCallback(fn);
    };
    CodeHSGraphics.registerPublicMethod('keyUpMethod');

    /**
     * Check if a key is currently pressed
     * @param {integer} keyCode - Key code of key being checked.
     * @returns {boolean} Whether or not that key is being pressed.
     */
    CodeHSGraphics.prototype.isKeyPressed = function(keyCode) {
        return pressedKeys.indexOf(keyCode) != -1;
    };
    CodeHSGraphics.registerPublicMethod('isKeyPressed');

    /**
     * Get the width of the entire graphics canvas.
     * @returns {float} The width of the canvas.
     */
    CodeHSGraphics.prototype.getWidth = function() {
        var canvas = this.getCanvas();
        return parseFloat(canvas.getAttribute('width'));
    };
    CodeHSGraphics.registerPublicMethod('getWidth');

    /**
     * Get the height of the entire graphics canvas.
     * @returns {float} The height of the canvas.
     */
    CodeHSGraphics.prototype.getHeight = function() {
        var canvas = this.getCanvas();
        return parseFloat(canvas.getAttribute('height'));
    };
    CodeHSGraphics.registerPublicMethod('getHeight');

    /**
     * Remove a timer associated with a function.
     * @param {function} fn - Function whose timer is removed.
     * note 'fn' may also be the name of the function.
     */
    CodeHSGraphics.prototype.stopTimer = function(fn) {
        var key = (typeof fn == "function" ? fn.name : fn);
        clearInterval(this.timers[key]);
    };
    CodeHSGraphics.registerPublicMethod('stopTimer');

    /**
     * Stop all timers.
     */
    CodeHSGraphics.prototype.stopAllTimers = function() {
        for (var i = 1; i < 99999; i++){
            window.clearInterval(i);
        }
        this.setMainTimer();
    };
    CodeHSGraphics.registerPublicMethod('stopAllTimers');

    /**
     * Create a new timer
     * @param {function} fn - Function to be called at intervals.
     * @param {integer} time - Time interval to call function `fn`
     * @param {dictionary} data - Any data associated with the timer.
     * @param {string} name - Name of this timer.
     */
    CodeHSGraphics.prototype.setTimer = function(fn, time, data, name) {
        // TODO: Throw error if fn not a function
        // TODO: Throw error if time not a int
        var self = this;

        // Safety, set a min frequency
        if(isNaN(time) || time < 15){
            time = 15;
        }

        if(this.waitingForClick()){
            this.delayedTimers.push({
                fn: fn,
                time: time,
                data: data,
                clicks: self.clickCount,
                name: name
            });
        }else{
            this.setGraphicsTimer(fn, time, data, name);
        }
    };
    CodeHSGraphics.registerPublicMethod('setTimer');

    /**
     * Set the background color of the canvas.
     * @param {Color} color - The desired color of the canvas.
     */
    CodeHSGraphics.prototype.setBackgroundColor = function(color) {
        this.backgroundColor = color;
    };
    CodeHSGraphics.registerPublicMethod('setBackgroundColor');

    /**
     * Clear everything from the canvas.
     */
    CodeHSGraphics.prototype.clear = function(context) {
        var ctx = context || this.getContext();
        ctx.clearRect(0, 0, this.getWidth(), this.getHeight());
    };
    CodeHSGraphics.registerPublicMethod('clear');

    /**
     * Get an element at a specific point.
     * If several elements are present at the position, return the one put there first.
     * @param {number} x - The x coordinate of a point to get element at.
     * @param {number} y - The y coordinate of a point to get element at.
     * @returns {Thing|null} The object at the point (x, y), if there is one (else null).
     */
    CodeHSGraphics.prototype.getElementAt = function(x, y) {
        for(var i = this.elements.length -1; i >= 0; i--){
            if(this.elements[i].containsPoint(x,y))
                return this.elements[i];
        }
        return null;
    };
    CodeHSGraphics.registerPublicMethod('getElementAt');

    /**
     * Check if an element exists with the given paramenters.
     * @param {object} params - Dictionary of parameters for the object.
     *      Includes x, y, heigh, width, color, radius, label and type.
     * @returns {boolean}
     */
    CodeHSGraphics.prototype.elementExistsWithParameters = function(params) {
        for(var i = this.elements.length - 1; i >= 0; i--){
            var elem = this.elements[i];
            try {
                if (params.x !== undefined && this.runCode('return ' + params.x).toFixed(0) !== elem.getX().toFixed(0)) {
                    continue;
                }
                if (params.y !== undefined && this.runCode('return ' + params.y).toFixed(0) !== elem.getY().toFixed(0)) {
                    continue;
                }

                if (params.width !== undefined && this.runCode('return ' + params.width).toFixed(0) !== elem.getWidth().toFixed(0)) {
                    continue;
                }

                if (params.height !== undefined && this.runCode('return ' + params.height).toFixed(0) !== elem.getHeight().toFixed(0)) {
                    continue;
                }

                if (params.radius !== undefined && this.runCode('return ' + params.radius).toFixed(0) !== elem.getRadius().toFixed(0)) {
                    continue;
                }

                if (params.color !== undefined && this.runCode('return ' + params.color) !== elem.getColor()) {
                    continue;
                }

                if (params.label !== undefined && params.label !== elem.getLabel()) {
                    continue;
                }

                if (params.type !== undefined && params.type !== elem.getType()) {
                    continue;
                }
            } catch(err) {
                continue;
            }
            return true;
        }
        return false;
    };
    CodeHSGraphics.registerPublicMethod('elementExistsWithParameters');

    /**
     * Remove all elements from the canvas.
     */
    CodeHSGraphics.prototype.removeAll = function() {
        this.elements = [];
    };
    CodeHSGraphics.registerPublicMethod('removeAll');

    /**
     * Remove a specific element from the canvas.
     * @param {Thing} elem - The element to be removed from the canvas.
     */
    CodeHSGraphics.prototype.remove = function(elem) {
        for(var i = 0; i < this.elements.length; i++){
            if(this.elements[i] == elem){
                this.elements.splice(i, 1); // Remove from list
            }
        }
    };
    CodeHSGraphics.registerPublicMethod('remove');

    /**
     * Set the size of the canvas.
     * @param {number} w - Desired width of the canvas.
     * @param {number} h - Desired height of the canvas.
     */
    CodeHSGraphics.prototype.setSize = function(w, h) {
        this.setSize(w, h);
    };
    CodeHSGraphics.registerPublicMethod('setSize');

    /****************** SHAPE CONSTRUCTORS **************/

    // Insertion point for graphics modules.

    CodeHSGraphics.prototype.Rectangle = require('./rectangle.js');
    CodeHSGraphics.registerConstructorMethod('Rectangle');

    CodeHSGraphics.prototype.Circle = require('./circle.js');
    CodeHSGraphics.registerConstructorMethod('Circle');

    CodeHSGraphics.prototype.Line = require('./line.js');
    CodeHSGraphics.registerConstructorMethod('Line');

    CodeHSGraphics.prototype.Grid = require('./grid.js');
    CodeHSGraphics.registerConstructorMethod('Grid');

    CodeHSGraphics.prototype.Line = require('./line.js');
    CodeHSGraphics.registerConstructorMethod('Line');

    CodeHSGraphics.prototype.Polygon = require('./polygon.js');
    CodeHSGraphics.registerConstructorMethod('Polygon');

    CodeHSGraphics.prototype.Text = require('./text.js');
    CodeHSGraphics.registerConstructorMethod('Text');

    CodeHSGraphics.prototype.Oval = require('./oval.js');
    CodeHSGraphics.registerConstructorMethod('Oval');

    CodeHSGraphics.prototype.Arc = require('./arc.js');
    CodeHSGraphics.registerConstructorMethod('Arc');

    CodeHSGraphics.prototype.Color = require('./color.js');
    CodeHSGraphics.registerConstructorMethod('Color');

    /****************** PRIVATE METHODS *****************/

    /**
     * This is how you run the code, but get access to the
     * state of the graphics library. The current instance
     * becomes accessible in the code.
     * @param {string} code - The code from the editor.
     */
    CodeHSGraphics.prototype.runCode = function(code) {
        console.log("RUNNING THE CODE");

        var getPublicMethodString = CodeHSGraphics.getNamespaceModifcationString();
        var getConstructorModificationString =
            CodeHSGraphics.getConstructorModificationString();

        var wrap = ''

        // Give the user easy access to public graphics methods
        // in the proper context.
        wrap += getPublicMethodString;
        wrap += getConstructorModificationString;

        // Text objects need access to some 2d graphics context to compute
        // height and width. This might be done before a draw call.
        wrap += "\nText.giveDefaultContext(__graphics__);\n";

        // User code.
        wrap += code;

        // Call the start function
        wrap += '\n\nif(typeof start == \'function\') {start();} ';

        return editorUtils.safeEval(wrap, this, '__graphics__');

        // console.log(this);
    };

    /**
     * Resets all the timers to time 0.
     */
    CodeHSGraphics.prototype.resetAllTimers = function() {
        for(var cur in this.timers){
            clearInterval(this.timers[cur]);
        }
    };

    /**
     * Resets the graphics instance to a clean slate.
     */
    CodeHSGraphics.prototype.resetAllState = function() {
        this.backgroundColor = null;
        this.elements = [];
        this.clickCallback = null;
        this.moveCallback = null;
        this.mouseDownCallback = null;
        this.mouseUpCallback = null;
        this.dragCallback = null;
        this.keyDownCallback = null;
        this.keyUpCallback = null;

        // A fast hash from timer key to timer interval #
        this.timers = {};

        // A useful list to store information about all timers.
        this.timersList = [];

        this.clickCount = 0;
        this.delayedTimers = [];
    };

    /**
     * Reset all timers to 0 and clear timers and canvas.
     */
    CodeHSGraphics.prototype.fullReset = function() {

        this.resetAllTimers();
        this.resetAllState();

        /* THIS LINE OF CODE. Leave it commented out.
         * If we override this setting ( like we do in karel)
         * it shouldn't be reset to true. */
        // this.globalTimer = true;
        this.setMainTimer();
    };

    /**
     * Return if the graphics canvas exists.
     * @returns {boolean} Whether or not the canvas exists.
     */
    CodeHSGraphics.prototype.canvasExists = function() {
        return this.getCanvas() !== null;
    };

    /**
     * Return the current canvas we are using. If there is no
     * canvas on the page this will return null.
     * @returns {object} The current canvas.
     */
    CodeHSGraphics.prototype.getCanvas = function() {
        return this.currentCanvas;
    };

    /**
     * Set the current canvas we are working with. If no canvas
     * tag matches the selectorv then we will just have the current
     * canvas set to null.
     * @param {string} canvasSelector - String representing canvas class or ID.
     *      Selected with jQuery.
     */
    CodeHSGraphics.prototype.setCurrentCanvas = function(canvasSelector) {

        /* If we were passed a selector, get the first matching
         * element. */
        if(canvasSelector){
            this.currentCanvas = $(canvasSelector)[0];
        }else{
            this.currentCanvas = document.getElementsByTagName('canvas')[0];
        }

        // If it is a falsey value like undefined, set it to null.
        if(!this.currentCanvas){
            this.currentCanvas = null;
        }

        // On changing the canvas reset the state.
        this.fullReset();
        this.setup();
    };

    /**
     * Stop the global timer
     */
    CodeHSGraphics.prototype.stopGlobalTimer = function() {
        this.globalTimer = false;
    };

    /**
     * Draw the background color for the current object.
     */
    CodeHSGraphics.prototype.drawBackground = function() {
        if(this.backgroundColor){
            var context = this.getContext();
            context.fillStyle = this.backgroundColor;
            context.beginPath();
            context.rect(0, 0, this.getWidth(), this.getHeight());
            context.closePath();
            context.fill();
        }
    };

    /**
     * Return the 2D graphics context for this graphics
     * object, or null if none exists.
     * @returns {context} The 2D graphics context.
     */
    CodeHSGraphics.prototype.getContext = function() {
        var drawingCanvas = this.getCanvas();
        // Check the element is in the DOM and the browser supports canvas
        if(drawingCanvas && drawingCanvas.getContext) {
            // Initaliase a 2-dimensional drawing context
            var context = drawingCanvas.getContext('2d');
            return context;
        }
        return null;
    };

    /**
     * Redraw this graphics canvas.
     */
    CodeHSGraphics.prototype.redraw = function() {
        this.clear();
        this.drawBackground();
        for(var i = 0; i < this.elements.length; i++){
            this.elements[i].draw(this);
        }
    };

    /**
     * Set the main timer for graphics.
     */
    CodeHSGraphics.prototype.setMainTimer = function() {
        var self = this;
        /* Refresh the screen every 40 ms */
        if(this.globalTimer){
            this.setTimer(function(){
                self.redraw();
            }, DEFAULT_FRAME_RATE, null, "MAIN_TIMER");
        }
    };

    /**
     * Set the size of the canvas.
     * This is a re-declaration so I'm leaving it commented out.
    CodeHSGraphics.prototype.setSize = function(width, height) {
        var canvas = this.getCanvas();
        canvas.width = width;
        canvas.height = height;
    };
    */

    /**
     * Whether the graphics instance is waiting for a click.
     * @returns {boolean} Whether or not the instance is waiting for a click.
     */
    CodeHSGraphics.prototype.waitingForClick = function() {
        return this.clickCount !== 0;
    };

    /**
     * Whether the selected canvas already has an instance associated.
     */
     CodeHSGraphics.prototype.canvasHasInstance = function(canvas) {
        var instance;
        for (var i = 0; i < allGraphicsInstances.length; i++) {
            instance = allGraphicsInstances[i];
            if ((instance.instanceId !== this.instanceId) &&
                    instance.getCanvas() === canvas) {
                return instance.instanceId;
            };
        }
        return null;
     }

    /**
     * Get the distance between two points, (x1, y1) and (x2, y2)
     * @param {number} x1
     * @param {number} y1
     * @param {number} x2
     * @param {number} y2
     * @returns {number} Distance between the two points.
     */
    CodeHSGraphics.prototype.getDistance = function(x1, y1, x2, y2) {
        return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
    };

    /**
     * Set up the graphics instance to prepare for interaction
     */
    CodeHSGraphics.prototype.setup = function() {
        var self = this;

        var drawingCanvas = this.getCanvas();

        // self.setMainTimer();

        drawingCanvas.onclick = function(e) {
            console.log('click');
            if(self.waitingForClick()){
                self.clickCount--;

                for(var i = 0; i < self.delayedTimers.length; i++){
                    var timer = self.delayedTimers[i];
                    timer.clicks--;
                    if(timer.clicks === 0){
                        self.setGraphicsTimer(timer.fn, timer.time, timer.data);
                    }
                }
                return;
            }

            if(self.clickCallback){
                self.clickCallback(e);
            }
        };

        var mouseDown = false;

        drawingCanvas.onmousemove = function(e) {
            if(self.moveCallback){
                self.moveCallback(e);
            }
            if(mouseDown && self.dragCallback){
                self.dragCallback(e);
            }
        };

        drawingCanvas.onmousedown = function(e) {
            mouseDown = true;
            if(self.mouseDownCallback){
                self.mouseDownCallback(e);
            }
        };

        drawingCanvas.onmouseup = function(e) {
            mouseDown = false;
            if(self.mouseUpCallback){
                self.mouseUpCallback(e);
            }
        };

        // TOUCH EVENTS!
        drawingCanvas.ontouchmove = function(e) {
            e.preventDefault();
            if(self.dragCallback){
                self.dragCallback(e);
            } else if(self.moveCallback){
                self.moveCallback(e);
            }
        };

        drawingCanvas.ontouchstart = function(e) {
            e.preventDefault();
            if(self.mouseDownCallback){
                self.mouseDownCallback(e);
            } else if(self.clickCallback){
                self.clickCallback(e);
            }

            if(self.waitingForClick()){
                self.clickCount--;

                for(var i = 0; i < self.delayedTimers.length; i++){
                    var timer = self.delayedTimers[i];
                    timer.clicks--;
                    if(timer.clicks === 0){
                        self.setGraphicsTimer(timer.fn, timer.time, timer.data);
                    }
                }
                return;
            }
        };

        drawingCanvas.ontouchend = function(e) {
            e.preventDefault();
            if(self.mouseUpCallback){
                self.mouseUpCallback(e);
            }
        };
    };

    /**
     * Set a graphics timer.
     * @param {function} fn - The function to be executed on the timer.
     * @param {number} time - The time interval for the function.
     * @param {object} data - Any arguments to be passed into `fn`.
     * @param {string} name - The name of the timer.
     */
    CodeHSGraphics.prototype.setGraphicsTimer = function(fn, time, data, name) {
        if(typeof name === "undefined") {
            name = fn.name;
        }

        this.timers[name] = editorUtils.safeSetInterval(fn, data, time);

        this.timersList.push({
            name: name,
            fn: fn,
            data: data,
            time: time
        });
    };

    /** KEY EVENTS ****/
    window.onkeydown = function(e) {
        var index = pressedKeys.indexOf(e.keyCode);
        if (index === -1) {
            pressedKeys.push(e.keyCode);
        }

        var toReturn;
        // Any graphics instance might need to respond to key events.
        for(var i = 0; i < allGraphicsInstances.length; i++){
            var curInstance = allGraphicsInstances[i];

            if(curInstance.keyDownCallback){
                curInstance.keyDownCallback(e);
                // Override the default behavior of certain keys
                // Jeremy: Unfortunately, I'm not sure what the default behavior
                // was, or what this update does... or what depends on this.
                toReturn = true;
                if (e.keyCode == Keyboard.SPACE) {
                    toReturn = false;
                }
                if (e.keyCode >= Keyboard.LEFT && e.keyCode <= Keyboard.DOWN) {
                    toReturn = false;
                }
            }
        }

        return toReturn;
    };

    window.onkeyup = function(e) {
        var index = pressedKeys.indexOf(e.keyCode);
        if (index !== -1) {
            pressedKeys.splice(index,1);
        }

        // Any graphics instance might need to respond to key events.
        for(var i = 0; i < allGraphicsInstances.length; i++){
            var curInstance = allGraphicsInstances[i];
            if(curInstance.keyUpCallback){
                curInstance.keyUpCallback(e);
            }
        }
    };

    /* Mouse Event Helpers */

    /* Method based on: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element */
    CodeHSGraphics.getMouseCoordinates = function(e) {
        var x;
        var y;
        if (e.pageX || e.pageY) {
          x = e.pageX;
          y = e.pageY;
        }
        else {
          x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
          y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
        }

        x -= e.currentTarget.offsetLeft;
        y -= e.currentTarget.offsetTop;

        return {x: x, y: y};
    };

    CodeHSGraphics.getTouchCoordinates = CodeHSGraphics.getMouseCoordinates;

    MouseEvent.prototype.getX = function() {
        return CodeHSGraphics.getMouseCoordinates(this).x;
    };

    MouseEvent.prototype.getY = function() {
        return CodeHSGraphics.getMouseCoordinates(this).y;
    };

    if(typeof TouchEvent != "undefined") {
        TouchEvent.prototype.getX = function() {
            return CodeHSGraphics.getTouchCoordinates(self.touches[0]).x;
        };
        TouchEvent.prototype.getY = function() {
            return CodeHSGraphics.getTouchCoordinates(self.touches[0]).y;
        };
    }

    module.exports = {
        CodeHSGraphics: CodeHSGraphics,
        PUBLIC_METHODS: PUBLIC_METHODS,
        PUBLIC_CONSTRUCTORS: PUBLIC_CONSTRUCTORS
    }