# Source: CollectionGroup.js

    "use strict";

    // Import external names locally
    var Shared,
    	Db,
    	DbInit,
    	Collection;

    Shared = require('./Shared');

    /**
     * Creates a new collection group. Collection groups allow single operations to be
     * propagated to multiple collections at once. CRUD operations against a collection
     * group are in fed to the group's collections. Useful when separating out slightly
     * different data into multiple collections but querying as one collection.
     * @constructor
     */
    var CollectionGroup = function () {
    	this.init.apply(this, arguments);
    };

    CollectionGroup.prototype.init = function (name) {
    	var self = this;

    	self._name = name;
    	self._data = new Collection('__FDB__cg_data_' + self._name);
    	self._collections = [];
    	self._view = [];
    };

    Shared.addModule('CollectionGroup', CollectionGroup);
    Shared.mixin(CollectionGroup.prototype, 'Mixin.Common');
    Shared.mixin(CollectionGroup.prototype, 'Mixin.ChainReactor');
    Shared.mixin(CollectionGroup.prototype, 'Mixin.Constants');
    Shared.mixin(CollectionGroup.prototype, 'Mixin.Triggers');
    Shared.mixin(CollectionGroup.prototype, 'Mixin.Tags');
    Shared.mixin(CollectionGroup.prototype, 'Mixin.Events');

    Collection = require('./Collection');
    Db = Shared.modules.Db;
    DbInit = Shared.modules.Db.prototype.init;

    CollectionGroup.prototype.on = function () {
    	this._data.on.apply(this._data, arguments);
    };

    CollectionGroup.prototype.off = function () {
    	this._data.off.apply(this._data, arguments);
    };

    CollectionGroup.prototype.emit = function () {
    	this._data.emit.apply(this._data, arguments);
    };

    /**
     * Gets / sets the primary key for this collection group.
     * @param {String=} keyName The name of the primary key.
     * @returns {*}
     */
    CollectionGroup.prototype.primaryKey = function (keyName) {
    	if (keyName !== undefined) {
    		this._primaryKey = keyName;
    		return this;
    	}

    	return this._primaryKey;
    };

    /**
     * Gets / sets the current state.
     * @param {String=} val The name of the state to set.
     * @returns {*}
     */
    Shared.synthesize(CollectionGroup.prototype, 'state');

    /**
     * Gets / sets the db instance the collection group belongs to.
     * @param {Db=} db The db instance.
     * @returns {*}
     */
    Shared.synthesize(CollectionGroup.prototype, 'db');

    /**
     * Gets / sets the instance name.
     * @param {String=} name The new name to set.
     * @returns {*}
     */
    Shared.synthesize(CollectionGroup.prototype, 'name');

    CollectionGroup.prototype.addCollection = function (collection) {
    	if (collection) {
    		if (this._collections.indexOf(collection) === -1) {
    			//var self = this;

    			// Check for compatible primary keys
    			if (this._collections.length) {
    				if (this._primaryKey !== collection.primaryKey()) {
    					throw(this.logIdentifier() + ' All collections in a collection group must have the same primary key!');
    				}
    			} else {
    				// Set the primary key to the first collection added
    				this.primaryKey(collection.primaryKey());
    			}

    			// Add the collection
    			this._collections.push(collection);
    			collection._groups = collection._groups || [];
    			collection._groups.push(this);
    			collection.chain(this);

    			// Hook the collection's drop event to destroy group data
    			collection.on('drop', function () {
    				// Remove collection from any group associations
    				if (collection._groups && collection._groups.length) {
    					var groupArr = [],
    						i;

    					// Copy the group array because if we call removeCollection on a group
    					// it will alter the groups array of this collection mid-loop!
    					for (i = 0; i < collection._groups.length; i++) {
    						groupArr.push(collection._groups[i]);
    					}

    					// Loop any groups we are part of and remove ourselves from them
    					for (i = 0; i < groupArr.length; i++) {
    						collection._groups[i].removeCollection(collection);
    					}
    				}

    				delete collection._groups;
    			});

    			// Add collection's data
    			this._data.insert(collection.find());
    		}
    	}

    	return this;
    };

    CollectionGroup.prototype.removeCollection = function (collection) {
    	if (collection) {
    		var collectionIndex = this._collections.indexOf(collection),
    			groupIndex;

    		if (collectionIndex !== -1) {
    			collection.unChain(this);
    			this._collections.splice(collectionIndex, 1);

    			collection._groups = collection._groups || [];
    			groupIndex = collection._groups.indexOf(this);

    			if (groupIndex !== -1) {
    				collection._groups.splice(groupIndex, 1);
    			}

    			collection.off('drop');
    		}

    		if (this._collections.length === 0) {
    			// Wipe the primary key
    			delete this._primaryKey;
    		}
    	}

    	return this;
    };

    CollectionGroup.prototype._chainHandler = function (chainPacket) {
    	//sender = chainPacket.sender;
    	switch (chainPacket.type) {
    		case 'setData':
    			// Decouple the data to ensure we are working with our own copy
    			chainPacket.data.dataSet = this.decouple(chainPacket.data.dataSet);

    			// Remove old data
    			this._data.remove(chainPacket.data.oldData);

    			// Add new data
    			this._data.insert(chainPacket.data.dataSet);
    			break;

    		case 'insert':
    			// Decouple the data to ensure we are working with our own copy
    			chainPacket.data.dataSet = this.decouple(chainPacket.data.dataSet);

    			// Add new data
    			this._data.insert(chainPacket.data.dataSet);
    			break;

    		case 'update':
    			// Update data
    			this._data.update(chainPacket.data.query, chainPacket.data.update, chainPacket.options);
    			break;

    		case 'remove':
    			this._data.remove(chainPacket.data.query, chainPacket.options);
    			break;

    		default:
    			break;
    	}
    };

    CollectionGroup.prototype.insert = function () {
    	this._collectionsRun('insert', arguments);
    };

    CollectionGroup.prototype.update = function () {
    	this._collectionsRun('update', arguments);
    };

    CollectionGroup.prototype.updateById = function () {
    	this._collectionsRun('updateById', arguments);
    };

    CollectionGroup.prototype.remove = function () {
    	this._collectionsRun('remove', arguments);
    };

    CollectionGroup.prototype._collectionsRun = function (type, args) {
    	for (var i = 0; i < this._collections.length; i++) {
    		this._collections[i][type].apply(this._collections[i], args);
    	}
    };

    CollectionGroup.prototype.find = function (query, options) {
    	return this._data.find(query, options);
    };

    /**
     * Helper method that removes a document that matches the given id.
     * @param {String} id The id of the document to remove.
     */
    CollectionGroup.prototype.removeById = function (id) {
    	// Loop the collections in this group and apply the remove
    	for (var i = 0; i < this._collections.length; i++) {
    		this._collections[i].removeById(id);
    	}
    };

    /**
     * Uses the passed query to generate a new collection with results
     * matching the query parameters.
     *
     * @param query
     * @param options
     * @returns {*}
     */
    CollectionGroup.prototype.subset = function (query, options) {
    	var result = this.find(query, options);

    	return new Collection()
    		.subsetOf(this)
    		.primaryKey(this._primaryKey)
    		.setData(result);
    };

    /**
     * Drops a collection group from the database.
     * @returns {boolean} True on success, false on failure.
     */
    CollectionGroup.prototype.drop = function (callback) {
    	if (!this.isDropped()) {
    		var i,
    			collArr,
    			viewArr;

    		if (this._debug) {
    			console.log(this.logIdentifier() + ' Dropping');
    		}

    		this._state = 'dropped';

    		if (this._collections && this._collections.length) {
    			collArr = [].concat(this._collections);

    			for (i = 0; i < collArr.length; i++) {
    				this.removeCollection(collArr[i]);
    			}
    		}

    		if (this._view && this._view.length) {
    			viewArr = [].concat(this._view);

    			for (i = 0; i < viewArr.length; i++) {
    				this._removeView(viewArr[i]);
    			}
    		}

    		this.emit('drop', this);

    		delete this._listeners;

    		if (callback) { callback(false, true); }
    	}

    	return true;
    };

    // Extend DB to include collection groups
    Db.prototype.init = function () {
    	this._collectionGroup = {};
    	DbInit.apply(this, arguments);
    };

    /**
     * Creates a new collectionGroup instance or returns an existing
     * instance if one already exists with the passed name.
     * @func collectionGroup
     * @memberOf Db
     * @param {String} name The name of the instance.
     * @returns {*}
     */
    Db.prototype.collectionGroup = function (name) {
    	var self = this;

    	if (name) {
    		// Handle being passed an instance
    		if (name instanceof CollectionGroup) {
    			return name;
    		}

    		if (this._collectionGroup && this._collectionGroup[name]) {
    			return this._collectionGroup[name];
    		}

    		this._collectionGroup[name] = new CollectionGroup(name).db(this);

    		self.deferEmit('create', self._collectionGroup[name], 'collectionGroup', name);

    		return this._collectionGroup[name];
    	} else {
    		// Return an object of collection data
    		return this._collectionGroup;
    	}
    };

    /**
     * Returns an array of collection groups the DB currently has.
     * @returns {Array} An array of objects containing details of each collection group
     * the database is currently managing.
     */
    Db.prototype.collectionGroups = function () {
    	var arr = [],
    		i;

    	for (i in this._collectionGroup) {
    		if (this._collectionGroup.hasOwnProperty(i)) {
    			arr.push({
    				name: i
    			});
    		}
    	}

    	return arr;
    };

    module.exports = CollectionGroup;