
/**
 * @class Links 
 * @description A simple panel which loads the Links app
 * @extends Ext.Panel
 * @extends Voyeur.Tool
 * @author Andrew MacDonald
 * @since 1.0
 */
Voyeur.Tool.Links = Ext.extend(Ext.Panel, {
    initialized: false,
    getLinksObjectId : function() {return this.id.replace(/-/g,'_')+'_links';},
    // tracks the document ids that have been added
    docIds: [],
    // base params for collocate calls
    baseParams: {
        stopList: 'stop.en.smart.txt',
        sortBy: 'relativeRatio',
        sortDirection: 'DESC',
        start: 0,
        limit: 5
    },
    // configuration for the flash app
    flashvars : {
        bridgeName: 'links',
        autoFit: true,
        valueSizing: true,
        removeOrphans: true
    },
    initLinks: function() {
        if (!this.initialized) {
            var id = this.getLinksObjectId();
var scripts = '<script type="text/javascript">'+
'function linksClickHandler(word, docIds, event) {'+
'if (window.console && console.info) console.info(word, event.type);'+
'var linksTool = Ext.ComponentMgr.all.find(function(object){if (object.xtype==\'voyeurLinks\') {return true;} else {return false;}});'+
'linksTool.linksClickHandler(word, docIds, event);'+
'}'+
'</script>';
            this.body.update(scripts+'<div id="'+id+'"></div>', true);
            var params = {
                menu: "false",
                scale: "noScale",
                allowFullscreen: "true",
                allowScriptAccess: "always",
                bgcolor: "#FFFFFF",
                wmode: 'opaque'
            };
            var attributes = {
                id: id,
                name: id
            };
            swfobject.embedSWF(this.getApplication().getBaseUrl()+"resources/lib/links/Links.swf", id,
                '100%', '100%', "10.0.0", "expressInstall.swf", this.flashvars, params, attributes);
            this.initialized = true;
        }
    },
    linksClickHandler: function(word, docIds, event) {
        if (event.type == 'doubleClick') {
            this.addWord(word);  // use add word so we can get frequency info first
        }
// alternate double click behaviour
/*if (event.type == 'doubleClick') {
    if (docIds == null) Voyeur.application.dispatchEvent('corpusTypeSelected', this, {type: word});
    else {
        var docIdTypes = [];
        for (var i = 0; i < docIds.length; i++) {
            docIdTypes.push(docIds[i]+':'+word);
        }
        Voyeur.application.dispatchEvent('documentTypesSelected', this, {docIdType: docIdTypes});
    }
}*/
    },
    constructor : function(config) {
        Ext.apply(this, new Voyeur.Tool(config, this));
        
        // add exporter for image
        this.exporters.img = this.localize('exportImg');
        
        Ext.applyIf(config, {
            tbar: [{
                xtype: 'textfield',
                emptyText: 'Type a word',
                regex: /^[^\s]+$/,
                regexText: 'You must enter exactly one word.',
                enableKeyEvents: true,
                listeners: {
                    keypress: {
                        fn: function(textfield, event) {
                            if (event.getKey() == 13) {
                                if (textfield.isValid()) {
                                    var word = textfield.getValue();
                                    this.addWord(word);
                                }
                            }
                        },
                        scope: this
                    }
                }
            },{
                xtype: 'button',
                text: 'Add Word',
                handler: function(button, event) {
                    var textfield = button.ownerCt.findByType('textfield')[0];
                    if (textfield.isValid()) {
                        var word = textfield.getValue();
                        this.addWord(word);
                    }
                },
                scope: this
            }]
        });
        
        Voyeur.Tool.Links.superclass.constructor.apply(this, arguments);

        this.addListener('afterrender', function(src, params) {
            this.initLinks();
        }, this);
        this.addListener('CorpusSummaryResultLoaded', function(src, params) {
            this.initLinks();
            var me = this;
            FABridge.addInitializationCallback('links', function() {
                me.addInitialContent(params);
            });
        }, this);
        this.addListener('CorpusTypeFrequenciesResultLoaded', function(src, data) {
            this.handleCorpusTypeData(this.corpusTypeReader.readRecords(data).records);
        }, this);
        this.addListener('DocumentTypeFrequenciesResultLoaded', function(src, data) {
            this.handleDocumentTypeData(this.documentTypeReader.readRecords(data).records);
        }, this);
        this.addListener('DocumentTypeCollocateFrequenciesResultLoaded', function(src, data) {
            this.handleTypeCollocateData(this.documentTypeCollocatesReader.readRecords(data).records);
        }, this);
        this.addListener('corpusDocumentSelected', function(src, params) {
            var urlParams = Ext.urlDecode(location.search.substring(1));
            var corpus = urlParams.corpus;
            this.addDocument(corpus, params.docId);
        }, this);
        this.addListener('corpusTypeSelected', function(src, params) {
            if (src != this) this.addWord(params.type);
        }, this);
        this.addListener('documentTypeSelected', function(src, params) {
            var wordParams = params.docIdType.split(':');
            this.addWord(wordParams[1]);
        }, this);
        this.addListener('export', function(exp) {
            if (exp=='img') {
                var img_win = window.open('', 'Charts: Export as Image');
                var linksApp = FABridge.links.root();
                var img_data = linksApp.getImageBinary();
                with(img_win.document) {
                    write('<html><head><title>'+this.localize('title')+'<\/title><\/head><body>' + 
                            "<img src='data:image/png;base64," + img_data + "' /><br />"+this.getFooterText()+"<p>"+this.localize("exportImgSrc")+"</p><tt>&lt;img src=\"data:image/png;base64,"+img_data+"\" alt=\"\" /&gt;</tt><\/body><\/html>")
                }
                img_win.document.close(); // stop the 'loading...' message
                img_win.focus();
            }
        }, this);
    }
    
    ,addInitialContent : function(params) {
        var linksApp = FABridge.links.root();
        linksApp.setGroupsName('Documents');
    	
        params.limit = 5;
        if (params.docId) {
            this.addDocument(params.docId);
            this.fetch('DocumentTypeFrequencies', params);
        } else {
        	params.corpus = this.getApiParamValue('corpus');
            params.limit = 5;
            params.sortBy = 'relativeKurtosis';
            params.start = 0;
            params.direction = 'DESC';
            params.sortDirection = 'DESC';
            params.extendedSortZscoreMinimum = 1;
            this.fetch('CorpusTypeFrequencies', params);
        }
    }
    
    ,addDocument : function(docId) {
        if (this.docIds.indexOf(docId) == -1) {
            this.docIds.push(docId);
        
            var doc = this.getCorpus().getDocuments().get(docId);
            var linksApp = FABridge.links.root();
            linksApp.addGroup({
                groupId: docId,
                label: doc.get('title')
            });
        }
    }
    
    ,addWord : function(word) {
        var params = {
            type: word,
            limit: 5,
            bins: 10,
            sortBy: 'rawZscoreCorpusDelta',
            extendedSortZscoreMinimum: 1
        };
        this.fetch('DocumentTypeFrequencies', params);
    }
    
    ,removeWord : function(word) {
        var linksApp = FABridge.links.root();
        linksApp.removeItem(word);
        linksApp.anchorGraph();
    }
    
    ,getCollocates : function(word) {
        for (var i = 0; i < this.docIds.length; i++) {
            var params = {
                corpus: this.getCorpus().getId(),
                docIdType: this.docIds[i]+':'+word
            };
            this.fetch('DocumentTypeCollocateFrequencies', params);
        }
    }
    
    ,handleCorpusTypeData : function (records) {
        // find document with highest relative frequency for top corpus type
        var docIndex = 0;
        var max = records[0].get('relativeMax');
        var relativeFreqs = records[0].get('relativeFreqs');
        for (var i = 0; i < relativeFreqs.length; i++) {
            if (relativeFreqs[i] == max) {
                docIndex = i;
                break;
            }
        }
        this.getCorpus().getDocuments().each(function(doc, index) {
            this.addDocument(doc.get('id'));
        }, this);
        
        var docId = this.getCorpus().getDocument(docIndex).get('id');
        
        // the the top types for the document
        var params = {
            docId: docId,
            limit: 5,
            bins: 10,
            sortBy: 'rawZscoreCorpusDelta',
            extendedSortZscoreMinimum: 1
        };
        this.fetch('DocumentTypeFrequencies', params);
    }
    
    ,handleDocumentTypeData : function (records) {
        var words = [];
        for (var i = 0; i < records.length; i++) {
            var type = records[i].get('type');
            var docId = records[i].get('docId');
            var rawFreq = records[i].get('rawFreq');
            if (rawFreq > 0) {
                words.push({
                    word: type,
                    groupId: docId,
                    value: rawFreq
                });
                
                var params = {
                    corpus: this.getCorpus().getId(),
                    docIdType: docId+':'+type
                };
                this.fetch('DocumentTypeCollocateFrequencies', params);
            }
        }
        this.sendWords(words);
    }
    
    ,handleTypeCollocateData : function (records) {
        var words = [];
        for (var i = 0; i < records.length; i++) {
            words.push({
                word: records[i].get('type'),
                groupId: records[i].get('docId'),
                value: records[i].get('rawFreq'),
                parent: records[i].get('keyword')
            });
        }
        this.sendWords(words);
    }

    ,sendWords: function(words) {
        var linksApp = FABridge.links.root();
        linksApp.addItems(words);
        linksApp.anchorGraph(1000); // anchor graph 1 second after adding items
    }
    
    /**
     * Fetch Voyeur results using the specified tool and the provided params (and other parameters as needed).
     * @params {String} tool the tool to fetch data (CorpusTypeFrequencies or DocumentTypeFrequencies)
     * @params {Object} params parameters to be sent to the tool (others may be provided by default if not specified).
     */
    ,fetch : function(tool,params) {
    	var apiParams = this.getApiParams();
        if (apiParams.stopList) {params.stopList = apiParams.stopList}
        Ext.applyIf(params, this.baseParams);
        this.update({tool: tool, params: params, title: tool})
    }
    
    ,corpusTypeReader : new Ext.data.JsonReader({
        root : 'corpusTypes.types'
        ,totalProperty : 'corpusTypes["@totalTypes"]'
    }, Ext.data.Record.create(Voyeur.data.CorpusTypes.fields))
    ,documentTypeReader : new Ext.data.JsonReader({
        root : 'documentTypes.types'
        ,totalProperty : 'documentTypes["@totalTypes"]'
    }, Ext.data.Record.create(Voyeur.data.DocumentTypes.fields)) 
    ,documentTypeCollocatesReader : new Ext.data.JsonReader({
        root : 'documentTypeCollocateFrequencies.types'
        ,totalProperty : 'documentTypeCollocateFrequencies["@totalTypes"]'
    }, Ext.data.Record.create(Voyeur.data.DocumentTypeCollocates.fields))
    
    ,showOptions : function() {
        this.showOptionsWindow({
            items : [{
                xtype : 'form',
                labelWidth : 150,
                labelAlign : 'right',
                border : false,
                items : [{
                    xtype : 'combo',
                    id : 'stopList',
                    value : this.baseParams.stopList,
                    fieldLabel : '<span ext:qtip="'
                            + this.localize('stopListTip','tool') + '">'
                            + this.localize('stopList','tool') + '</span>',
                    loadingText : this.localize('loading', 'tool'),
                    width : 180,
                    store : this.getApplication().getStopListsStore()
                    ,selectOnFocus : true
                    ,displayField: 'label'
                    ,triggerAction: 'all'
                    ,valueField: 'id'
                    ,mode: 'local'
                    ,emptyText: this.localize('none','tool')
                },{
                    xtype: 'radiogroup',
                    id: 'sizing',
                    fieldLabel: '<span ext:qtip="'
                            + this.localize('sizingTip') + '">'
                            + this.localize('sizing') + '</span>',
                    width : 200,
                    items: [
                        {boxLabel: this.localize('valueSizing'), name: 'sizing', inputValue: 'value', checked: this.flashvars.valueSizing == true},
                        {boxLabel: this.localize('linkSizing'), name: 'sizing', inputValue: 'link', checked: this.flashvars.valueSizing != true}
                    ]
                },{
                    xtype: 'checkbox',
                    id: 'autoFit',
                    checked: this.flashvars.autoFit,
                    fieldLabel: '<span ext:qtip="'
                            + this.localize('autoFitTip') + '">'
                            + this.localize('autoFit') + '</span>'
                },{
                    xtype: 'checkbox',
                    id: 'removeOrphans',
                    checked: this.flashvars.removeOrphans,
                    fieldLabel: '<span ext:qtip="'
                            + this.localize('removeOrphansTip') + '">'
                            + this.localize('removeOrphans') + '</span>'
                }],
                buttons : [{
                    text : this.localize('ok', 'tool'),
                    iconCls : 'icon-accept',
                    listeners : {
                        click : {
                            fn : function(btn) {
                                var formPanel = btn.findParentByType('form');
                                var form = formPanel.getForm();
                                var stopList = form.findField('stopList');
                                var sizing = form.findField('sizing');
                                var autoFit = form.findField('autoFit');
                                var removeOrphans = form.findField('removeOrphans');
                                
                                // make sure we don't have any queries
                                if (stopList.getValue() && !stopList.getRawValue()) {stopList.setValue('');}
                                else if (stopList.lastQuery && stopList.lastQuery!=stopList.getValue()) {
                                    stopList.getStore().loadData({stopLists: {lists: [{id: stopList.lastQuery, label: stopList.lastQuery, description: ''}]}}, true);
                                    stopList.setValue(stopList.lastQuery)
                                }

                                if (form.isDirty()) {
                                    this.baseParams.stopList = stopList.getValue();
                                    var linksApp = FABridge.links.root();
                                    this.flashvars.valueSizing = sizing.getValue().inputValue == 'value';
                                    this.flashvars.autoFit = autoFit.checked;
                                    this.flashvars.removeOrphans = removeOrphans.checked;
                                    linksApp.setValueSizing(this.flashvars.valueSizing);
                                    linksApp.setAutofit(autoFit.checked);
                                    linksApp.setRemoveOrphans(removeOrphans.checked);
                                    
                                }
                                formPanel.findParentByType('window').destroy();
                            },
                            scope : this
                        }
                    }
                }, {
                    text : this.localize('cancel', 'tool'),
                    handler : function(btn) {
                        btn.findParentByType('window').destroy();
                    }
                }]
            }]
        })
    }
    
    
    ,api: {
    	limit: {'default': 5}
    	,start: {'default': 0}
        ,extendedSortZscoreMinimum: {'default': 1}
        ,docId: {'default': null}
        ,docIndex: {'default': null}
        ,stopList: {'default': null}
    }
    
    ,i18n : {
        title : {en: "Links"}
        ,help: {
            en: "This tool finds collocates for words and displays links between them using a force directed graph.<br/><br/><b>Controls</b><br/>"+
            "Double-click a word to find its collocates.<br/>Right-click a word to remove it, or make it \"sticky\".<br/>Right-click an empty area to save the graph to an image file."
        }
        ,adaptedFrom: {
            en: "This tool is based on the <a href=\"http://taporware.mcmaster.ca/~taporware/otherTools/viscollocator.shtml\" target=\"_blank\">Visual Collocator</a>, and uses the <a href=\"http://mark-shepherd.com/blog/springgraph-flex-component/\" target=\"_blank\">SpringGraph</a> Flex component."
        }
        ,sizing: {
            en: "Node size determined by" 
        }
        ,sizingTip: {
            en: "Select either type frequencies or node links (number of collocates) to determine the size of each node."
        }
        ,valueSizing: {
            en: "Type frequency"
        }
        ,linkSizing: {
            en: "Node links" 
        }
        ,autoFit: {
            en: "Autofit graph on screen" 
        }
        ,autoFitTip: {
            en: "If checked, the graph will scroll and zoom automatically, in order to contain all nodes within the viewing area."
        }
        ,removeOrphans: {
            en: "Remove orphans" 
        }
        ,removeOrphansTip: {
            en: "If checked, nodes with no links will be automatically removed."
        }
        ,exportImg: {en : 'a static image in a new window'}
        ,exportImgSrc: {en: 'HTML source code for this image:'}
    }
});

Ext.reg('voyeurLinks', Voyeur.Tool.Links);
