/**
 * Adds rows in dataset2 to dataset1
 */
morpheus.JoinedDataset = function(dataset1, dataset2, dataset1Field,
		dataset2Field, sourceFieldName) {
	sourceFieldName = sourceFieldName || 'Source';
	this.dataset1Field = dataset1Field;
	this.dataset2Field = dataset2Field;
	if (dataset1 == null) {
		throw 'dataset1 is null';
	}
	if (dataset2 == null) {
		throw 'dataset2 is null';
	}
	if (dataset1Field) { // reorder dataset 2 to match dataset 1
		var v1 = dataset1.getColumnMetadata().getByName(dataset1Field);
		var dataset2ValueToIndex = morpheus.VectorUtil
				.createValueToIndexMap(dataset2.getColumnMetadata().getByName(
						dataset2Field));
		var dataset2ColumnIndices = [];
		for (var i = 0; i < v1.size(); i++) {
			dataset2ColumnIndices[i] = dataset2ValueToIndex.get(v1.getValue(i));
			// undefined indices are handles in SlicedDatasetWithNulls
		}
		dataset2 = new morpheus.SlicedDatasetWithNulls(dataset2,
				dataset2ColumnIndices, dataset1.getColumnCount(), dataset1
						.getColumnMetadata());
	}

	if (!dataset1.getRowMetadata().getByName(sourceFieldName)) {
		var sourceVector = dataset1.getRowMetadata().add(sourceFieldName);
		var name = dataset1.getName();
		for (var i = 0, nrows = sourceVector.size(); i < nrows; i++) {
			sourceVector.setValue(i, name);
		}
	}
	if (!dataset2.getRowMetadata().getByName(sourceFieldName)) {
		var sourceVector = dataset2.getRowMetadata().add(sourceFieldName);
		var name = dataset2.getName();
		for (var i = 0, nrows = sourceVector.size(); i < nrows; i++) {
			sourceVector.setValue(i, name);
		}

	}

	// make sure dataset1 and dataset2 have the same row metadata fields in the
	// same order
	for (var i = 0, count = dataset1.getRowMetadata().getMetadataCount(); i < count; i++) {
		var name = dataset1.getRowMetadata().get(i).getName();
		if (dataset2.getRowMetadata().getByName(name) == null) {
			dataset2.getRowMetadata().add(name);
		}
	}
	for (var i = 0, count = dataset2.getRowMetadata().getMetadataCount(); i < count; i++) {
		var name = dataset2.getRowMetadata().get(i).getName();
		if (dataset1.getRowMetadata().getByName(name) == null) {
			dataset1.getRowMetadata().add(name);
		}
	}

	// put dataset2 row metadata names in same order as dataset1
	var dataset2RowMetadataOrder = [];
	var metadataInDifferentOrder = false;
	for (var i = 0, count = dataset1.getRowMetadata().getMetadataCount(); i < count; i++) {
		var name = dataset1.getRowMetadata().get(i).getName();
		var index = morpheus.MetadataUtil.indexOf(dataset2.getRowMetadata(),
				name);
		dataset2RowMetadataOrder.push(index);
		if (index !== i) {
			metadataInDifferentOrder = true;
		}
	}
	this.dataset1 = dataset1;
	this.dataset2 = dataset2;
	// TODO put series in same order
	var maxSeriesCount = Math.max(this.dataset1.getSeriesCount(), this.dataset2
			.getSeriesCount());
	for (var i = this.dataset1.getSeriesCount(); i < maxSeriesCount; i++) {
		this.dataset1.addSeries({
			name : this.dataset2.getName(i)
		});
	}
	for (var i = this.dataset2.getSeriesCount(); i < maxSeriesCount; i++) {
		this.dataset2.addSeries({
			name : this.dataset1.getName(i)
		});
	}

	this.rowMetadata = new morpheus.JoinedMetadataModel(this.dataset1
			.getRowMetadata(), !metadataInDifferentOrder ? this.dataset2
			.getRowMetadata() : new morpheus.MetadataModelColumnView(
			this.dataset2.getRowMetadata(), dataset2RowMetadataOrder));
};
morpheus.JoinedDataset.prototype = {
	getName : function(seriesIndex) {
		return this.dataset1.getName(seriesIndex);
	},
	setName : function(seriesIndex, name) {
		this.dataset1.setName(seriesIndex, name);
	},
	getDatasets : function() {
		return [ this.dataset1, this.dataset2 ];
	},
	getDataset1 : function() {
		return this.dataset1;
	},
	getRowMetadata : function() {
		return this.rowMetadata;
	},
	getColumnMetadata : function() {
		return this.dataset1.getColumnMetadata();
	},
	getRowCount : function() {
		return this.dataset1.getRowCount() + this.dataset2.getRowCount();
	},
	getColumnCount : function() {
		return this.dataset1.getColumnCount();
	},
	getValue : function(i, j, seriesIndex) {
		return i < this.dataset1.getRowCount() ? this.dataset1.getValue(i, j,
				seriesIndex) : this.dataset2.getValue(i
				- this.dataset1.getRowCount(), j, seriesIndex);
	},
	setValue : function(i, j, value, seriesIndex) {
		i < this.dataset1.getRowCount() ? this.dataset1.setValue(i, j, value,
				seriesIndex) : this.dataset2.setValue(i
				- this.dataset1.getRowCount(), j, value, seriesIndex);
	},
	getSeriesCount : function() {
		return this.dataset1.getSeriesCount();
	},
	addSeries : function(options) {
		this.dataset1.addSeries(options);
		return this.dataset2.addSeries(options);
	},
	toString : function() {
		return this.getName();
	}
};
morpheus.SlicedDatasetWithNulls = function(dataset, columnIndices, columnCount,
		columnMetadata) {
	morpheus.DatasetAdapter.call(this, dataset);
	this.columnIndices = columnIndices;
	this.columnCount = columnCount;
	this.columnMetadata = columnMetadata;
};
morpheus.SlicedDatasetWithNulls.prototype = {
	getColumnMetadata : function() {
		return this.columnMetadata;
	},
	getColumnCount : function() {
		return this.columnCount;
	},
	getValue : function(i, j, seriesIndex) {
		var index = this.columnIndices[j];
		return index === undefined ? undefined : this.dataset.getValue(i,
				index, seriesIndex);
	},
	setValue : function(i, j, value, seriesIndex) {
		var index = this.columnIndices[j];
		if (index !== undefined) {
			this.dataset.setValue(i, index, value, seriesIndex);
		} else {
			console.log(j + ' out of range');
		}
	}
};
morpheus.Util.extend(morpheus.SlicedDatasetWithNulls, morpheus.DatasetAdapter);
morpheus.JoinedVector = function(v1, v2) {
	this.v1 = v1;
	this.v2 = v2;
	morpheus.VectorAdapter.call(this, v1);
	this.properties = new morpheus.Map();
};
morpheus.JoinedVector.prototype = {
	setValue : function(i, value) {
		i < this.v1.size() ? this.v1.setValue(i, value) : this.v2.setValue(i
				- this.v1.size(), value);
	},
	getValue : function(i) {
		return i < this.v1.size() ? this.v1.getValue(i) : this.v2.getValue(i
				- this.v1.size());
	},
	size : function() {
		return this.v1.size() + this.v2.size();
	},
	getProperties : function() {
		return this.properties;
	}
};
morpheus.Util.extend(morpheus.JoinedVector, morpheus.VectorAdapter);
morpheus.JoinedMetadataModel = function(m1, m2) {
	this.m1 = m1;
	this.m2 = m2;
	this.vectors = [];
	for (var i = 0, count = m1.getMetadataCount(); i < count; i++) {
		var v1 = this.m1.get(i);
		var v2 = this.m2.get(i);
		var v = new morpheus.JoinedVector(v1, v2);
		// copy properties
		v1.getProperties().forEach(function(val, key) {
			if (!morpheus.VectorKeys.COPY_IGNORE.has(key)) {
				v.properties.set(key, val);
			}
		});
		v2.getProperties().forEach(function(val, key) {
			if (!morpheus.VectorKeys.COPY_IGNORE.has(key)) {
				v.properties.set(key, val);
			}
		});

		this.vectors.push(v);
	}
};
morpheus.JoinedMetadataModel.prototype = {
	add : function(name) {
		var index = morpheus.MetadataUtil.indexOf(this, name);
		var oldVector;
		if (index !== -1) {
			oldVector = this.remove(index);
		}
		var v = new morpheus.Vector(name, this.getItemCount());
		if (oldVector != null) {
			// copy properties
			oldVector.getProperties().forEach(function(val, key) {
				v.getProperties().set(key, val);
			});
			// copy values
			for (var i = 0, size = oldVector.size(); i < size; i++) {
				v.setValue(i, oldVector.getValue(i));
			}
		}
		this.vectors.push(v);
		return v;
	},
	getItemCount : function() {
		return this.m1.getItemCount() + this.m2.getItemCount();
	},
	get : function(index) {
		return this.vectors[index];
	},
	remove : function(index) {
		return this.vectors.splice(index, 1)[0];
	},
	getByName : function(name) {
		for (var i = 0, length = this.vectors.length; i < length; i++) {
			if (name === this.vectors[i].getName()) {
				return this.vectors[i];
			}
		}
	},
	getMetadataCount : function() {
		return this.vectors.length;
	}
};
