/*eslint no-shadow:0 no-loop-func:0*/
define("sap_viz_ext_stackedcolumnlinechart-src/js/utils/util", [], function() {
	/*
	 * In most cases, you don't need to modify the following code.
	 */
	var _util = { /*__FOLD__*/
		/*
		 * Converts data to flatten table format. Accepts MultiAxisDataAdapter, CrosstableDataset and FlattableDataset as data input.
		 * Invocation example:
		 * _util.toTable(data, [mapper], callback);
		 * data : data input
		 * mapper[optional] : a mapper that maps each data to another format.
		 * eg. mapper = function(d, [meta]){...}
		 * callback : accepts the error message and output data to generate visualization.
		 * eg. callback = function(err, data, [meta]){...}
		 */
		toTable: function(data, f1, f2) {
			var cb = f2 || f1,
				mapper = f2 ? f1 : undefined,
				rows;
			try {
				var me = this,
					parser = me._getParser(data);
				rows = parser.call(me, data);
				if (!rows) {
					rows = [];
				}
				me._meta = rows.meta;

				if (mapper) {
					rows = rows.map(function(d) {
						return mapper(d, me._meta);
					});
					rows.meta = me._meta;
				}
			} catch (err) {
				return cb(err, null, null);
			}
			if (cb) {
				return cb(null, rows, me._meta);
			} else {
				return rows;
			}

		},

		buildNameIdx: function(feeds) {
			if (feeds) {
				this._feeds = feeds;
				this._dimMap = {};
				this._mgMap = {};
				var that = this;
				feeds.forEach(function(feed) {
					if (feed.aaIndex) {
						that._dimMap[feed.name] = feed.aaIndex - 1;
					} else {
						that._mgMap[feed.name] = feed.mgIndex - 1;
					}
				});
			}
		},

		_getParser: function(data) {
			if (data.dataset) {
				var dataset = data.dataset;
				if (dataset.table) {
					return this._flat;
				} else {
					return this._cross;
				}
			}
			return this._cross;
		},

		_flat: function(data) {
			var dataset = data.dataset;
			var ret = dataset.table();
			ret.meta = {
				_dimensionSets: [dataset.dimensions()],
				_measureSets: [dataset.measures()],

				dimensions: function(i, j) {
					if (arguments.length === 2) {
						return this._dimensionSets[0][j];
					}
					return this._dimensionSets[0];
				},

				measures: function(i, j) {
					if (arguments.length === 2) {
						return this._measureSets[0][j];
					}
					return this._measureSets[0];
				}
			};

			return ret;
		},

		_parseMeta: function(meta) {
			if (!meta) {
				return null;
			} else {
				return {
					_dimMap: this._dimMap,
					_mgMap: this._mgMap,
					_meta: {
						measureSets: (function(measureSets) {
							var tmp = [];
							$.each(measureSets, function(idx, ele) {
								tmp[idx] = ele.map(function(d) {
									return d.measure;
								});
							});
							return tmp;
						}(meta.measureSets)),
						dimSets: (function(dimSets) {
							var tmp = [];
							$.each(dimSets, function(idx, ele) {
								tmp[idx] = ele.map(function(d) {
									return d.dimension;
								});
							});
							return tmp;
						}(meta.dimSets))
					},
					measures: function(i, j) {
						if (arguments.length === 0) {
							var ret = [];
							$.each(this._meta.measureSets, function(idx, ms) {
								$.each(ms, function(idx, measure) {
									ret.push(measure);
								});
							});
							return ret;
						} else if (arguments.length === 1) {
							if (this._mgMap && this._mgMap[i] !== undefined) {
								i = this._mgMap[i];
							}
							if (!this._meta.measureSets[i]) {
								throw "MeasureGroup \"" + i + "\" not found!";
							}
							return this._meta.measureSets[i];
						} else {
							return this._meta.measureSets[i][j];
						}
					},
					dimensions: function(i, j) {
						if (arguments.length === 0) {
							var ret = [];
							$.each(this._meta.dimSets, function(idx, ds) {
								$.each(ds, function(idx, dim) {
									ret.push(dim);
								});
							});
							return ret;
						} else if (arguments.length === 1) {
							if (this._dimMap && this._dimMap[i] !== undefined) {
								i = this._dimMap[i];
							}
							if (!this._meta.dimSets[i]) {
								throw "Dimension Set \"" + i + "\" not found!";
							}
							return this._meta.dimSets[i];
						} else {
							return this._meta.dimSets[i][j];
						}
					}
				};
			}
		},

		_extractCtx: function(meta, data, fdata) {
			var ctx = {},
				mvLen = data._mg[0].values[0].rows.length,
				vLen = data._mg[0].values[0].rows[0].length,
				dataCtx = [],
				i, j;

			for (i = 0; i < mvLen; i++) {
				var arr = [];
				for (j = 0; j < vLen; j++) {
					arr.push({});
				}
				dataCtx.push(arr);
			}
			$.each(data._mg, function(idx_mg, mg) {
				$.each(mg.values, function(idx_mv, mgValue) {
					var ctxRows = [];
					ctx[mgValue.col] = ctxRows;
					$.each(mgValue.rows, function(idx_a2, rows) {
						$.each(rows, function(idx_a1, row) {
							ctxRows.push(row.ctx);
							dataCtx[idx_a2][idx_a1][mgValue.col] = row.ctx;
						});
					});
				});
			});
			$.each(data._aa, function(idx, aa) {
				$.each(aa.values, function(idx, axis) {
					var ctxRows = [];
					ctx[axis.col.val] = ctxRows;
					$.each(axis.rows, function(idx, row) {
						ctxRows.push(row.ctx);
					});
				});
			});
			fdata.forEach(function(e, idxFdata) {
				Object.defineProperty(e, "context", {
					enumerable: false,
					get: function() {
						return (function(ctxs) {
							return function(measure) {
								if (ctxs && ctxs[measure]) {
									return {
										ctx: [ctxs[measure].path]
									};
								}
								return {
									ctx: [ctxs[measure]]
								};
							};
						}(dataCtx[Math.floor(idxFdata / vLen)][idxFdata % vLen]));
					}
				});
			});
			meta._ctx = ctx;
			meta.context = function(col, dataIdx) {
				return this._ctx[col][dataIdx];
			};
		},

		_cross: function(data) {
			var ret = this._toFlattenTable(data);
			if (!ret) {
				return null;
			}
			return ret;
		},
		/*
		 * extract dimension sets from data
		 * @param data [Crosstable Dataset] crosstable dataset
		 * @returns array of dimension sets, and each dimension set is an object of {dimension: "dimension name", data: [members]}.
		 * e.g. [{dimension: "country", data: ["China", "US", ...]}, {dimension: "year", data: ["2010", "2011", ...]}, ...]
		 */
		_extractDimSets: function(data) {
			var dimSet1, dimSet2, res = [];
			if (data.getAnalysisAxisDataByIdx) {
				dimSet1 = data.getAnalysisAxisDataByIdx(0);
				dimSet2 = data.getAnalysisAxisDataByIdx(1);
			} else if (data.dataset && data.dataset.data) {
				var analysisAxis = data.dataset.data().analysisAxis;
				if (analysisAxis) {
					analysisAxis.forEach(function(g) {
						var resg = [];
						g.data.forEach(function(d) {
							var result = {};
							result.data = [];
							for (var prop in d.values) {
								if (d.values.hasOwnProperty(prop)) {
									result.data[prop] = d.values[prop];
								}
							}
							result.dimension = d.name;
							resg.push(result);
						});
						res.push(resg);
					});
				}
				return res;
			}

			$.each([dimSet1, dimSet2], function(idx, dimSet) {
				dimSet = dimSet ? dimSet.values : undefined;
				if (!dimSet) {
					return;
				}
				var dims = [],
					dim;
				for (var i = 0; i < dimSet.length; i++) {
					dim = {
						dimension: dimSet[i].col.val,
						data: []
					};
					for (var j = 0; j < dimSet[i].rows.length; j++) {
						dim.data.push(dimSet[i].rows[j].val);
					}
					dims.push(dim);
				}
				res.push(dims);
			});
			return res;
		},

		/*
		 * extract measure sets from data
		 * @param data [Crosstable Dataset] crosstable dataset
		 * @returns array of measures, and each measure is an object of {measure: "measure name", data: [measure data]}.
		 * for example, [[{measure: "income", data: [555, 666, 777, ...]}, {measure: "cost", data:[55, 66, 77, ...]}, ...], ...]
		 */
		_extractMeasureSets: function(data) {
			var measureSet1, measureSet2, measureSet3, reses = [];
			if (data.getMeasureValuesGroupDataByIdx) {
				measureSet1 = data.getMeasureValuesGroupDataByIdx(0);
				measureSet2 = data.getMeasureValuesGroupDataByIdx(1);
				measureSet3 = data.getMeasureValuesGroupDataByIdx(2);
			} else if (data.dataset && data.dataset.data) {
				data.dataset.data().measureValuesGroup.forEach(function(g) {
					var resg = [];
					g.data.forEach(function(d) {
						var result = {};
						result.data = [];
						for (var prop in d.values) {
							if (d.values.hasOwnProperty(prop)) {
								result.data[prop] = d.values[prop];
							}
						}
						result.measure = d.name;
						resg.push(result);
					});
					reses.push(resg);
				});
				return reses;
			}

			$.each([measureSet1, measureSet2, measureSet3], function(idx, measureSet) {
				measureSet = measureSet ? measureSet.values : undefined;
				if (!measureSet) {
					return;
				}
				var res = [],
					resItem, resData, measure;
				for (var k = 0; k < measureSet.length; k++) {
					measure = measureSet[k];
					resItem = {
						measure: measure.col,
						data: []
					};
					resData = resItem.data;
					for (var i = 0; i < measure.rows.length; i++) {
						resData[i] = [];
						for (var j = 0; j < measure.rows[i].length; j++) {
							resData[i].push(measure.rows[i][j].val);
						}
					}
					res.push(resItem);
				}
				reses.push(res);
			});

			return reses;
		},

		/*
		 * convert crosstable data to flatten table data
		 * @param data [Crosstable Dataset] crosstable dataset or MultiAxisDataAdapter
		 * @returns array of objects, and each object represents a row of data table:
		 * [{"dimension name1" : value1, "dimension name2" : value2, "measure name1" : value3}, ....{"dimension name1" : valueN1, "dimension name2" : valueN2, "measure name1" : valueN3} ]
		 *
		 * This method returns an extra meta data in data.meta, which includes all dimension and measure sets.
		 */
		_toFlattenTable: function(data) {
			var dimSets = this._extractDimSets(data),
				measureSets = this._extractMeasureSets(data),
				fdata = [],
				meta,
				ctx,
				d;
			//measureValueGroup is necessary in crosstable dataset
			//please directly call _util.extractDimSets() to get dimension values 
			if (measureSets.length === 0) {
				return undefined;
			}
			meta = {
				dimSets: dimSets,
				measureSets: measureSets
			};

			if (data.getAnalysisAxisDataByIdx) {
				fdata = this._toFlatJsonArray(measureSets, dimSets);
				/**Extract data context for MultiAxisDataAdapter*/
				meta = this._parseMeta(meta);
				this._extractCtx(meta, data, fdata);
			} else {
				if (data && data.dataset) {
					d = new sap.viz.api.data.CrosstableDataset();
					d.data(data.dataset.data());
					d.info(data.dataset.info());
				} else if (data) {
					d = data;
				}
				if (sap.viz.extapi.utils && sap.viz.extapi.utils.Data && sap.viz.extapi.utils.Data.getDataContext) {
					ctx = sap.viz.extapi.utils.Data.getDataContext(d);
				}
				fdata = this._toFlatJsonArray(measureSets, dimSets, ctx);
				meta = this._parseMeta(meta);
			}

			//fill meta data. for compatible
			fdata.meta = meta;
			return fdata;
		},

		_toFlatJsonArray: function(measureSets, dimSets, ctx) {
			//convert data from ct to flat
			var fdata = [],
				measure0Data, i, j, m, measure, datumCtx, datum;

			measure0Data = measureSets[0][0].data;
			for (i = 0; i < measure0Data.length; i++) {
				for (j = 0; j < measure0Data[i].length; j++) {
					datum = {};
					datumCtx = {};
					$.each(dimSets, function(idx, dimSet) {
						if (!dimSet) {
							return;
						}
						var counter = idx === 0 ? j : i;
						for (m = 0; m < dimSet.length; m++) {
							datum[dimSet[m].dimension] = dimSet[m].data[counter];
						}
					});
					$.each(measureSets, function(idx, measureSet) {
						if (!measureSet) {
							return;
						}
						for (m = 0; m < measureSet.length; m++) {
							measure = measureSet[m];
							datum[measure.measure] = measure.data[i][j];
							if (ctx) {
								/**currently not support same measure name in different measureGroup*/
								datumCtx[measure.measure] = ctx[idx][m][i][j];
							}
						}
					});
					if (ctx) {
						Object.defineProperty(datum, "context", {
							enumerable: false,
							value: (function(ctxs) {
								return function(measure) {
									if (ctxs && ctxs[measure]) {
										return {
											ctx: [ctxs[measure].path]
										};
									}
									return {
										ctx: [ctxs[measure]]
									};
								};
							}(datumCtx))
						});
					}
					fdata.push(datum);
				}
			}
			return fdata;
		},

		composeSelection: function(measure, val, ele, ctx) {
			var len = 1,
				selectionData = [],
				selectElements = [];

			for (var i = 0; i < len; i++) {
				selectionData.push({
					val: val,
					ctx: ctx
				});
			}

			selectElements.push({
				target: ele,
				data: selectionData
			});
			return selectElements;
		}
	};
	return _util;
});