define( [
            'dojo/_base/declare',
            'dojo/_base/array',
            'dojo/_base/lang',
            'dojo/aspect',
            'dojo/on',
            'JBrowse/has',
            'dojo/window',
            'dojo/dom-construct',
            'JBrowse/Util',
            'dijit/form/TextBox',
            'dijit/form/Button',
            'dijit/form/RadioButton',
            'dijit/Dialog',
            'FileSaver/FileSaver'
        ],
        function(
            declare,
            array,
            lang,
            aspect,
            on,
            has,
            dojoWindow,
            dom,
            Util,
            dijitTextBox,
            dijitButton,
            dijitRadioButton,
            dijitDialog,
            FileSaver
        ) {

/**
 * Mixin for a track that can export its data.
 * @lends JBrowse.View.Track.ExportMixin
 */
return declare( null, {

    _canSaveFiles: function() {
        return has('save-generated-files') && ! this.config.noExportFiles;
    },

    _canExport: function() {
        if( this.config.noExport )
            return false;

        var highlightedRegion = this.browser.getHighlight();
        var visibleRegion = this.browser.view.visibleRegion();
        var wholeRefSeqRegion = { ref: this.refSeq.name, start: this.refSeq.start, end: this.refSeq.end };
        var canExportVisibleRegion = this._canExportRegion( visibleRegion );
        var canExportWholeRef = this._canExportRegion( wholeRefSeqRegion );
        return highlightedRegion && this._canExportRegion( highlightedRegion )
            || this._canExportRegion( visibleRegion )
            || this._canExportRegion( wholeRefSeqRegion );
    },

    _possibleExportRegions: function() {
        var regions = [
            // the visible region
            (function() {
                 var r = dojo.clone( this.browser.view.visibleRegion() );
                 r.description = 'Visible region';
                 r.name = 'visible';
                 return r;
             }.call(this)),
            // whole reference sequence
            { ref: this.refSeq.name, start: this.refSeq.start, end: this.refSeq.end, description: 'Whole reference sequence', name: 'wholeref' }
        ];

        var highlightedRegion = this.browser.getHighlight();
        if( highlightedRegion ) {
            regions.unshift( lang.mixin( lang.clone( highlightedRegion ), { description: "Highlighted region", name: "highlight" } ) );
        }

        return regions;
    },

    _exportDialogContent: function() {
        // note that the `this` for this content function is not the track, it's the menu-rendering context
        var possibleRegions = this.track._possibleExportRegions();

        // for each region, calculate its length and determine whether we can export it
        array.forEach( possibleRegions, function( region ) {
            region.length = Math.round( region.end - region.start + 1 );
            region.canExport = this._canExportRegion( region );
        },this.track);

        var setFilenameValue = dojo.hitch(this.track, function() {
            var region = this._readRadio(form.elements.region);
            var format = nameToExtension[this._readRadio(form.elements.format)];
            form.elements.filename.value = ((this.key || this.label) + "-" + region).replace(/[^ .a-zA-Z0-9_-]/g,'-') + "." + format;
        });

        var form = dom.create('form', { onSubmit: function() { return false; } });
        var regionFieldset = dom.create('fieldset', {className: "region"}, form );
        dom.create('legend', {innerHTML: "Region to save"}, regionFieldset);

        var checked = 0;
        array.forEach( possibleRegions, function(r) {
                var locstring = Util.assembleLocString(r);
                var regionButton = new dijitRadioButton(
                    { name: "region", id: "region_"+r.name,
                      value: locstring, checked: r.canExport && !(checked++) ? "checked" : ""
                    });
                regionFieldset.appendChild(regionButton.domNode);
                var regionButtonLabel = dom.create("label", {"for": regionButton.id, innerHTML: r.description+' - <span class="locString">'
                                   +         locstring+'</span> ('+Util.humanReadableNumber(r.length)+(r.canExport ? 'b' : 'b, too large')+')'}, regionFieldset);
                if(!r.canExport) {
                    regionButton.domNode.disabled = "disabled";
                    regionButtonLabel.className = "ghosted";
                }

                on(regionButton, "click", setFilenameValue);

                dom.create('br',{},regionFieldset);
        });


        var formatFieldset = dom.create("fieldset", {className: "format"}, form);
        dom.create("legend", {innerHTML: "Format"}, formatFieldset);

        checked = 0;
        var nameToExtension = {};
        array.forEach( this.track._exportFormats(), function(fmt) {
            if( ! fmt.name ) {
                fmt = { name: fmt, label: fmt };
            }
            if( ! fmt.fileExt) {
                fmt.fileExt = fmt.name || fmt;
            }
            nameToExtension[fmt.name] = fmt.fileExt;
            var formatButton = new dijitRadioButton({ name: "format", id: "format"+fmt.name, value: fmt.name, checked: checked++?"":"checked"});
            formatFieldset.appendChild(formatButton.domNode);
            var formatButtonLabel = dom.create("label", {"for": formatButton.id, innerHTML: fmt.label}, formatFieldset);

            on(formatButton, "click", setFilenameValue);
            dom.create( "br", {}, formatFieldset );
        },this);


        var filenameFieldset = dom.create("fieldset", {className: "filename"}, form);
        dom.create("legend", {innerHTML: "Filename"}, filenameFieldset);
        dom.create("input", {type: "text", name: "filename", style: {width: "100%"}}, filenameFieldset);

        setFilenameValue();

        var actionBar = dom.create( 'div', {
            className: 'dijitDialogPaneActionBar'
        });

        // note that the `this` for this content function is not the track, it's the menu-rendering context
        var dialog = this.dialog;

        new dijitButton({ iconClass: 'dijitIconDelete', onClick: dojo.hitch(dialog,'hide'), label: 'Cancel' })
            .placeAt( actionBar );
        var viewButton = new dijitButton({ iconClass: 'dijitIconTask',
                          label: 'View',
                          disabled: ! array.some(possibleRegions,function(r) { return r.canExport; }),
                          onClick: lang.partial( this.track._exportViewButtonClicked, this.track, form, dialog )
            })
            .placeAt( actionBar );

        // don't show a download button if we for some reason can't save files
        if( this.track._canSaveFiles() ) {

            var dlButton = new dijitButton({ iconClass: 'dijitIconSave',
                              label: 'Save',
                              disabled: ! array.some(possibleRegions,function(r) { return r.canExport; }),
                              onClick: dojo.hitch( this.track, function() {
                                var format = this._readRadio( form.elements.format );
                                var region = this._readRadio( form.elements.region );
                                var filename = form.elements.filename.value.replace(/[^ .a-zA-Z0-9_-]/g,'-');
                                dlButton.set('disabled',true);
                                dlButton.set('iconClass','jbrowseIconBusy');
                                this.exportRegion( region, format, dojo.hitch( this, function( output ) {
                                    dialog.hide();
                                    this._fileDownload({ format: format, data: output, filename: filename });
                                }));
                              })})
                .placeAt( actionBar );
        }

        return [ form, actionBar ];
    },

    // run when the 'View' button is clicked in the export dialog
    _exportViewButtonClicked: function( track, form, dialog ) {
                            var viewButton = this;
                            viewButton.set('disabled',true);
                            viewButton.set('iconClass','jbrowseIconBusy');

                            var region = track._readRadio( form.elements.region );
                            var format = track._readRadio( form.elements.format );
                            var filename = form.elements.filename.value.replace(/[^ .a-zA-Z0-9_-]/g,'-');
                            track.exportRegion( region, format, function(output) {
                                dialog.hide();
                                var text = dom.create('textarea', {
                                                           rows: Math.round( dojoWindow.getBox().h / 12 * 0.5 ),
                                                           wrap: 'off',
                                                           cols: 80,
                                                           style: "maxWidth: 90em; overflow: scroll; overflow-y: scroll; overflow-x: scroll; overflow:-moz-scrollbars-vertical;",
                                                           readonly: true
                                                       });
                                text.value = output;
                                var actionBar = dom.create( 'div', {
                                    className: 'dijitDialogPaneActionBar'
                                });
                                var exportView = new dijitDialog({
                                    className: 'export-view-dialog',
                                    title: format + ' export - <span class="locString">'+ region+'</span> ('+Util.humanReadableNumber(output.length)+'bytes)',
                                    content: [ text, actionBar ]
                                });
                                new dijitButton({ iconClass: 'dijitIconDelete',
                                                  label: 'Close', onClick: dojo.hitch( exportView, 'hide' )
                                                })
                                     .placeAt(actionBar);

                                // only show a button if the browser can save files
                                if( track._canSaveFiles() ) {
                                    var saveDiv = dom.create( "div", { className: "save" }, actionBar );

                                    var saveButton = new dijitButton(
                                        {
                                            iconClass: 'dijitIconSave',
                                            label: 'Save',
                                            onClick: function() {
                                                var filename = fileNameText.get('value').replace(/[^ .a-zA-Z0-9_-]/g,'-');
                                                exportView.hide();
                                                track._fileDownload({ format: format, data: output, filename: filename });
                                            }
                                        }).placeAt(saveDiv);
                                    var fileNameText = new dijitTextBox({
                                            value: filename,
                                            style: "width: 24em"
                                        }).placeAt( saveDiv );
                                }

                                aspect.after( exportView, 'hide', function() {
                                    // manually unhook and free the (possibly huge) text area
                                    text.parentNode.removeChild( text );
                                    text = null;
                                    setTimeout( function() {
                                        exportView.destroyRecursive();
                                    }, 500 );
                                });
                                exportView.show();
                            });
                          },

    _fileDownload: function( args ) {
        FileSaver.saveAs(new Blob([args.data], {type: args.format ? 'application/x-'+args.format.toLowerCase() : 'text/plain'}), args.filename);
        // We will need to check whether this breaks the WebApollo plugin.
    },

    // cross-platform function for (portably) reading the value of a radio control. sigh. *rolls eyes*
    _readRadio: function( r ) {
        if( r.length ) {
            for( var i = 0; i<r.length; i++ ) {
                if( r[i].checked )
                    return r[i].value;
            }
        }
        return r.value;
    },

    exportRegion: function( region, format, callback ) {
        // parse the locstring if necessary
        if( typeof region == 'string' )
            region = Util.parseLocString( region );

        // we can only export from the currently-visible reference
        // sequence right now
        if( region.ref != this.refSeq.name ) {
            console.error("cannot export data for ref seq "+region.ref+", "
                          + "exporting is currently only supported for the "
                          + "currently-visible reference sequence" );
            return;
        }

        dojo.global.require( [format.match(/\//)?format:'JBrowse/View/Export/'+format], dojo.hitch(this,function( exportDriver ) {
            new exportDriver({
                refSeq: this.refSeq,
                track: this,
                store: this.store
            }).exportRegion( region, callback );
        }));
    },

    _trackMenuOptions: function() {
        var opts = this.inherited(arguments);

        if( ! this.config.noExport )
            // add a "Save track data as" option to the track menu
            opts.push({ label: 'Save track data',
                        iconClass: 'dijitIconSave',
                        disabled: ! this._canExport(),
                        action: 'bareDialog',
                        content: this._exportDialogContent,
                        dialog: { id: 'exportDialog', className: 'export-dialog' }
                      });

        return opts;
    },

    _canExportRegion: function( l ) {
        //console.log('can generic export?');
        if( ! l ) return false;

        // if we have a maxExportSpan configured for this track, use it.
        if( typeof this.config.maxExportSpan == 'number' || typeof this.config.maxExportSpan == 'string' ) {
            return l.end - l.start + 1 <= this.config.maxExportSpan;
        }
        else {
            // if we know the store's feature density, then use that with
            // a limit of maxExportFeatures or 5,000 features
            var thisB = this;
            var storeStats = {};
            // will return immediately if the stats are available
            this.store.getGlobalStats( function( s ) {
                storeStats = s;
            }, function(error){ }); // error callback does nothing for now
            if( storeStats.featureDensity ) {
                return storeStats.featureDensity*(l.end - l.start) <= ( thisB.config.maxExportFeatures || 50000 );
            }
        }

        // otherwise, i guess we can export
        return true;
    }

});
});
