/**
 * StreamGraph is a visualization that depicts the change of the frequency of words in a corpus (or within a single document).
 *
 * @example
 *
 *   let config = {
 *     "bins": null,
 *     "docId": null,
 *     "docIndex": null,
 *     "limit": null,
 *     "query": null,
 *     "stopList": null,
 *     "withDistributions": null
 *   };
 *
 *   loadCorpus("austen").tool("streamgraph", config);
 *
 * @class StreamGraph
 * @tutorial streamgraph
 * @memberof Tools
 */
Ext.define('Voyant.panel.StreamGraph', {
	extend: 'Ext.panel.Panel',
	mixins: ['Voyant.panel.Panel'],
	alias: 'widget.streamgraph',
    statics: {
    	i18n: {
    	},
    	api: {
			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {limit}
			 * @default
			 */
    		limit: 5,

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {stopList}
			 * @default
			 */
    		stopList: 'auto',

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {query}
			 */
    		query: undefined,

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {withDistributions}
			 * @default
			 */
    		withDistributions: 'relative',

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {bins}
			 * @default
			 */
    		bins: 50,

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {docIndex}
			 */
    		docIndex: undefined,

			/**
			 * @memberof Tools.StreamGraph
			 * @instance
			 * @property {docId}
			 */
    		docId: undefined
    	},
		glyph: 'xf1fe@FontAwesome'
    },
    
    config: {
    	visLayout: undefined,
    	vis: undefined,
    	mode: 'corpus',
    	
    	layerData: undefined,
    	
    	graphId: undefined,
    	options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'}]
    },
    
    graphMargin: {top: 20, right: 60, bottom: 110, left: 80},
    
    MODE_CORPUS: 'corpus',
    MODE_DOCUMENT: 'document',
    
    constructor: function(config) {
        this.callParent(arguments);
    	this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
    	
    	this.setGraphId(Ext.id(null, 'streamgraph_'));
    },
    
    initComponent: function() {
        var me = this;
        
        Ext.apply(me, {
    		title: this.localize('title'),
    		tbar: new Ext.Toolbar({
                overflowHandler: 'scroller',
				items: ['->',{
					xtype: 'legend',
					store: new Ext.data.JsonStore({
						fields: ['name', 'mark', 'active']
					}),
					listeners: {
						itemclick: function(view, record, el, index) {
							var isActive = Ext.fly(el.firstElementChild).hasCls('x-legend-inactive');
							record.set('active', isActive);
							var terms = this.getCurrentTerms();
							this.setApiParams({query: terms, limit: terms.length, stopList: undefined, categories: this.getApiParam("categories")});
							this.loadFromCorpus();
						},
						scope: this
					}
				},'->']
			}),
			bbar: {
                overflowHandler: 'scroller',
				items: [{
                	xtype: 'querysearchfield'
                },{
	            	xtype: 'button',
	            	text: this.localize('clearTerms'),
	            	handler: function() {
	            		this.setApiParams({query: undefined});
	            		this.loadFromRecords([]);
	            	},
	            	scope: this
	            },{
	            	xtype: 'corpusdocumentselector',
	            	singleSelect: true
	            },{
	            	text: this.localize('freqsMode'),
					glyph: 'xf201@FontAwesome',
				    tooltip: this.localize('freqsModeTip'),
				    menu: {
				    	items: [{
				               text: this.localize('relativeFrequencies'),
				               checked: true,
				               itemId: 'relative',
				               group: 'freqsMode',
				               checkHandler: function(item, checked) {
				            	   if (checked) {
				                	   this.setApiParam('withDistributions', 'relative');
				                	   this.loadFromCorpus();
				            	   }
				               },
				               scope: this
				           }, {
				               text: this.localize('rawFrequencies'),
				               checked: false,
				               itemId: 'raw',
				               group: 'freqsMode',
				               checkHandler: function(item, checked) {
				            	   if (checked) {
				                	   this.setApiParam('withDistributions', 'raw');
				                	   this.loadFromCorpus();
				            	   }
				               },
				               scope: this
			           }]
				    }
	            },{
	            	xtype: 'slider',
	            	itemId: 'segmentsSlider',
	            	fieldLabel: this.localize('segments'),
	            	labelAlign: 'right',
	            	labelWidth: 70,
	            	width: 150,
	            	increment: 10,
	            	minValue: 10,
	            	maxValue: 300,
	            	listeners: {
	            		afterrender: function(slider) {
	            			slider.setValue(this.getApiParam('bins'));
	            		},
	            		changecomplete: function(slider, newvalue) {
	            			this.setApiParams({bins: newvalue});
	            			this.loadFromCorpus();
	            		},
	            		scope: this
	            	}
	            }]
			}
        });
        
        this.on('loadedCorpus', function(src, corpus) {
        	if (this.getCorpus().getDocumentsCount() == 1 && this.getMode() != this.MODE_DOCUMENT) {
				this.setMode(this.MODE_DOCUMENT);
			}
			if (!('bins' in this.getModifiedApiParams())) {
				if (this.getMode() == this.MODE_CORPUS) {
					var count = corpus.getDocumentsCount();
					var binsMax = 100;
					this.setApiParam('bins', count > binsMax ? binsMax : count);
				}
			}
    		if (this.isVisible()) {
    			this.loadFromCorpus();
    		}
        }, this);
        
        this.on('corpusSelected', function(src, corpus) {
    		if (src.isXType('corpusdocumentselector')) {
    			this.setMode(this.MODE_CORPUS);
    			this.setApiParams({docId: undefined, docIndex: undefined});
    			this.setCorpus(corpus);
        		this.loadFromCorpus();
    		}
    	});
        
        this.on('documentSelected', function(src, doc) {
        	var docId = doc.getId();
        	this.setApiParam('docId', docId);
        	this.loadFromDocumentTerms();
        }, this);
        
		this.on('query', function(src, query) {
        	var terms = this.getCurrentTerms();
        	terms.push(query);
        	this.setApiParams({query: terms, limit: terms.length, stopList: undefined});
        	if (this.getMode() === this.MODE_DOCUMENT) {
        		this.loadFromDocumentTerms();
        	} else {
        		this.loadFromCorpusTerms(this.getCorpus().getCorpusTerms());
        	}
        }, this);
		
        this.on('resize', this.resizeGraph, this);
        
        this.on('boxready', this.initGraph, this);
        
        me.callParent(arguments);
    },
    
    loadFromCorpus: function() {
    	var corpus = this.getCorpus();
		if (this.getApiParam('docId') || this.getApiParam('docIndex')) {
			this.loadFromDocumentTerms();
		} else if (corpus.getDocumentsCount() == 1) {
			this.loadFromDocument(corpus.getDocument(0));
		} else {
			this.loadFromCorpusTerms(corpus.getCorpusTerms());
		}
	},

    loadFromCorpusTerms: function(corpusTerms) {
    	var params = this.getApiParams(['limit','stopList','query','withDistributions','bins','categories']);
		// ensure that we're not beyond the number of documents
		if (params.bins && params.bins > this.getCorpus().getDocumentsCount()) {
			params.bins = this.getCorpus().getDocumentsCount();
		}
		corpusTerms.load({
		    callback: function(records, operation, success) {
		    	if (success) {
		    		this.setMode(this.MODE_CORPUS);
			    	this.loadFromRecords(records);
		    	} else {
					Voyant.application.showResponseError(this.localize('failedGetCorpusTerms'), operation);
		    	}
		    },
		    scope: this,
		    params: params
    	});
    },
    
    loadFromDocument: function(document) {
    	if (document.then) {
    		var me = this;
    		document.then(function(document) {me.loadFromDocument(document);});
    	} else {
    		var ids = [];
    		if (Ext.getClassName(document)=="Voyant.data.model.Document") {
        		this.setApiParams({
        			docIndex: undefined,
        			query: undefined,
        			docId: document.getId()
        		});
        		if (this.isVisible()) {
                	this.loadFromDocumentTerms();
        		}
    		}
    	}
    },
    
    loadFromDocumentTerms: function(documentTerms) {
    	if (this.getCorpus()) {
        	documentTerms = documentTerms || this.getCorpus().getDocumentTerms({autoLoad: false});
    		documentTerms.load({
    		    callback: function(records, operation, success) {
    		    	if (success) {
    		    		this.setMode(this.MODE_DOCUMENT);
    		    		this.loadFromRecords(records);
    		    	}
    		    	else {
    					Voyant.application.showResponseError(this.localize('failedGetDocumentTerms'), operation);
    		    	}
    		    },
    		    scope: this,
    		    params: this.getApiParams(['docId','docIndex','limit','stopList','query','withDistributions','bins','categories'])
        	});
    	}
    },
    
    loadFromRecords: function(records) {
    	var legendData = [];
    	var layers = [];
    	records.forEach(function(record, index) {
    		var key = record.getTerm();
    		var values = record.get('distributions');
    		for (var i = 0; i < values.length; i++) {
    			if (layers[i] === undefined) {
    				layers[i] = {};
    			}
    			layers[i][key] = values[i];
    		}
    		legendData.push({id: key, name: key, mark: this.getApplication().getColorForTerm(key, true), active: true});
    	}, this);
    	
    	this.setLayerData(layers);
    	
    	this.down('[xtype=legend]').getStore().loadData(legendData);

    	this.doLayout();
    },
    
    doLayout: function(layers) {
    	var layers = this.getLayerData();
    	if (layers !== undefined) {
    		var me = this;
    		
	    	var keys = [];
	    	this.down('[xtype=legend]').getStore().each(function(r) { keys.push(r.getId()); });
	    	
	    	var steps;
	    	if (this.getMode() === this.MODE_DOCUMENT) {
	    		steps = this.getApiParam('bins');
	    	} else {
	    		var bins = this.getApiParam('bins');
	    		var docsCount = this.getCorpus().getDocumentsCount();
	    		
	    		steps = bins < docsCount ? bins : docsCount;
	    	}
	    	
	    	this.getVisLayout().keys(keys);
	    	var processedLayers = this.getVisLayout()(layers);
	    	
	    	var width = this.body.down('svg').getWidth() - this.graphMargin.left - this.graphMargin.right;
	    	var x = d3.scaleLinear().domain([0, steps-1]).range([0, width]);
	    	
	    	var min = d3.min(processedLayers, function(layer) {
	    		return d3.min(layer, function(d) { return d[0]; });
	    	});
	    	var max = d3.max(processedLayers, function(layer) {
	    		return d3.max(layer, function(d) { return d[1]; });
	    	});
	    	
	    	var height = this.body.down('svg').getHeight() - this.graphMargin.top - this.graphMargin.bottom;
	    	var y = d3.scaleLinear().domain([min, max]).range([height, 0]);
	    	
	    	var area = d3.area()
		    	.x(function(d, i) { return x(i); })
			    .y0(function(d) { return y(d[0]); })
			    .y1(function(d) { return y(d[1]); })
			    .curve(d3.curveCatmullRom);
	    	
	    	var xAxis;
	    	if (this.getMode() === this.MODE_CORPUS) {
	    		var xAxisDomain = [];
	    		this.getCorpus().getDocuments().each(function(doc) {
	    			xAxisDomain.push(doc.getTinyLabel());
	    		});
	    		var xAxisScale = d3.scalePoint().domain(xAxisDomain).range([0, width]);    		
	    		xAxis = d3.axisBottom(xAxisScale);
	    	} else {
	    		xAxis = d3.axisBottom(x);
	    	}
	    	
	    	var yAxis = d3.axisLeft(y);
	    	
	    	var paths = this.getVis().selectAll('path').data(processedLayers, function(d) { return d; });
	    	
	    	paths
	    		.attr('d', function(d) { return area(d); })
		    	.style('fill', function(d) { return me.getApplication().getColorForTerm(d.key, true); })
				.select('title').text(function (d) { return d.key; });
	    	
	    	paths.enter().append('path')
				.attr('d', function(d) { return area(d); })
				.style('fill', function(d) { return me.getApplication().getColorForTerm(d.key, true); })
				.append('title').text(function (d) { return d.key; });
	    	
	    	paths.exit().remove();
	    	
	    	this.getVis().selectAll('g.axis').remove();
	    	
	    	this.getVis().append('g')
	    		.attr('class', 'axis x')
	    		.attr('transform', 'translate(0,'+height+')')
	    		.call(xAxis);
	    	
	    	var xAxisText;
	    	if (this.getMode() === this.MODE_CORPUS) {
	    		this.getVis().select('g.axis.x').selectAll('text').each(function() {
					d3.select(this)
						.attr('text-anchor', 'end')
						.attr('transform', 'rotate(-45)');
	    		});
	    		
	    		xAxisText = this.localize('documents');
	    	} else {
	    		xAxisText = this.localize('documentSegments');
	    	}
	    	this.getVis().select('g.axis.x').append("text")
				.attr('text-anchor', 'middle')
				.attr('transform', 'translate('+width/2+', '+(this.graphMargin.bottom-30)+')')
				.attr('fill', '#000')
				.text(xAxisText);
	    	
	    	this.getVis().append('g')
				.attr('class', 'axis y')
				.attr('transform', 'translate(0,0)')
				.call(yAxis);
	    	
	    	var yAxisText;
	    	if (this.getApiParam('withDistributions') === 'raw') {
	    		yAxisText = this.localize('rawFrequencies');
	    	} else {
	    		yAxisText = this.localize('relativeFrequencies');
	    	}
	    	this.getVis().select('g.axis.y').append("text")
				.attr('text-anchor', 'middle')
				.attr('transform', 'translate(-'+(this.graphMargin.left-20)+', '+height/2+') rotate(-90)')
				.attr('fill', '#000')
				.text(yAxisText);
    	}
    },
    
	getCurrentTerms: function() {
    	var terms = [];
    	this.down('[xtype=legend]').getStore().each(function(record) {
    		if (record.get('active')) {
    			terms.push(record.get('name'));
    		}
    	}, this);
    	return terms;
    },
	
    initGraph: function() {
    	if (this.getVisLayout() === undefined) {
	    	var el = this.getLayout().getRenderTarget();
	    	
	    	this.setVisLayout(d3.stack().offset(d3.stackOffsetWiggle).order(d3.stackOrderInsideOut));
			this.setVis(d3.select(el.dom).append('svg').attr('id',this.getGraphId()).append('g').attr('transform', 'translate('+this.graphMargin.left+','+this.graphMargin.top+')'));
			
			this.resizeGraph();
    	}
    },
    
    resizeGraph: function() {
    	var el = this.body;//getLayout().getRenderTarget();
    	var width = el.getWidth();
		var height = el.getHeight();
		
		d3.select(el.dom).select('svg').attr('width', width).attr('height', height);
		
		this.doLayout();
    }
});