/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2015 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


package org.broad.igv.ui.util;

import org.broad.igv.logging.*;
import org.broad.igv.exceptions.DataLoadException;
import org.broad.igv.feature.genome.GenomeManager;
import org.broad.igv.feature.tribble.CodecFactory;
import org.broad.igv.sam.reader.AlignmentIndexer;
import org.broad.igv.sam.reader.FeatureIndex;
import org.broad.igv.tools.IgvTools;
import org.broad.igv.util.ResourceLocator;
import htsjdk.tribble.FeatureCodec;
import htsjdk.tribble.TribbleException;
import htsjdk.tribble.index.Index;
import htsjdk.tribble.index.IndexFactory;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import javax.swing.*;
import javax.swing.border.*;


public class IndexCreatorDialog extends org.broad.igv.ui.IGVDialog  {

    private static Logger log = LogManager.getLogger(IndexCreatorDialog.class);

    File file;
    File idxFile;
    IndexWorker worker;
    FileType fileType;
    String introText;

    private enum FileType {
        SAM,
        TRIBBLE
    }


    public static IndexCreatorDialog createShowDialog(Frame parent, File baseFile, File newIdxFile, String introText) {
        final IndexCreatorDialog dialog = new IndexCreatorDialog(parent, true, baseFile, newIdxFile, introText);
        dialog.setLocationRelativeTo(parent);

        UIUtilities.invokeAndWaitOnEventThread(new Runnable() {
            @Override
            public void run() {
                dialog.setVisible(true);
            }
        });
        return dialog;
    }

    /**
     * Creates new form IndexCreatorDialog
     */
    public IndexCreatorDialog(java.awt.Frame parent, boolean modal, File file, File idxFile, String introText) {
        super(parent, modal);
        initComponents();
        this.introText = introText;

        jLabel1.setVisible(false);

        this.file = file;
        this.idxFile = idxFile;
        this.determineFileType(file);
        if (this.fileType == null) {
            log.error("Cannot determine file type for " + file.getAbsolutePath());
        }

        String txt = introText;

        this.introTextArea.setText(txt);

        this.introTextArea.setBorder(BorderFactory.createEmptyBorder());

        switch (this.fileType) {
            case SAM:
                worker = new SamIndexWorker();
                break;
            case TRIBBLE:
                worker = new TribbleIndexWorker();
                break;
        }
    }

    private void determineFileType(File file) {

        String filename = file.getName();
        if (filename.toLowerCase().endsWith(".sam")) {
            fileType = FileType.SAM;
        } else if (CodecFactory.hasCodec(new ResourceLocator(file.getPath()), null)) {
            fileType = FileType.TRIBBLE;
        } else {
            fileType = null;
        }
    }

    public Object getIndex() {
        if (worker == null || !worker.isDone()) {
            return null;
        } else {
            try {
                return worker.get();
            } catch (Exception ex) {
                MessageUtils.showMessage(ex.getMessage());
            }
            return null;
        }
    }


    private void goButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goButtonActionPerformed

        if (worker.isDone() || worker.isCancelled()) {
            setVisible(false);
        } else {
            if (!worker.isStarted) {
                goButton.setEnabled(false);
                worker.isStarted = true;
                worker.execute();
                jLabel1.setVisible(true);
                //Haven't worked out how to publish progress yet, just going to set it to indeterminate
                if (fileType == FileType.TRIBBLE) {
                    IndexCreatorDialog.this.progressBar.setIndeterminate(true);
                    jLabel1.setText("Creating index...");
                }
            }
        }
    }

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {

        if (worker != null && worker.isStarted && !(worker.isDone() || worker.isCancelled())) {
            worker.isStarted = false;
            worker.cancel(true);  // <+ doing this before execution starts will raise an error
        }
        setVisible(false);
    }

    public class SamIndexWorker extends IndexWorker<FeatureIndex> {
        @Override
        protected FeatureIndex doInBackground() throws Exception {
            AlignmentIndexer indexer = AlignmentIndexer.getInstance(file, progressBar, this);
            return indexer.createSamIndex(idxFile, 16000);
        }
    }

    private class TribbleIndexWorker extends IndexWorker<Index> {
        @Override
        protected Index doInBackground() throws Exception {
            int binSize = IgvTools.LINEAR_BIN_SIZE;
            FeatureCodec codec = CodecFactory.getCodec(file.getAbsolutePath(), GenomeManager.getInstance().getCurrentGenome());
            if (codec != null) {
                try {
                    Index index = IndexFactory.createLinearIndex(file, codec, binSize);
                    if (index != null) {
                        IgvTools.writeTribbleIndex(index, idxFile.getAbsolutePath());
                    }
                    return index;
                } catch (TribbleException.MalformedFeatureFile e) {
                    StringBuffer buf = new StringBuffer();
                    buf.append("<html>Files must be sorted by start position prior to indexing.<br>");
                    buf.append(e.getMessage());
                    buf.append("<br><br>Note: igvtools can be used to sort the file, select \"File > Run igvtools...\".");
                    MessageUtils.showMessage(buf.toString());
                }
            } else {
                throw new DataLoadException("Unknown File Type", file.getAbsolutePath());
            }
            return null;
        }
    }

    public abstract class IndexWorker<I> extends SwingWorker<I, Void> {

        private boolean isStarted = false;

        @Override
        protected void done() {
            setVisible(false);
        }

        public void setTimeRemaining(long timeInMillis) {
            final int timeRemaining = (int) (timeInMillis / (60 * 1000));
            UIUtilities.invokeOnEventThread(new Runnable() {

                public void run() {
                    String txt = String.valueOf(timeRemaining) + " minutes";
                    if (timeRemaining == 1) {
                        txt = "1 minute";
                    } else if (timeRemaining < 1) {
                        txt = " < 1 minute";
                    }
                    timeRemainingLabel.setText(txt);
                }
            });

        }


    }

    /**
     * ProgressListener listens to "progress" property
     * changes in the SwingWorkers that search and load
     * images.
     */
    class ProgressListener implements PropertyChangeListener {
        // prevent creation without providing a progress bar
        private ProgressListener() {
        }

        ProgressListener(JProgressBar progressBar) {
            this.progressBar = progressBar;
            this.progressBar.setValue(0);
        }

        public void propertyChange(PropertyChangeEvent evt) {
            String strPropertyName = evt.getPropertyName();
            if ("progress".equals(strPropertyName)) {
                progressBar.setIndeterminate(false);
                int progress = (Integer) evt.getNewValue();
                progressBar.setValue(progress);
            }
        }

        private JProgressBar progressBar;
    }


    /**
     * This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    private void initComponents() {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
        // Generated using JFormDesigner non-commercial license
        dialogPane = new JPanel();
        contentPanel = new JPanel();
        scrollPane1 = new JScrollPane();
        introTextArea = new JTextPane();
        timeRemainingLabel = new JLabel();
        jLabel1 = new JLabel();
        progressBar = new JProgressBar();
        buttonBar = new JPanel();
        goButton = new JButton();
        cancelButton = new JButton();

        //======== this ========
        setResizable(false);
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        //======== dialogPane ========
        {
            dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
            dialogPane.setLayout(new BorderLayout());

            //======== contentPanel ========
            {
                contentPanel.setLayout(null);

                //======== scrollPane1 ========
                {
                    scrollPane1.setViewportView(introTextArea);
                }
                contentPanel.add(scrollPane1);
                scrollPane1.setBounds(0, 0, 465, 260);
                contentPanel.add(timeRemainingLabel);
                timeRemainingLabel.setBounds(255, 265, 210, 31);

                //---- jLabel1 ----
                jLabel1.setHorizontalTextPosition(SwingConstants.RIGHT);
                contentPanel.add(jLabel1);
                jLabel1.setBounds(0, 265, 210, 31);
                contentPanel.add(progressBar);
                progressBar.setBounds(0, 305, 465, progressBar.getPreferredSize().height);

                { // compute preferred size
                    Dimension preferredSize = new Dimension();
                    for(int i = 0; i < contentPanel.getComponentCount(); i++) {
                        Rectangle bounds = contentPanel.getComponent(i).getBounds();
                        preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
                        preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
                    }
                    Insets insets = contentPanel.getInsets();
                    preferredSize.width += insets.right;
                    preferredSize.height += insets.bottom;
                    contentPanel.setMinimumSize(preferredSize);
                    contentPanel.setPreferredSize(preferredSize);
                }
            }
            dialogPane.add(contentPanel, BorderLayout.CENTER);

            //======== buttonBar ========
            {
                buttonBar.setBorder(new EmptyBorder(12, 0, 0, 0));
                buttonBar.setLayout(new GridBagLayout());
                ((GridBagLayout)buttonBar.getLayout()).columnWidths = new int[] {0, 85, 80};
                ((GridBagLayout)buttonBar.getLayout()).columnWeights = new double[] {1.0, 0.0, 0.0};

                //---- goButton ----
                goButton.setText("Go");
                goButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        goButtonActionPerformed(e);
                    }
                });
                buttonBar.add(goButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
                    GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                    new Insets(0, 0, 0, 5), 0, 0));

                //---- cancelButton ----
                cancelButton.setText("Cancel");
                cancelButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        cancelButtonActionPerformed(e);
                    }
                });
                buttonBar.add(cancelButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
                    GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                    new Insets(0, 0, 0, 0), 0, 0));
            }
            dialogPane.add(buttonBar, BorderLayout.SOUTH);
        }
        contentPane.add(dialogPane, BorderLayout.CENTER);
        setSize(500, 415);
        setLocationRelativeTo(getOwner());
        // JFormDesigner - End of component initialization  //GEN-END:initComponents
    }

    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
    // Generated using JFormDesigner non-commercial license
    private JPanel dialogPane;
    private JPanel contentPanel;
    private JScrollPane scrollPane1;
    private JTextPane introTextArea;
    private JLabel timeRemainingLabel;
    private JLabel jLabel1;
    private JProgressBar progressBar;
    private JPanel buttonBar;
    private JButton goButton;
    private JButton cancelButton;
    // JFormDesigner - End of variables declaration  //GEN-END:variables
}
