/*
    Copyright 2008-2013
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
    
    You can redistribute it and/or modify it under the terms of the
    
      * GNU Lesser General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version
      OR
      * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
    
    JSXGraph 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 Lesser General Public License for more details.
    
    You should have received a copy of the GNU Lesser General Public License and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 utils/type
 */

/** 
 * @fileoverview In this file the class Group is defined, a class for
 * managing grouping of points.
 */

define([
    'jxg', 'base/constants', 'base/element', 'math/math', 'utils/type'
], function (JXG, Const, GeometryElement, Mat, Type) {

    "use strict";

    /**
     * Creates a new instance of Group.
     * @class In this class all group management is done.
     * @param {JXG.Board} board
     * @param {String} id Unique identifier for this object.  If null or an empty string is given,
     * an unique id will be generated by Board
     * @param {String} name Not necessarily unique name, displayed on the board.  If null or an
     * empty string is given, an unique name will be generated.
     * @param {Array} objects Array of points to add to this group.
     * @constructor
     */
    JXG.Group = function (board, id, name, objects) {
        var number, objArray, i, obj;

        this.board = board;
        this.objects = {};
        number = this.board.numObjects;
        this.board.numObjects += 1;

        if ((id === '') || !Type.exists(id)) {
            this.id = this.board.id + 'Group' + number;
        } else {
            this.id = id;
        }
        this.board.groups[this.id] = this;

        this.type = Const.OBJECT_TYPE_POINT;
        this.elementClass = Const.OBJECT_CLASS_POINT;

        if ((name === '') || !Type.exists(name)) {
            this.name = 'group_' + this.board.generateName(this);
        } else {
            this.name = name;
        }
        delete this.type;

        this.coords = {};

        if (Type.isArray(objects)) {
            objArray = objects;
        } else {
            objArray = Array.prototype.slice.call(arguments, 3);
        }

        for (i = 0; i < objArray.length; i++) {
            obj = this.board.select(objArray[i]);

            if ((!obj.visProp.fixed) && ((obj.type === Const.OBJECT_TYPE_POINT) || (obj.type === Const.OBJECT_TYPE_GLIDER))) {
                if (obj.group.length !== 0) {
                    this.addGroup(obj.group[obj.group.length - 1]);
                } else {
                    this.addPoint(obj);
                }
            }
        }

        this.methodMap = {
            ungroup: 'ungroup',
            add: 'addPoint',
            addPoint: 'addPoint',
            addPoints: 'addPoints',
            addGroup: 'addGroup',
            remove: 'removePoint',
            removePoint: 'removePoint',
            setAttribute: 'setAttribute',
            setProperty: 'setAttribute'
        };
    };

    JXG.extend(JXG.Group.prototype, /** @lends JXG.Group.prototype */ {
        /**
         * Releases the group added to the points in this group, but only if this group is the last group.
         */
        ungroup: function () {
            var el;

            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    if (Type.isArray(this.objects[el].point.group) &&
                            this.objects[el].point.group[this.objects[el].point.group.length - 1] === this) {
                        this.objects[el].point.group.pop();
                    }

                    this.removePoint(this.objects[el].point);
                }
            }
        },

        /**
         * Sends an update to all group members. This method is called from the points' coords object event listeners
         * and not by the board.
         * @param {JXG.Point} point The point that caused the update.
         * @param {Number} dX
         * @param {Number} dY
         */
        //update: function (point, dX, dY) {
        update: function (fromParent) {
            var el, trans, transObj, j,
                obj = null;

            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    obj = this.objects[el].point;

                    if (obj.coords.distance(Const.COORDS_BY_USER, this.coords[el]) > Mat.eps) {
                        trans = [
                            obj.coords.usrCoords[1] - this.coords[obj.id].usrCoords[1],
                            obj.coords.usrCoords[2] - this.coords[obj.id].usrCoords[2]
                        ];
                        transObj = obj;
                        break;
                    }
                }
            }

            if (Type.exists(transObj)) {
                for (el in this.objects) {
                    if (this.objects.hasOwnProperty(el)) {
                        if (Type.exists(this.board.objects[el])) {
                            obj = this.objects[el].point;
                            if (obj.id !== transObj.id) {
                                obj.coords.setCoordinates(Const.COORDS_BY_USER, [this.coords[el].usrCoords[1] + trans[0], this.coords[el].usrCoords[2] + trans[1]]);
                            }
                            //this.objects[el].point.prepareUpdate().update(false).updateRenderer();
                        } else {
                            delete this.objects[el];
                        }
                        this.coords[obj.id] = {usrCoords: [obj.coords.usrCoords[0], obj.coords.usrCoords[1], obj.coords.usrCoords[2]]};
                    }
                }

                for (el in this.objects) {
                    if (this.objects.hasOwnProperty(el)) {
                        for (j in this.objects[el].descendants) {
                            if (this.objects[el].descendants.hasOwnProperty(j)) {
                                this.objects[el].descendants.needsUpdate = this.objects[el].descendants.needsRegularUpdate || this.board.needsFullUpdate;
                            }
                        }
                    }
                }
                this.board.updateElements(fromParent);
            }

            return this;
        },

        /**
         * Adds an Point to this group.
         * @param {JXG.Point} object The point added to the group.
         */
        addPoint: function (object) {
            this.objects[object.id] = {
                point: object
            };
            this.coords[object.id] = {usrCoords: [object.coords.usrCoords[0], object.coords.usrCoords[1], object.coords.usrCoords[2]]};
        },

        /**
         * Adds multiple points to this group.
         * @param {Array} objects An array of points to add to the group.
         */
        addPoints: function (objects) {
            var p;

            for (p = 0; p < objects.length; p++) {
                this.addPoint(objects[p]);
            }
        },

        /**
         * Adds all points in a group to this group.
         * @param {JXG.Group} group The group added to this group.
         */
        addGroup: function (group) {
            var el;

            for (el in group.objects) {
                if (group.objects.hasOwnProperty(el)) {
                    this.addPoint(group.objects[el].point);
                }
            }
        },

        /**
         * Removes a point from the group.
         * @param {JXG.Point} point
         */
        removePoint: function (point) {
            delete this.objects[point.id];
        },

        /**
         * @deprecated
         * Use setAttribute
         */
        setProperty: JXG.shortcut(JXG.Group.prototype, 'setAttribute'),

        setAttribute: function () {
            var el;

            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    this.objects[el].point.setAttribute.apply(this.objects[el].point, arguments);
                }
            }
        }
    });

    /**
     * Groups points.
     * @param {JXG.Board} board The board the points are on.
     * @param {Array} parents Array of points to group.
     * @param {Object} attributes Visual properties.
     * @returns {JXG.Group}
     */
    JXG.createGroup = function (board, parents, attributes) {
        var i, g = new JXG.Group(board, attributes.id, attributes.name, parents);

        g.elType = 'group';
        g.parents = [];

        for (i = 0; i < parents.length; i++) {
            g.parents.push(parents[i].id);
        }

        return g;
    };

    JXG.registerElement('group', JXG.createGroup);

    return {
        Group: JXG.Group,
        createGroup: JXG.createGroup
    };
});