/**
 *Stack holds all the figures on the screen
 *It will also hold the groups formed on the screen
 *@this {Stack}
 *@constructor
 **/
function Stack(){
    /**Keeps all the figures on the canvas*/
    this.figures = []; 
    
    /**Keeps all the groups in the canvas*/
    this.groups = [];
    
    this.containers = [];
    
    /**Keeps current generated Id. Not for direct access*/
    this.currentId = 0;
    
    /**Keeps a map like (figure Id, figure index). It is similar to an index*/
    this.idToIndex = [];
    
    /**Type used in serialization*/
    this.oType = 'Stack';
}



/**Creates a {Stack} out of JSON parsed object
 *@param {JSONObject} o - the JSON parsed object
 *@return {Stack} a newly constructed Stack
 *@author Alex Gheorghiu <alex@scriptoid.com>
 **/
Stack.load = function(o){
    var newStack = new Stack(); //empty constructor
    

    newStack.figures = Figure.loadArray(o.figures);
    newStack.containers = Container.loadArray(o.containers);
    newStack.groups = Group.loadArray(o.groups);
    newStack.figureSelectedIndex = o.figureSelectedIndex;
    newStack.currentId = o.currentId;
    newStack.idToIndex = o.idToIndex;

    return newStack;
}


Stack.prototype = {

    constructor: Stack,

    /**Creates a {Group} based on a set of figure IDs
    * Group is created by 1. creating a Group and 2. setting Figure's groupId property to the new id of the Group
    *@param {Array} figureIds - all the ids of {Figure}s
    *@param {Number} groupId - the id of the {Group} (optional) 
    *@return {Number} - the id of newly created Group
    **/
    groupCreate: function (figureIds, groupId) {

        //we should allow to create more than one temporary group
        for (var i = 0; i < this.groups.length; i++) {
            if (this.groups[i].permanent == false) {
                throw 'Stack::groupCreate()  You can not create new groups while you have temporary groups alive';
            }
        }

        //create group
        var g = new Group(groupId);

        //add figures to group
        for (var i = 0; i < figureIds.length; i++) {
            var f = this.figureGetById(figureIds[i]);
            f.groupId = g.id;
        }
        var bounds = g.getBounds();
        g.rotationCoords.push(new Point(bounds[0] + (bounds[2] - bounds[0]) / 2, bounds[1] + (bounds[3] - bounds[1]) / 2));
        g.rotationCoords.push(new Point(bounds[0] + (bounds[2] - bounds[0]) / 2, bounds[1]));

        //save group to STACK
        this.groups.push(g);

        return g.id;
    },


    /**Finds a {Group} by it's id
    *@param {Number} groupId - the {Group}'s id
    *@return {Group} founded group of null if none finded
    **/
    groupGetById: function (groupId) {
        for (var i = 0; i < this.groups.length; i++) {
            if (this.groups[i].id == groupId) {
                return this.groups[i];
            }
        }
        return null;
    },

    /**Destroy a group by it's Id. 
    *I will be removed from Stack's groups and any member figure will be removed
    *from group (by set the groupId to -1)
    * @param {Number} groupId - the id of the group
    **/
    groupDestroy: function (groupId) {
        var index = -1;

        //search for the group
        for (var i = 0; i < this.groups.length; i++) {
            if (this.groups[i].id == groupId) {
                index = i;
                break;
            }
        }

        //remove it
        if (index > -1) {
            //remove Group
            this.groups.splice(index, 1);

            //remove the Figures from Group
            var groupFigures = this.figureGetByGroupId(groupId);
            for (var i = 0; i < groupFigures.length; i++) {
                groupFigures[i].groupId = -1;
            }
        }
    },

    /**Removes any temporary group*/
    groupRemoveTemporary: function () {
        throw Exception("Not implemented");
    },

    /**See if this STACK is equal to another. It is a shallow compare.
    *@param {Stack} anotherStack - the other STACK object
    *@return {Boolean} - true if equals, false otherwise
    **/
    equals: function (anotherStack) {
        var msg = '';
        if (!anotherStack instanceof Stack) {
            return false;
            msg += 'not same class';
        }



        //test figures
        if (this.figures.length != anotherStack.figures.length) {
            msg += 'not same nr of figures';
            return false;
        }


        for (var i = 0; i < this.figures.length; i++) {
            if (!this.figures[i].equals(anotherStack.figures[i])) {
                msg += 'figures not the same';
                return false;
            }
        }



        //test groups
        if (this.groups.length != anotherStack.groups.length) {
            msg += 'not same nr of groups';
            return false;
        }

        for (var i = 0; i < this.groups.length; i++) {
            if (!this.groups[i].equals(anotherStack.groups[i])) {
                msg += 'groups not the same';
                return false;
            }
        }



        //test idToIndex
        for (var i = 0; i < this.figures.idToIndex; i++) {
            if (this.idToIndex[i] != undefined //if not undefined
                && anotherStack.idToIndex != undefined //if not undefined
                && this.idToIndex[i] != anotherStack.idToIndex[i]) {

                msg += 'not same idToIndex';
                return false;
            }
        }

        if (this.currentId != anotherStack.currentId) {
            msg += 'not same currentId';
            return false;
        }

        if (msg != '') {
            alert(msg);
        }

        return true;

    },

    /**Generates an returns a new unique ID
    *@return {Number} - next id*/
    generateId: function () {
        return this.currentId++;
    },

    /**Adds a figure to the Stack of figures
    *@param {Figure} figure - the figure to add
    **/
    figureAdd: function (figure) {
        this.figures.push(figure);
        this.idToIndex[figure.id] = this.figures.length - 1;
    },


    containerAdd: function (container) {
        this.containers.push(container);
    },

    /*code taken from ConnectionPoint.removeConnector
    *@param {Figure} figure - the figure to remove
    *@deprecated
    **/
    figureRemove_deprecated: function (figure) {
        var index = -1;
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i] == figure) {
                index = i;
                break;
            }
        }
        CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figure.id);
        if (index > -1) {
            this.figures.splice(index, 1);
            for (var i = index; i < this.figures.length; i++) {
                this.idToIndex[this.figures[i].id] = i;
            }
        }
        if (index < this.figureSelectedIndex && index != -1) {
            this.figureSelectedIndex--;
        }
    },

    /**Removes a container by it's id
    *@param {Number} containerId - the {Container}'s id
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    containerRemoveById: function (containerId) {
        var index = -1;
        for (var i = 0; i < this.containers.length; i++) {
            if (this.containers[i].id === containerId) {
                index = i;
                break;
            }
        }

        if (index > -1) {
            //remove figure
            this.containers.splice(index, 1);

            //reindex
            //            this.reindex();
        }
    },

    /**Removes a figure by it's id
    *@param {Number} figId - the {Figure}'s id
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    figureRemoveById: function (figId) {
        var index = -1;
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i].id == figId) {
                index = i;
                break;
            }
        }

        if (index > -1) {
            //remove all affected Glues
            var cCPs = CONNECTOR_MANAGER.connectionPointGetAllByParent(figId); //get all connection points
            var length = cCPs.length;
            var k;
            for (k = 0; k < length; k++) {
                CONNECTOR_MANAGER.glueRemoveAllByFirstId(cCPs[k].id);
            }

            // remove figure's connection points
            CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figId);

            //remove figure
            this.figures.splice(index, 1);

            //reindex
            this.reindex();
        }
    },


    /**Recreates the index (id, index)
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    reindex: function () {
        for (var i = 0; i < this.figures.length; i++) {
            this.idToIndex[this.figures[i].id] = i;
        }
    },

    /**Deletes all the figure and reset any index*/
    reset: function () {
        this.figures = [];
        this.figureSelectedIndex = -1;
        this.currentId = 0;
    },

    /**Find the storage index of a figure
    *@param {Figure} figure - the figure you search for
    *@return {Number} - the index where you can find the Figure or -1 if not founded
    **/
    getIndex: function (figure) {
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i] == figure) {
                return i;
            }
        }
        return -1;
    },



    /**Returns all figures from a group
    *@param {Number} groupId - the id of the group
    *@return {Array} - the {Array} of {Figure}s that belong to the group
    **/
    figureGetByGroupId: function (groupId) {
        var groupFigures = [];
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i].groupId == groupId) {
                groupFigures.push(this.figures[i]);
            }
        }

        return groupFigures;
    },


    /**Returns all figures ids from a group
    *@param {Number} groupId - the id of the group
    *@return {Array} - the {Array} of {Number}s that belong to the group
    **/
    figureGetIdsByGroupId: function (groupId) {
        var groupFiguresIds = [];
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i].groupId == groupId) {
                groupFiguresIds.push(this.figures[i].id);
            }
        }

        return groupFiguresIds;
    },

    /**Returns a figure by id
    *@param {Number} id - the id of the figure
    *@return {Figure} - the figure object or null if no figure with that id found
    *TODO: use idToIndex to speed up the search....well there is no search at all :)
    **/
    figureGetById: function (id) {
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i].id == id) {
                return this.figures[i];
            }
        }
        return null;
    },


    /**
    *Returns first figure glued to a connector
    *@param {Number} connectorId - the id of the connector
    *@return {Figure} - the figure connected, or null if none 
    **/
    figureGetAsFirstFigureForConnector: function (connectorId) {
        Log.group("Stack:figureGetAsFirstFigureForConnector");

        /*Algorithm
        *Connector -> first Connector's ConnectionPoint-> Glue -> Figure's ConnectionPoint -> Figure
        **/
        var figure = null;

        //var connector = CONNECTOR_MANAGER.connectorGetById(connectorId);        
        Log.debug("Connector id = " + connectorId);

        var startConnectionPoint = CONNECTOR_MANAGER.connectionPointGetFirstForConnector(connectorId);
        Log.debug("ConnectionPoint id = " + startConnectionPoint.id);

        var startGlue = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(startConnectionPoint.id)[0];
        if (startGlue) {
            Log.debug("Glue id1 = (" + startGlue.id1 + ", " + startGlue.id2 + ')');

            var figureConnectionPoint = CONNECTOR_MANAGER.connectionPointGetById(startGlue.id1);
            Log.debug("Figure's ConnectionPoint id = " + figureConnectionPoint.id);

            figure = this.figureGetById(figureConnectionPoint.parentId);
        }
        else {
            Log.debug("no glue");
        }

        Log.groupEnd();

        return figure;
    },


    /**
    *Returns second figure glued to a connector
    *@param {Number} connectorId - the id of the connector
    *@return {Figure} - the figure connected, or null if none 
    **/
    figureGetAsSecondFigureForConnector: function (connectorId) {
        Log.group("Stack:figureGetAsSecondFigureForConnector");

        /*Algorithm
        *Connector -> first Connector's ConnectionPoint-> Glue -> Figure's ConnectionPoint -> Figure
        **/
        var figure = null;

        //var connector = CONNECTOR_MANAGER.connectorGetById(connectorId);        
        Log.debug("Connector id = " + connectorId);

        var endConnectionPoint = CONNECTOR_MANAGER.connectionPointGetSecondForConnector(connectorId);
        Log.debug("ConnectionPoint id = " + endConnectionPoint.id);

        var startGlue = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(endConnectionPoint.id)[0];
        if (startGlue) {
            Log.debug("Glue id1 = (" + startGlue.id1 + ", " + startGlue.id2 + ')');

            var figureConnectionPoint = CONNECTOR_MANAGER.connectionPointGetById(startGlue.id1);
            Log.debug("Figure's ConnectionPoint id = " + figureConnectionPoint.id);

            figure = this.figureGetById(figureConnectionPoint.parentId);
        }
        else {
            Log.debug("no glue");
        }

        Log.groupEnd();

        return figure;
    },



    /**
    *Returns the Figure's id if there is a figure for the given coordinates
    *It will return the first figure we found from top to bottom (Z-order)
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Ox axis
    *@return {Number} - the id of the figure or -1 if none found
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    figureGetByXY: function (x, y) {
        var id = -1;
        for (var i = this.figures.length - 1; i >= 0; i--) {
            if (this.figures[i].contains(x, y)) {
                id = this.figures[i].id;
                break;
                /*
                *Some old stuff left from a previous version. 
                *TODO: delete it if no longer needed after the grouping has been (re)done
                *we always want to get the group of a figure, not the figure itself where possible
                *(this section only does anything when we change the order of some items
                *var figure = this.figures[i];
                *While the figure belongs to a group or the group is part of another group
                *we will go up in the ancestor hierarchy
                * while(figure.groupId >= 0){
                *     figure = this.figureGetById(figure.groupId);
                * }
                return figure;
                */
            } //end if
        } //end for
        return id;
    },

    /**
    *Returns the Text primitive of parent figure for the given coordinates
    *It will return the first Text primitive we found from top to bottom (Z-order)
    *@param {Number} fId - the id of figure - parent of target Text primitive
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Oy axis
    *@return {Number} - the id value of Text primitive or -1 if none found
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    textGetByFigureXY: function (fId, x, y) {
        var figureLength = this.figures.length;
        for (var i = figureLength - 1; i >= 0; i--) {
            var figure = this.figures[i];
            if (figure.id === fId) {
                var primitiveLength = figure.primitives.length;
                for (var j = primitiveLength - 1; j >= 0; j--) { //top to bottom
                    var primitive = figure.primitives[j];
                    if ((primitive.oType === "Text") && primitive.contains(x, y)) {
                        return primitive.id;
                    }
                }
            } //end if
        } //end for
        return -1;
    },

    /**
    *Returns the Text primitive of parent container for the given coordinates
    *It will return the first Text primitive we found from top to bottom (Z-order)
    *@param {Number} cId - the id of container - parent of target Text primitive
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Oy axis
    *@return {Number} - the id value of Text primitive or -1 if none found
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    textGetByContainerXY: function (cId, x, y) {
        var containerLength = this.containers.length;
        for (var i = containerLength - 1; i >= 0; i--) {
            var container = this.containers[i];
            if (container.id === cId) {
                var primitiveLength = container.primitives.length;
                for (var j = primitiveLength - 1; j >= 0; j--) {
                    var primitive = container.primitives[j];
                    if ((primitive.oType == "Text") && primitive.contains(x, y)) {
                        return primitive.id;
                    }
                }
            } //end if
        } //end for
        return -1;
    },

    /**
    *Returns the Container's id if there is a container for the given coordinates
    *It will return the first container we found from top to bottom (Z-order)
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Ox axis
    *@return {Number} - the id of the container or -1 if none found
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    containerGetByXY: function (x, y) {
        var id = -1;
        for (var i = this.containers.length - 1; i >= 0; i--) {
            if (this.containers[i].contains(x, y)) {
                id = this.containers[i].id;
                break;
            } //end if
        } //end for
        return id;
    },


    /**
    *Returns the Container's id if there is a container for the given coordinates
    *It will return the first container we found from top to bottom (Z-order)
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Ox axis
    *@return {Number} - the id of the {Container} or -1 if none found
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    containerGetByXYOnEdge: function (x, y) {
        var id = -1;
        for (var i = this.containers.length - 1; i >= 0; i--) {
            if (this.containers[i].onEdge(x, y)) {
                id = this.containers[i].id;
                break;
            } //end if
        } //end for

        return id;
    },


    /**Returns a container by id
    *@param {Number} id - the id of the container
    *@return {Container} - the container object or null if no container with that id found
    **/
    containerGetById: function (id) {
        for (var i = 0; i < this.containers.length; i++) {
            if (this.containers[i].id == id) {
                return this.containers[i];
            }
        }
        return null;
    },

    /**
    *Returns an Array of Figure's id if there are figures for the given coordinates
    *@param {Number} x - the value on Ox axis
    *@param {Number} y - the value on Ox axis
    *@return {Array} of {Number} - the array of {Figure} ids. Figures are arranged from top (closer to viewer)
    * to bottom (far from viewer)
    *@author Alex Gheorghiu <alex@scriptoid.com>
    **/
    figuresGetByXY: function (x, y) {
        var ids = [];

        for (var i = this.figures.length - 1; i >= 0; i--) {
            if (this.figures[i].contains(x, y)) {
                ids.push(this.figures[i].id);
            }
        }

        return ids;
    },
    /**Returns a imageFrame by figureId
    *@param {Number} figureId - the figureId of the Figure
    *@return {ImageFrame} - the ImageFrame object or null if no ImageFrame with that found
    **/
    figuresImagePrimitiveGetByFigureId: function (figureId) {
        var f = this.figureGetById(figureId);
        var imageFrame = null;
        for (var i = 0; i < f.primitives.length; i++) {
            if (f.primitives[i].oType == 'ImageFrame') {
                imageFrame = f.primitives[i];
            }
        }
        return imageFrame;
    },
    /**Returns a Text by figureId
    *@param {Number} figureId - the figureId of the Figure
    *@return {Text} - the Text object or null if no Text with that found
    **/
    figuresTextPrimitiveGetByFigureId: function (figureId) {
        var f = this.figureGetById(figureId);
        var text = null;
        if (f) {
            for (var i = 0; i < f.primitives.length; i++) {
                if (f.primitives[i].oType == 'Text') {
                    text = f.primitives[i];
                }
            }
        }
        return text;
    },

    /* Sets the z index of a figure
    * @param {Figure} figure - the figure to move
    * @param {Number} position - the Z index. The bigger value means  closer to user (last painted);
    * @deprecated
    * */
    setPosition_deprecated: function (figure, position) {
        var figureIndex = -1;
        for (var i = 0; i < this.figures.length; i++) {
            if (this.figures[i] == figure) {
                figureIndex = i;
            }
        }
        if (figureIndex != -1 && position >= 0 && position < this.figures.length) {
            //tempFigures=[];
            //tempFigures.spli
            //if(position<figureIndex){
            var tempFigure = this.figures.splice(figureIndex, 1); //the figure to move
            this.figures.splice(position, 0, tempFigure[0]);
            //}
            var added = false
            for (var i = 0; i < this.figures.length; i++) {
                this.idToIndex[this.figures[i].id] = i;
            }
            this.figureSelectedIndex = position;
            //this.figures=tempFigures;
        }
    },

    /** Sets the new z position of a currently selected figure (if present)
    * It actually swap figures.
    * <p/>
    * Note: it's just a simple switch between current position and new position
    * <p/>
    * Zack: Is it just a switch? All you are doing is swapping, what if the user didn't want to swap, but shift up
    * using this method, if you have 5 figures, and bring the very back one to the front, the front figure
    * is moved to the very back, surely the correct solution is to move everything back 1, and move the selected
    * figure to the front
    * <p/>
    * Alex: What you are saying is an insert at a certain position which is not what we want with this method.
    * Maybe we should rename it swapToPosition(...) or swapIntoPosition(...)
    * <p/>
    * @param {Number} figureId - the id of the {Figure}
    * @param {Number} newPosition - the new Z index of the figure. The bigger the value, close to user (last painted);
    * @author Alex Gheorghiu <alex@scriptoid.com>

    * */
    swapToPosition: function (figureId, newPosition) {
        var oldPosition = this.idToIndex[figureId];

        if (oldPosition != -1 /**oldPosition valid*/
            && newPosition >= 0 && newPosition < this.figures.length /**newPosition in vector bounds*/) {

            //update idToIndex index
            this.idToIndex[figureId] = newPosition;
            this.idToIndex[this.figures[newPosition].id] = oldPosition;

            //switch figures
            var temp = this.figures[oldPosition];
            this.figures[oldPosition] = this.figures[newPosition];
            this.figures[newPosition] = temp;
        }
    },


    /**
    *Insert a figure into a position and shifts all other figures
    *Used by moveToBack and moveToFront, sets the selected figure to the selected position, and rotates all other figures away
    *@example
    *[0] = 0
    *[1] = 1
    *[2] = 2
    *
    *change to
    *
    *@example
    *[0] = 1
    *[1] = 2
    *[2] = 0
    *
    *@example
    *figureA
    *figureB
    *figureC
    *
    *change to
    *
    *@example
    *figureB
    *figureC
    *figureA
    *@param {Number} figureId - the id of the figure
    *@param {Number} newPosition - the new position of the figure
    *@author Zack Newsham
    */
    setPosition: function (figureId, newPosition) {
        //are we moving forward or back?
        var oldPosition = this.idToIndex[figureId];
        var temp = this.figures[oldPosition];
        var direction = -1; //move to back
        if (oldPosition < newPosition) {//move to front
            direction = 1;
        }
        Log.info(direction);
        //while i between oldposition and new position, move 1 in given direction
        for (var i = oldPosition; i != newPosition; i += direction) {
            this.figures[i] = this.figures[i + direction]; //set the figure
            this.idToIndex[this.figures[i].id] = i; //change the index
        }
        this.figures[newPosition] = temp; //replace the temp
        this.idToIndex[this.figures[newPosition].id] = newPosition;
    },


    /**Test if an (x,y) is over a figure
    *@param {Number} x - the x coordinates
    *@param {Number} y - the y coordinates
    *@return {Boolean} - true if over a figure, false otherwise
    **/
    figureIsOver: function (x, y) {
        var found = false;
        for (var i = 0; i < this.figures.length; i++) {
            var figure = this.figures[i];
            if (figure.contains(x, y)) {
                found = true;
                break;
            }
        }
        return found;
    },


    /**Test if an (x,y) is over a container
    *@param {Number} x - the x coordinates
    *@param {Number} y - the y coordinates
    *@return {Boolean} - true if over a container, false otherwise
    **/
    containerIsOver: function (x, y) {
        var found = false;
        for (var i = 0; i < this.containers.length; i++) {
            var container = this.containers[i];
            if (container.contains(x, y)) {
                found = true;
                break;
            }
        }
        return found;
    },


    /**Test if an (x,y) is over a Container's edge (rigt on its edge)
    *@param {Number} x - the x coordinates
    *@param {Number} y - the y coordinates
    *@return {Boolean} - true if over a container, false otherwise
    **/
    containerIsOnEdge: function (x, y) {
        var found = false;
        for (var i = 0; i < this.containers.length; i++) {
            var container = this.containers[i];
            if (container.onEdge(x, y)) {
                found = true;
                break;
            }
        }
        return found;
    },

    /**Return the bounds for all objects on work area (canvas).
    *
    *@return {Array<Number>} - returns [minX, minY, maxX, maxY] - bounds, where
    *  all objects on canvas (Figures/Containers/Connectors) are in the bounds.
    *
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    getWorkAreaBounds: function () {
        var minX;
        var maxX;
        var minY;
        var maxY;
        var unset = true;   // defines if there were no object - no bounds set

        // function to run for any bounds in format [minX, minY, maxX, maxY]
        // compares given bounds with current values of canvas
        var compareAndSet = function (bounds) {
            // if minX is unset or bigger than given
            if (typeof (minX) === 'undefined' || minX > bounds[0]) {
                minX = bounds[0];
            }
            // if minY is unset or bigger than given
            if (typeof (minY) === 'undefined' || minY > bounds[1]) {
                minY = bounds[1];
            }
            // if maxX is unset or bigger than given
            if (typeof (maxX) === 'undefined' || bounds[2] > maxX) {
                maxX = bounds[2];
            }
            // if maxY is unset or bigger than given
            if (typeof (maxY) === 'undefined' || bounds[3] > maxY) {
                maxY = bounds[3];
            }

            // if once function were ran - one object setted it's bounds
            unset = false;
        };

        var i;
        // get bounds of containers
        var containerLength = this.containers.length;
        for (i = 0; i < containerLength; i++) {
            compareAndSet(this.containers[i].getBounds());
        }

        // get bounds of figures
        var figureLength = this.figures.length;
        for (i = 0; i < figureLength; i++) {
            compareAndSet(this.figures[i].getBounds());
        }

        // get bounds of connectors
        var connectorLength = CONNECTOR_MANAGER.connectors.length;
        for (i = 0; i < connectorLength; i++) {
            compareAndSet(CONNECTOR_MANAGER.connectors[i].getBounds());
        }

        // bounds were setted/changed?
        if (unset) {
            // return full canvas size
            return [0, 0, canvasProps.getWidth(), canvasProps.getHeight()];
        } else {
            // return setted new bounds
            return [minX, minY, maxX, maxY];
        }
    },


    /**
    *Apply a transformation to all Figures and Containers in Stack
    *@param {Matrix} matrix - a matrix of numbers
    *
    *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
    **/
    transform: function (matrix) {
        // translate Containers
        var i;
        var containerLength = this.containers.length;
        for (i = 0; i < containerLength; i++) {
            this.containers[i].transform(matrix);
        }

        // translate Figures
        var figureLength = this.figures.length;
        for (i = 0; i < figureLength; i++) {
            // Does Figure is placed outside of container?
            if (CONTAINER_MANAGER.getContainerForFigure(this.figures[i].id) === -1) {
                this.figures[i].transform(matrix);
            }
            // otherwise it is already transformed
        }
    },


    /**Paints all {Figure}s from back to top (Z order)
    *@param  {Context} context - the 2D context
    *@param  {boolean} ignoreSelection - if ignoreSelection is set to true selections will not be painted
    **/
    paint: function (context, ignoreSelection) {
        //        Log.group("STACK: paint");
        /*The idea is to paint from bottom to top
        * 1. figures (first)
        * 2. connectors
        * 3. handlers
        * 4. selection area (last) 
        **/

        if (DIAGRAMO.debug) {
            var pos = 1;
            context.save();
            context.font = "10px Arial";
            context.fillStyle = '#000000';
            context.strokeStyle = '#000000';
            context.lineWidth = 1;
            context.fillText("state: " + state, 0, 10 * pos++);
            context.fillText("selectedFigureId: : " + selectedFigureId, 0, 10 * pos++);
            context.fillText("selectedGroupId: : " + selectedGroupId, 0, 10 * pos++);
            context.fillText("selectedContainerId: : " + selectedContainerId, 0, 10 * pos++);
            if (selectedGroupId != -1) {
                var logGroup = this.groupGetById(selectedGroupId);
                context.fillText("permanent: : " + logGroup.permanent, 0, 10 * pos++);
            }
            context.fillText("selectedConnectorId: : " + selectedConnectorId, 0, 10 * pos++);
            if (selectedConnectorId != -1) {
                var connector = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                context.fillText("connector type: : " + connector.type, 0, 10 * pos++);
            }

            context.restore();
        }

        //paint containers
        for (var i = 0; i < this.containers.length; i++) {
            context.save();

            this.containers[i].paint(context);

            context.restore();
        }
        //end paint containers


        //paint figures
        for (var i = 0; i < this.figures.length; i++) {


            if (!context.save) {
                alert("save() no present")
            }
            context.save();

            this.figures[i].paint(context);
            context.restore();

            //if we are connecting something we should paint connection points too
            if (state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND
                || state == STATE_CONNECTOR_MOVE_POINT) {
                CONNECTOR_MANAGER.connectionPointPaint(context, this.figures[i].id);
            }

        } //end for


        //if we are connecting something we should paint currentCloud too
        if (state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND
            || state == STATE_CONNECTOR_MOVE_POINT) {
            CONNECTOR_MANAGER.connectionCloudPaint(context);
        }


        //paint connector(s)
        CONNECTOR_MANAGER.connectorPaint(context, selectedConnectorId);


        //paint handlers for selected shape
        if (state == STATE_FIGURE_SELECTED) { //FIGURE
            var f = this.figureGetById(selectedFigureId);
            //self dgq
            if (f) {
                f.style.gradientBounds = f.getBounds();
                HandleManager.shapeSet(f);
            }
            //alert('Paint handles');
            if (!ignoreSelection) {
                HandleManager.paint(context);
            }
        }
        else if (state == STATE_CONNECTOR_SELECTED) { //CONNECTOR
            var c = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
            HandleManager.shapeSet(c);
            if (!ignoreSelection) {
                HandleManager.paint(context);
            }
        }
        else if (state == STATE_CONTAINER_SELECTED) { //CONTAINER
            var cont = STACK.containerGetById(selectedContainerId);
            HandleManager.shapeSet(cont);
            if (!ignoreSelection) {
                HandleManager.paint(context);
            }
        }
        else if (state == STATE_GROUP_SELECTED) { //GROUP 
            var g = this.groupGetById(selectedGroupId);
            HandleManager.shapeSet(g);
            if (!ignoreSelection) {
                HandleManager.paint(context);
            }
        }
        else if (state == STATE_SELECTING_MULTIPLE) { //SELECTION
            /* If shift is pressed, then leave the selected figure 
            * or group drawn on screen and allow drawing region in same time*/
            if (SHIFT_PRESSED) {
                if (selectedFigureId != -1) {
                    var f = this.figureGetById(selectedFigureId);
                    HandleManager.paint(context);
                }
                if (selectedGroupId != -1) {
                    var g = this.groupGetById(selectedGroupId);
                    HandleManager.paint(context);
                }
            }

            selectionArea.paint(context);
            Log.info(selectionArea.toString());
        }

        if (DIAGRAMO.debug) {
            //            Log.group("Visual debug");
            var colors = {
                's0': '#ff0000',
                's1_1': '#009900',
                's1_2': '#0033ff',
                's2_1': '#00ff33',
                's2_2': '#ff3399',
                's2_3': '#808019',
                's2_4': '#e6e64c',
                's2_5': '#e67814',
                's2_6': '#309bda'
            };

            context.save();
            for (var i = 0; i < DIAGRAMO.debugSolutions.length; i++) {
                var shift = 3 + i * 3;
                var solution = DIAGRAMO.debugSolutions[i];
                //                Log.info("Solution: " + solution + " type of " + typeof(solution[2]) + " length: " + solution.length );
                //                Log.info("Solution points: " + solution[2]);
                var points = solution[2]; //get points


                //paint line                                
                context.save();
                context.strokeStyle = colors[solution[1]];
                context.lineWidth = 1;
                context.beginPath();
                context.moveTo(points[0].x + shift, points[0].y + shift);
                for (var j = 1; j < points.length; j++) {
                    context.lineTo(points[j].x + shift, points[j].y + shift)
                }
                //context.closePath();
                context.stroke();
                context.restore();


                //paint points
                for (var j = 0; j < points.length; j++) {
                    context.save();
                    //                    points[j].style.strokeStyle = '#FF0000';
                    //                    points[j].paint(context);
                    context.beginPath();
                    //context.strokeStyle= colors[solution[1]];
                    context.strokeStyle = '#cccccc';
                    //context.arc(points[j].x,points[j].y, 2 + i * 3, 0, Math.PI*2, true);
                    context.arc(points[j].x, points[j].y, 3, 0, Math.PI * 2, true);
                    //context.closePath();
                    context.stroke();
                    context.restore();
                }
            }

            //paint the legend
            pos += 4;
            for (var solName in colors) {
                context.save();
                context.strokeText(solName, 0, 10 * pos);
                context.strokeStyle = colors[solName];
                context.beginPath();
                context.moveTo(50, 10 * pos);
                context.lineTo(150, 10 * pos);
                //context.endPath();
                context.stroke();
                context.restore();
                pos += 2;
            }
            context.restore();

            //            Log.groupEnd();
        }

        //        Log.groupEnd();
    },

    /**Convert all STACK to SVG representation
    *@return {String} - the SVG string representation*/
    toSVG: function () {
        var svg = '';

        for (var i = 0; i < this.figures.length; i++) {
            svg += this.figures[i].toSVG();
        }

        return svg;
    }
}